diff --git a/.config/suppress.json b/.config/suppress.json new file mode 100644 index 00000000000..55e607b1b0c --- /dev/null +++ b/.config/suppress.json @@ -0,0 +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" + } + ] +} diff --git a/.config/tsaoptions.json b/.config/tsaoptions.json new file mode 100644 index 00000000000..786ef4331a2 --- /dev/null +++ b/.config/tsaoptions.json @@ -0,0 +1,12 @@ +{ + "codebaseName": "TFSMSAzure_PowerShell", + "instanceUrl": "https://msazure.visualstudio.com", + "projectName": "One", + "areaPath": "One\\MGMT\\Compute\\Powershell\\Powershell\\PowerShell Core\\pwsh", + "notificationAliases": [ + "adityap@microsoft.com", + "dongbow@microsoft.com", + "pmeinecke@microsoft.com", + "tplunk@microsoft.com" + ] +} diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index af18c5ffe6f..c849a9f78e5 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -3,13 +3,14 @@ # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. #------------------------------------------------------------------------------------------------------------- -FROM mcr.microsoft.com/powershell/test-deps:ubuntu-18.04 +FROM mcr.microsoft.com/powershell/test-deps:ubuntu-20.04@sha256:d1609c57d2426b9cfffa3a3ab7bda5ebc4448700f8ba8ef377692c4a70e64b8c # Avoid warnings by switching to noninteractive ENV DEBIAN_FRONTEND=noninteractive # Configure apt and install packages RUN apt-get update \ + && apt-get -y upgrade \ && apt-get -y install --no-install-recommends apt-utils 2>&1 \ # # Verify git, process tools, lsb-release (common in install instructions for CLIs) installed diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c7b3de62eef..eded2d1bdec 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,16 +1,23 @@ // See https://aka.ms/vscode-remote/devcontainer.json for format details. { - "name": ".NET Core 6.0, including pwsh (Ubuntu 18.04)", - "dockerFile": "Dockerfile", + "name": ".NET Core 6.0, including pwsh (Ubuntu 18.04)", + "dockerFile": "Dockerfile", - // Uncomment the next line to run commands after the container is created. - "postCreateCommand": "cd src/powershell-unix && dotnet restore", + "workspaceMount": "source=${localWorkspaceFolder},target=/PowerShell,type=bind", + "workspaceFolder": "/PowerShell", - "extensions": [ - "ms-azure-devops.azure-pipelines", - "ms-dotnettools.csharp", - "ms-vscode.powershell", - "DavidAnson.vscode-markdownlint", - "vitaliymaz.vscode-svg-previewer" - ] + // Uncomment the next line to run commands after the container is created. + "postCreateCommand": "cd src/powershell-unix && dotnet restore", + + "customizations": { + "vscode": { + "extensions": [ + "ms-azure-devops.azure-pipelines", + "ms-dotnettools.csharp", + "ms-vscode.powershell", + "DavidAnson.vscode-markdownlint", + "vitaliymaz.vscode-svg-previewer" + ] + } + } } diff --git a/.devcontainer/fedora30/Dockerfile b/.devcontainer/fedora30/Dockerfile deleted file mode 100644 index ae8d15ebd54..00000000000 --- a/.devcontainer/fedora30/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -#------------------------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. -#------------------------------------------------------------------------------------------------------------- - -FROM mcr.microsoft.com/powershell:preview-fedora-30 - -# Configure apt and install packages -RUN dnf install -y git procps wget findutils \ - && dnf clean all diff --git a/.devcontainer/fedora30/devcontainer.json b/.devcontainer/fedora30/devcontainer.json deleted file mode 100644 index d9ef8ef5312..00000000000 --- a/.devcontainer/fedora30/devcontainer.json +++ /dev/null @@ -1,16 +0,0 @@ -// See https://aka.ms/vscode-remote/devcontainer.json for format details. -{ - "name": "Fedora 30", - "dockerFile": "Dockerfile", - - // Uncomment the next line to run commands after the container is created. - "postCreateCommand": "pwsh -c 'import-module ./build.psm1;start-psbootstrap'", - - "extensions": [ - "ms-azure-devops.azure-pipelines", - "ms-dotnettools.csharp", - "ms-vscode.powershell", - "DavidAnson.vscode-markdownlint", - "vitaliymaz.vscode-svg-previewer" - ] -} diff --git a/.editorconfig b/.editorconfig index 74496fb9a7c..72707109516 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,6 +1,6 @@ # EditorConfig is awesome: https://EditorConfig.org # .NET coding convention settings for EditorConfig -# https://docs.microsoft.com/visualstudio/ide/editorconfig-code-style-settings-reference +# https://learn.microsoft.com/visualstudio/ide/editorconfig-code-style-settings-reference # # This file comes from dotnet repositories: # https://github.com/dotnet/runtime/blob/master/.editorconfig @@ -21,6 +21,7 @@ indent_size = 4 # Shell scripts [*.sh] +end_of_line = lf indent_size = 4 # Xml project files @@ -43,6 +44,9 @@ indent_size = 2 [*.{props,targets,config,nuspec}] indent_size = 2 +[*.tsv] +indent_style = tab + # Dotnet code style settings: [*.cs] # Sort using and Import directives with System.* appearing first @@ -99,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 @@ -113,6 +119,13 @@ 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 + # CSharp code style settings: [*.cs] diff --git a/.gitattributes b/.gitattributes index 10790ce3949..c9033dc798a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,5 +2,6 @@ CHANGELOG.md merge=union * text=auto *.png binary *.rtf binary +*.sh text eol=lf testablescript.ps1 text eol=lf TestFileCatalog.txt text eol=lf diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 26e01101693..6de13fd8daf 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -6,72 +6,60 @@ # 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 d6d78e52fb7..776a9a8c60f 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: @@ -52,71 +29,69 @@ if you don't have it - `Install-Module PlatyPS`. 1. Clone the [`MicrosoftDocs/PowerShell-Docs`](https://github.com/MicrosoftDocs/PowerShell-Docs) -repo if you don't already have it. +repository if you don't already have it. 1. Start your local build of PowerShell (with the change to the cmdlet you made). -1. Find the cmdlet's markdown file in PowerShell Docs - usually under +1. Find the cmdlet's Markdown file in PowerShell Docs - usually under `PowerShell-Docs/reference///.md` (Ex. `PowerShell-Docs/reference/7/Microsoft.PowerShell.Utility/Select-String.md`) 1. Run -`Update-MarkdownHelp -Path ` +`Update-MarkdownHelp -Path ` which will update the documentation for you. 1. Make any additional changes needed for the cmdlet to be properly documented. -1. Send a Pull Request to the PowerShell Docs repo with the changes that +1. Send a Pull Request to the PowerShell Docs repository with the changes that `PlatyPS` 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 repo. +* Otherwise, these issues should be treated like any other issue in this repository. -#### Spellchecking documentation +### Spell checking documentation Documentation is spellchecked. We use the -[markdown-spellcheck](https://github.com/lukeapage/node-markdown-spellcheck) command line tool, -which can be run in interactive mode to correct typos or add words to the ignore list -(`.spelling` at the repository root). +[textlint](https://github.com/textlint/textlint/wiki/Collection-of-textlint-rule) command-line tool, +which can be run in interactive mode to correct typos. -To run the spellchecker, follow these steps: +To run the spell checker, follow these steps: * install [Node.js](https://nodejs.org/en/) (v10 or up) -* install [markdown-spellcheck](https://github.com/lukeapage/node-markdown-spellcheck) by - `npm install -g markdown-spellcheck` (v0.11.0 or up) -* run `mdspell "**/*.md" --ignore-numbers --ignore-acronyms --en-us` -* if the `.spelling` file is updated, commit and push it +* install [textlint](https://github.com/textlint/textlint/wiki/Collection-of-textlint-rule) by + `npm install -g textlint textlint-rule-terminology` +* run `textlint --rule terminology `, + adding `--fix` will accept all the recommendations. + +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, +`markdown-link-check` command-line tool, which can be run to see if any links are dead. To run the link-checker, follow these steps: * install [Node.js](https://nodejs.org/en/) (v10 or up) -* install markdown-link-check by +* install `markdown-link-check` by `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 - -Please see PowerShell [Testing Guidelines - Running Tests Outside of CI][running-tests-outside-of-ci] on how to test your build locally. +## Contributing to Issues +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 1. Follow the instructions in [Contributing to Issues][contribute-issues] to find or open an issue. @@ -136,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 @@ -160,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. @@ -168,21 +196,21 @@ Additional references: A better example is: "Add Ensure parameter to New-Item cmdlet", with "Fix #5" in the PR's body. * When you create a pull request, include a summary about your changes in the PR description. - The description is used to create change logs, + The description is used to create changelogs, so try to have the first sentence explain the benefit to end users. If the changes are related to an existing GitHub issue, please reference the issue in the PR description (e.g. ```Fix #11```). See [this][closing-via-message] for more details. * Please use the present tense and imperative mood when describing your changes: - * Instead of "Adding support for Windows Server 2012 R2", write "Add support for Windows Server 2012 R2". - * Instead of "Fixed for server connection issue", write "Fix server connection issue". + * Instead of "Adding support for Windows Server 2012 R2", write "Add support for Windows Server 2012 R2". + * Instead of "Fixed for server connection issue", write "Fix server connection issue". - This form is akin to giving commands to the code base + This form is akin to giving commands to the codebase and is recommended by the Git SCM developers. It is also used in the [Git commit messages](#common-engineering-practices). * If the change is related to a specific resource, please prefix the description with the resource name: - * Instead of "New parameter 'ConnectionCredential' in New-SqlConnection", + * Instead of "New parameter 'ConnectionCredential' in New-SqlConnection", write "New-SqlConnection: add parameter 'ConnectionCredential'". * If your change warrants an update to user-facing documentation, a Maintainer will add the `Documentation Needed` label to your PR and add an issue to the [PowerShell-Docs repository][PowerShell-Docs], @@ -190,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. @@ -201,7 +229,7 @@ Additional references: ``` - * For `.ps1` and `.psm1` files use the copyright header with empty line after it: + * For `.ps1` and `.psm1` files use the copyright header with empty line after it: ```powershell # Copyright (c) Microsoft Corporation. @@ -233,8 +261,8 @@ Additional references: * After submitting your pull request, our [CI system (Azure DevOps Pipelines)][ci-system] will run a suite of tests and automatically update the status of the pull request. -* Our CI contains automated spellchecking and link checking for markdown files. If there is any false-positive, - [run the spellchecker command line tool in interactive mode](#spellchecking-documentation) +* 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. @@ -265,7 +293,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. @@ -278,7 +306,7 @@ Additional references: - `Approve` if you believe your feedback has been addressed or the code is fine as-is, it is customary (although not required) to leave a simple "Looks good to me" (or "LGTM") as the comment for approval. - `Comment` if you are making suggestions that the *author* does not have to accept. Early in the review, it is acceptable to provide feedback on coding formatting based on the published [Coding Guidelines][coding-guidelines], however, - after the PR has been approved, it is generally _not_ recommended to focus on formatting issues unless they go against the [Coding Guidelines][coding-guidelines]. + after the PR has been approved, it is generally *not* recommended to focus on formatting issues unless they go against the [Coding Guidelines][coding-guidelines]. Non-critical late feedback (after PR has been approved) can be submitted as a new issue or new pull request from the *reviewer*. 1. *Assignees* who are always *Maintainers* ensure that proper review has occurred and if they believe one approval is not sufficient, the *maintainer* is responsible to add more reviewers. An *assignee* may also be a reviewer, but the roles are distinct. @@ -297,9 +325,9 @@ In these cases: - If the *reviewer*'s comments are very minor, merge the change, fix the code immediately, and create a new PR with the fixes addressing the minor comments. - If the changes required to merge the pull request are significant but needed, *assignee* creates a new branch with the changes and open an issue to merge the code into the dev branch. 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 code base or a design change), *assignee* will simply close the 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]. @@ -308,12 +336,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, @@ -365,7 +393,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. @@ -379,11 +407,17 @@ When your pull request is created, it is checked by the CLA bot. If you have signed the CLA, the status check will be set to `passing`. Otherwise, it will stay at `pending`. Once you sign a CLA, all your existing and future pull requests will have the status check automatically set at `passing`. -[testing-guidelines]: ../docs/testing-guidelines/testing-guidelines.md +## Code of Conduct Enforcement + +Reports of abuse will be reviewed by the [PowerShell Committee][ps-committee] and if it has been determined that violations of the +[Code of Conduct](../CODE_OF_CONDUCT.md) has occurred, then a temporary ban may be imposed. +The duration of the temporary ban will depend on the impact and/or severity of the infraction. +This can vary from 1 day, a few days, a week, and up to 30 days. +Repeat offenses may result in a permanent ban from the PowerShell org. + [running-tests-outside-of-ci]: ../docs/testing-guidelines/testing-guidelines.md#running-tests-outside-of-ci [issue-management]: ../docs/maintainers/issue-management.md [vuln-reporting]: ./SECURITY.md -[governance]: ../docs/community/governance.md [using-prs]: https://help.github.com/articles/using-pull-requests/ [fork-a-repo]: https://help.github.com/articles/fork-a-repo/ [closing-via-message]: https://help.github.com/articles/closing-issues-via-commit-messages/ @@ -395,10 +429,11 @@ Once you sign a CLA, all your existing and future pull requests will have the st [up-for-grabs]: https://github.com/powershell/powershell/issues?q=is%3Aopen+is%3Aissue+label%3AUp-for-Grabs [semantic linefeeds]: https://rhodesmill.org/brandon/2012/one-sentence-per-line/ [PowerShell-Docs]: https://github.com/powershell/powershell-docs/ -[use-vscode-editor]: https://docs.microsoft.com/dotnet/core/tutorials/with-visual-studio-code +[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 [rfc-process]: https://github.com/PowerShell/PowerShell-RFC +[ps-committee]: ../docs/community/governance.md#powershell-committee diff --git a/.github/ISSUE_TEMPLATE/Bug_Report.md b/.github/ISSUE_TEMPLATE/Bug_Report.md deleted file mode 100644 index b8c8dabe08a..00000000000 --- a/.github/ISSUE_TEMPLATE/Bug_Report.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -name: Bug report 🐛 -about: Report errors or unexpected behavior 🤔 -title: "My bug report" -labels: Needs-Triage -assignees: '' - ---- - - -## Steps to reproduce - -```powershell - -``` - -## Expected behavior - -```none - -``` - -## Actual behavior - -```none - -``` - -## Environment data - - - -```none - -``` diff --git a/.github/ISSUE_TEMPLATE/Bug_Report.yaml b/.github/ISSUE_TEMPLATE/Bug_Report.yaml new file mode 100644 index 00000000000..03fcf444e88 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug_Report.yaml @@ -0,0 +1,75 @@ +name: Bug report 🐛 +description: Report errors or unexpected behavior 🤔 +labels: Needs-Triage +body: +- type: markdown + attributes: + value: > + For Windows PowerShell 5.1 issues, suggestions, or feature requests please use the + [Feedback Hub app](https://support.microsoft.com/windows/send-feedback-to-microsoft-with-the-feedback-hub-app-f59187f8-8739-22d6-ba93-f66612949332) + + This repository is **ONLY** for PowerShell Core 6 and PowerShell 7+ issues. +- type: checkboxes + attributes: + label: Prerequisites + options: + - label: Write a descriptive title. + required: true + - label: Make sure you are able to repro it on the [latest released version](https://github.com/PowerShell/PowerShell/releases) + required: true + - label: Search the existing issues. + required: true + - label: Refer to the [FAQ](https://github.com/PowerShell/PowerShell/blob/master/docs/FAQ.md). + required: true + - label: Refer to [Differences between Windows PowerShell 5.1 and PowerShell](https://learn.microsoft.com/powershell/scripting/whats-new/differences-from-windows-powershell). + required: true +- type: textarea + attributes: + label: Steps to reproduce + description: > + List of steps, sample code, failing test or link to a project that reproduces the behavior. + Make sure you place a stack trace inside a code (```) block to avoid linking unrelated issues. + placeholder: > + I am experiencing a problem with X. + I think Y should be happening but Z is actually happening. + validations: + required: true +- type: textarea + attributes: + label: Expected behavior + render: console + placeholder: | + PS> 2 + 2 + 4 + validations: + required: true +- type: textarea + attributes: + label: Actual behavior + render: console + placeholder: | + PS> 2 + 2 + 5 + validations: + required: true +- type: textarea + attributes: + label: Error details + description: Paste verbatim output from `Get-Error` if PowerShell return an error. + render: console + placeholder: PS> Get-Error +- type: textarea + attributes: + label: Environment data + description: Paste verbatim output from `$PSVersionTable` below. + render: powershell + placeholder: PS> $PSVersionTable + validations: + required: true +- type: textarea + attributes: + label: Visuals + description: > + Please upload images or animations that can be used to reproduce issues in the area below. + Try the [Steps Recorder](https://support.microsoft.com/en-us/windows/record-steps-to-reproduce-a-problem-46582a9b-620f-2e36-00c9-04e25d784e47) + on Windows or [Screenshot](https://support.apple.com/en-us/HT208721) on macOS. diff --git a/.github/ISSUE_TEMPLATE/Distribution_Request.md b/.github/ISSUE_TEMPLATE/Distribution_Request.md deleted file mode 100644 index b104df6a1be..00000000000 --- a/.github/ISSUE_TEMPLATE/Distribution_Request.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: Distribution Support Request -about: Requests support for a new distribution -title: "Distribution Support Request" -labels: Distribution-Request, Needs-Triage -assignees: '' - ---- - -## Details of the Distribution - -- Name of the Distribution: -- Version of the Distribution: -- Package Types - - [ ] Deb - - [ ] RPM - - [ ] Tar.gz - - Snap - Please file issue in https://github.com/powershell/powershell-snap. This issues type is unrelated to snap packages with a distribution neutral. -- Processor Architecture (One per request): -- The following is a requirement for supporting a distribution **without exception.** - - [ ] 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). -- The following are requirements for supporting a distribution. - Please write a justification for any exception where these criteria are not met and - the PowerShell committee will review the request. - - [ ] The version of the Distribution is supported for at least one year. - - [ ] The version of the Distribution is not an [interim release](https://ubuntu.com/about/release-cycle) or equivalent. - -## Progress - -- [ ] An issues has been filed to create a Docker image in https://github.com/powershell/powershell-docker - -### For PowerShell Team **ONLY** - -- [ ] Docker image created -- [ ] Docker image published -- [ ] Distribution tested -- [ ] Update `packages.microsoft.com` deployment -- [ ] [Lifecycle](https://github.com/MicrosoftDocs/PowerShell-Docs/blob/staging/reference/docs-conceptual/PowerShell-Support-Lifecycle.md) updated -- [ ] Documentation Updated diff --git a/.github/ISSUE_TEMPLATE/Feature_Request.md b/.github/ISSUE_TEMPLATE/Feature_Request.md deleted file mode 100644 index 4ae10eb6e40..00000000000 --- a/.github/ISSUE_TEMPLATE/Feature_Request.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: Feature Request/Idea 🚀 -about: Suggest a new feature or improvement (this does not mean you have to implement it) -title: "Feature Request" -labels: Issue-Enhancement, Needs-Triage -assignees: '' - ---- - -## Summary of the new feature/enhancement - - - -## Proposed technical implementation details (optional) - - diff --git a/.github/ISSUE_TEMPLATE/Feature_Request.yaml b/.github/ISSUE_TEMPLATE/Feature_Request.yaml new file mode 100644 index 00000000000..c8e4cec3c4d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_Request.yaml @@ -0,0 +1,20 @@ +name: Feature Request / Idea 🚀 +description: Suggest a new feature or improvement (this does not mean you have to implement it) +labels: [Issue-Enhancement, Needs-Triage] +body: +- type: textarea + attributes: + label: Summary of the new feature / enhancement + description: > + A clear and concise description of what the problem is that the + new feature would solve. Try formulating it in user story style + (if applicable). + placeholder: "'As a user I want X so that Y...' with X being the being the action and Y being the value of the action." + validations: + required: true +- type: textarea + attributes: + label: Proposed technical implementation details (optional) + placeholder: > + A clear and concise description of what you want to happen. + Consider providing an example PowerShell experience with expected result. diff --git a/.github/ISSUE_TEMPLATE/Microsoft_Update_Issue.yaml b/.github/ISSUE_TEMPLATE/Microsoft_Update_Issue.yaml new file mode 100644 index 00000000000..ce3de7ae848 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Microsoft_Update_Issue.yaml @@ -0,0 +1,87 @@ +name: Microsoft Update issue report 🐛 +description: Report issue installing a PowerShell 7 Update or fresh install through Microsoft Update 🤔 +labels: Needs-Triage +assignees: + - TravisEz13 +body: +- type: markdown + attributes: + value: > + For Windows PowerShell 5.1 issues, suggestions, or feature requests please use the + [Feedback Hub app](https://support.microsoft.com/windows/send-feedback-to-microsoft-with-the-feedback-hub-app-f59187f8-8739-22d6-ba93-f66612949332) + + This repository is **ONLY** for PowerShell Core 6 and PowerShell 7+ issues. +- type: checkboxes + attributes: + label: Prerequisites + options: + - label: Write a descriptive title. + required: true + - label: Make sure you are able to repro it on the [latest released version](https://github.com/PowerShell/PowerShell/releases) + required: true + - label: Search the existing issues. + required: true + - label: Refer to the [FAQ](https://github.com/PowerShell/PowerShell/blob/master/docs/FAQ.md). + required: true + - label: Refer to [Differences between Windows PowerShell 5.1 and PowerShell](https://learn.microsoft.com/powershell/scripting/whats-new/differences-from-windows-powershell). + required: true +- type: textarea + attributes: + label: Steps to reproduce + description: > + List of steps, sample code, failing test or link to a project that reproduces the behavior. + Make sure you place a stack trace inside a code (```) block to avoid linking unrelated issues. + placeholder: > + I am experiencing a problem with X. + I think Y should be happening but Z is actually happening. + validations: + required: true +- type: textarea + attributes: + label: Expected behavior + render: console + placeholder: | + PS> 2 + 2 + 4 + validations: + required: true +- type: textarea + attributes: + label: Actual behavior + render: console + placeholder: | + PS> 2 + 2 + 5 + validations: + required: true +- type: textarea + attributes: + label: Environment data + description: Paste verbatim output from `$PSVersionTable` below. + render: powershell + placeholder: PS> $PSVersionTable + validations: + required: true +- type: textarea + attributes: + label: OS Data + description: Paste verbatim output from `(Get-CimInstance Win32_OperatingSystem) | Select-Object -Property Version, Caption` below. + render: powershell + placeholder: PS> (Get-CimInstance Win32_OperatingSystem) | Select-Object -Property Version, Caption + validations: + required: true +- type: textarea + attributes: + label: Windows update log + description: Please run `Get-WindowsUpdateLog` and upload the resulting file to this issue. + render: markdown + placeholder: PS> Get-WindowsUpdateLog + validations: + required: true +- type: textarea + attributes: + label: Visuals + description: > + Please upload images or animations that can be used to reproduce issues in the area below. + Try the [Steps Recorder](https://support.microsoft.com/en-us/windows/record-steps-to-reproduce-a-problem-46582a9b-620f-2e36-00c9-04e25d784e47) + on Windows or [Screenshot](https://support.apple.com/en-us/HT208721) on macOS. diff --git a/.github/ISSUE_TEMPLATE/Release_Process.md b/.github/ISSUE_TEMPLATE/Release_Process.md deleted file mode 100644 index 0f93a4bbb70..00000000000 --- a/.github/ISSUE_TEMPLATE/Release_Process.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -name: Release Process -about: Maintainers Only - Release Process -title: "Release Process for v6.x.x" -labels: Issue-Meta, Needs-Triage -assignees: '' - ---- - - - -## Checklist - -- [ ] Verify that `PowerShell-Native` has been updated/released as needed. -- [ ] Check for `PowerShellGet` and `PackageManagement` release plans. -- [ ] Start process to sync Azure DevOps artifacts feed such as modules and NuGet packages. -- [ ] Create a private branch named `release/v6.x.x` in Azure DevOps repository. - All release related changes should happen in this branch. -- [ ] Prepare packages - - [ ] Kick off coordinated build. -- [ ] Kick off Release pipeline. - - *These tasks are orchestrated by the release pipeline, but here as status to the community.* - - [ ] Prepare packages - - [ ] Sign the RPM package. - - [ ] Install and verify the packages. - - [ ] Trigger the docker staging builds (signing must be done). - - [ ] Create the release tag and push the tag to `PowerShell/PowerShell` repository. - - [ ] Run tests on all supported Linux distributions and publish results. - - [ ] Update documentation, and scripts. - - [ ] Update [CHANGELOG.md](../../CHANGELOG.md) with the finalized change log draft. - - [ ] Stage a PR to master to update other documents and - scripts to use the new package names, links, and `metadata.json`. - - [ ] For preview releases, - merge the release branch to GitHub `master` with a merge commit. - - [ ] For non-preview releases, - make sure all changes are either already in master or have a PR open. - - [ ] Delete the release branch. - - [ ] Trigger the Docker image release. - - [ ] Retain builds. - - [ ] Update https://github.com/dotnet/dotnet-docker/tree/master/3.0/sdk with new version and SHA hashes for global tool. diff --git a/.github/ISSUE_TEMPLATE/Release_Process.yaml b/.github/ISSUE_TEMPLATE/Release_Process.yaml new file mode 100644 index 00000000000..7e8d6282db1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Release_Process.yaml @@ -0,0 +1,41 @@ +name: Release Process +description: Maintainers Only - Release Process +title: "Release Process for v7.x.x" +labels: [Issue-Meta, Needs-Triage] +body: +- type: markdown + attributes: + value: > + This template is for maintainers to create an issues to track the release process. + Please **only** use this template if you are a maintainer. +- type: textarea + attributes: + label: Checklist + value: | + - [ ] Verify that [`PowerShell-Native`](https://github.com/PowerShell/PowerShell-Native) has been updated / released as needed. + - [ ] Check for `PowerShellGet` and `PackageManagement` release plans. + - [ ] Start process to sync Azure DevOps artifacts feed such as modules and NuGet packages. + - [ ] Create a private branch named `release/v6.x.x` in Azure DevOps repository. + All release related changes should happen in this branch. + - [ ] Prepare packages + - [ ] Kick off coordinated build. + - [ ] Kick off Release pipeline. + - *These tasks are orchestrated by the release pipeline, but here as status to the community.* + - [ ] Prepare packages + - [ ] Sign the RPM package. + - [ ] Install and verify the packages. + - [ ] Trigger the docker staging builds (signing must be done). + - [ ] Create the release tag and push the tag to `PowerShell/PowerShell` repository. + - [ ] Run tests on all supported Linux distributions and publish results. + - [ ] Update documentation, and scripts. + - [ ] Update [CHANGELOG.md](../../CHANGELOG.md) with the finalized change log draft. + - [ ] Stage a PR to master to update other documents and + scripts to use the new package names, links, and `metadata.json`. + - [ ] For preview releases, + merge the release branch to GitHub `master` with a merge commit. + - [ ] For non-preview releases, + make sure all changes are either already in master or have a PR open. + - [ ] Delete the release branch. + - [ ] Trigger the Docker image release. + - [ ] Retain builds. + - [ ] Update https://github.com/dotnet/dotnet-docker/tree/master/3.0/sdk with new version and SHA hashes for global tool. NOTE: this link is broken! diff --git a/.github/ISSUE_TEMPLATE/WG_member_request.yaml b/.github/ISSUE_TEMPLATE/WG_member_request.yaml new file mode 100644 index 00000000000..052a2b0f713 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/WG_member_request.yaml @@ -0,0 +1,67 @@ +name: Working Group Member Request +description: Request membership to serve on a PowerShell Working Group +title: Working Group Member Request +labels: [WG-NeedsReview, WG-Cmdlets, WG-Engine, WG-Interactive-Console, WG-Remoting, Needs-Triage] +body: +- type: markdown + attributes: + value: | + ## Thank you for your interest in joining a PowerShell Working Group. + + ### Please complete the following public form to request membership to a PowerShell Working Group. + + > [!NOTE] + > Not all Working Groups are accepting new members at this time. +- type : dropdown + id : request_type + validations: + required: true + attributes: + label: Name of Working Group you are requesting to join? + description: >- + Please select the name of the working group you are requesting to join. (Select one) + options: + - "Cmdlets and Modules" + - "Engine" + - "Interactive UX" + - "Remoting" +- type: dropdown + id: time + validations: + required: true + attributes: + label: Can you provide at least 1 hour per week to the Working Group? Note that time commitments will vary per Working Group and decided by its members. + description: >- + Please select Yes or No. + options: + - "Yes" + - "No" +- type: markdown + attributes: + value: | + ## ⚠️ This form is public. Do not provide any private or proprietary information. ⚠️ +- type: textarea + attributes: + label: Why do you want to join this working group? + description: Please provide a brief description of why you want to join this working group. + placeholder: > + I want to join this working group because... + validations: + required: true +- type: textarea + attributes: + label: What skills do you bring to this working group? + description: Please provide a brief description of what skills you bring to this working group. + placeholder: > + I bring the following skills to this working group... + validations: + required: true +- type: textarea + attributes: + label: Public links to articles, code, or other resources that demonstrate your skills. + description: Please provide public links to articles, code, or other resources that demonstrate your skills. + placeholder: > + I have the following public links to articles, code, or other resources that demonstrate my skills... + validations: + required: true + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 4e050986fa5..973921cb24a 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,11 +1,11 @@ blank_issues_enabled: false contact_links: - name: Windows PowerShell - url: https://windowsserver.uservoice.com/forums/301869-powershell + url: https://support.microsoft.com/windows/send-feedback-to-microsoft-with-the-feedback-hub-app-f59187f8-8739-22d6-ba93-f66612949332 about: Windows PowerShell issues or suggestions. - name: Support url: https://github.com/PowerShell/PowerShell/blob/master/.github/SUPPORT.md about: PowerShell Support Questions/Help - name: Documentation Issue - url: https://github.com/MicrosoftDocs/PowerShell-Docs + url: https://github.com/MicrosoftDocs/PowerShell-Docs/issues/new/choose about: Please open issues on documentation for PowerShell here. 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 b64343410f7..27089847987 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -11,35 +11,21 @@ ## PR Checklist - [ ] [PR has a meaningful title](https://github.com/PowerShell/PowerShell/blob/master/.github/CONTRIBUTING.md#pull-request---submission) - - Use the present tense and imperative mood when describing your changes + - 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** - - [ ] [Experimental feature(s) needed](https://github.com/MicrosoftDocs/PowerShell-Docs/blob/staging/reference/6/Microsoft.PowerShell.Core/About/about_Experimental_Features.md) - - [ ] Experimental feature name(s): + - [ ] None + - **OR** + - [ ] [Experimental feature(s) needed](https://github.com/MicrosoftDocs/PowerShell-Docs/blob/main/reference/7.5/Microsoft.PowerShell.Core/About/about_Experimental_Features.md) + - [ ] Experimental feature name(s): - **User-facing changes** - - [ ] Not Applicable - - **OR** - - [ ] [Documentation needed](https://github.com/PowerShell/PowerShell/blob/master/.github/CONTRIBUTING.md#pull-request---submission) - - [ ] Issue filed: + - [ ] Not Applicable + - **OR** + - [ ] [Documentation needed](https://github.com/PowerShell/PowerShell/blob/master/.github/CONTRIBUTING.md#pull-request---submission) + - [ ] 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: + - [ ] 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) diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 10633f0d6d1..f941d308b1f 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -1,12 +1,41 @@ -# Security Vulnerabilities + -Security issues are treated very seriously and will, by default, -takes precedence over other considerations including usability, performance, -etc... Best effort will be used to mitigate side effects of a security -change, but PowerShell must be secure by default. +## Security -## Reporting a security vulnerability +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin) and [PowerShell](https://github.com/PowerShell). -If you believe that there is a security vulnerability in PowerShell, -it **must** be reported to [secure@microsoft.com](https://technet.microsoft.com/security/ff852094.aspx) to allow for [Coordinated Vulnerability Disclosure](https://technet.microsoft.com/security/dn467923). -**Only** file an issue, if [secure@microsoft.com](https://www.microsoft.com/en-us/msrc/faqs-report-an-issue?rtc=1) has confirmed filing an issue is appropriate. +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +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). + +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: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). + + diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md index a34d36186ed..6acedb28d27 100644 --- a/.github/SUPPORT.md +++ b/.github/SUPPORT.md @@ -5,9 +5,9 @@ If you do not see your problem captured, please file a [new issue][] and follow Also make sure to see the [Official Support Policy][]. If you know how to fix the issue, feel free to send a pull request our way. (The [Contribution Guides][] apply to that pull request, you may want to give it a read!) -[Official Support Policy]: https://docs.microsoft.com/powershell/scripting/powershell-support-lifecycle +[Official Support Policy]: https://learn.microsoft.com/powershell/scripting/powershell-support-lifecycle [FAQ]: https://github.com/PowerShell/PowerShell/tree/master/docs/FAQ.md [Contribution Guides]: https://github.com/PowerShell/PowerShell/tree/master/.github/CONTRIBUTING.md -[known issues]: https://docs.microsoft.com/powershell/scripting/whats-new/known-issues-ps6 +[known issues]: https://learn.microsoft.com/powershell/scripting/whats-new/differences-from-windows-powershell [GitHub issues]: https://github.com/PowerShell/PowerShell/issues [new issue]: https://github.com/PowerShell/PowerShell/issues/new/choose 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..93adaf6b17a --- /dev/null +++ b/.github/actions/build/ci/action.yml @@ -0,0 +1,52 @@ +name: CI Build +description: 'Builds PowerShell' +runs: + using: composite + steps: + - name: Capture Environment + if: success() || failure() + run: 'Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose' + shell: pwsh + - name: Set Build Name for Non-PR + if: github.event_name != 'PullRequest' + run: Write-Host "##vso[build.updatebuildnumber]$env:BUILD_SOURCEBRANCHNAME-$env:BUILD_SOURCEVERSION-$((get-date).ToString("yyyyMMddhhmmss"))" + shell: pwsh + - uses: actions/setup-dotnet@v4 + 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: xUnit Tests + if: success() + continue-on-error: true + run: |- + Write-Verbose -Verbose "Running xUnit tests..." + Import-Module .\tools\ci.psm1 + Restore-PSOptions + Invoke-CIxUnit -SkipFailing + shell: pwsh + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: build + path: ${{ runner.workspace }}/build + - name: Upload xunit artifact + uses: actions/upload-artifact@v4 + with: + name: testResults-xunit + path: ${{ runner.workspace }}/xunit diff --git a/.github/actions/infrastructure/path-filters/action.yml b/.github/actions/infrastructure/path-filters/action.yml new file mode 100644 index 00000000000..78426bdff03 --- /dev/null +++ b/.github/actions/infrastructure/path-filters/action.yml @@ -0,0 +1,105 @@ +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 }} +runs: + using: composite + steps: + - name: Check if GitHubWorkflowChanges is present + id: filter + uses: actions/github-script@v7.0.1 + 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; + } + + console.log(`Getting files changed in PR #${context.issue.number}`); + + // Fetch the list of files changed in the PR + let files = []; + let page = 1; + let fetchedFiles; + do { + fetchedFiles = await github.rest.pulls.listFiles({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + per_page: 100, + page: page++ + }); + files = files.concat(fetchedFiles.data); + } while (fetchedFiles.data.length > 0); + + const actionsChanged = files.some(file => file.filename.startsWith('.github/actions')); + const workflowsChanged = files.some(file => file.filename.startsWith('.github/workflows')); + const githubChanged = actionsChanged || workflowsChanged; + + const toolsCiPsm1Changed = files.some(file => file.filename.startsWith('tools/ci.psm1')); + const toolsBuildCommonChanged = files.some(file => file.filename.startsWith('tools/buildCommon/')); + const toolsChanged = toolsCiPsm1Changed || toolsBuildCommonChanged; + + const propsChanged = files.some(file => file.filename.endsWith('.props')); + + const testsChanged = files.some(file => file.filename.startsWith('test/powershell/') || file.filename.startsWith('test/tools/') || file.filename.startsWith('test/xUnit/')); + + const mainSourceChanged = files.some(file => file.filename.startsWith('src/')); + + const buildModuleChanged = files.some(file => file.filename.startsWith('build.psm1')); + + const source = mainSourceChanged || toolsChanged || githubChanged || propsChanged || testsChanged; + + 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('source', source); + + - name: Capture outputs + run: | + Write-Verbose -Verbose "source: ${{ steps.filter.outputs.source }}" + Write-Verbose -Verbose "github: ${{ steps.filter.outputs.githubChanged }}" + Write-Verbose -Verbose "tools: ${{ steps.filter.outputs.toolsChanged }}" + Write-Verbose -Verbose "props: ${{ steps.filter.outputs.propsChanged }}" + Write-Verbose -Verbose "tests: ${{ steps.filter.outputs.testsChanged }}" + Write-Verbose -Verbose "mainSource: ${{ steps.filter.outputs.mainSourceChanged }}" + Write-Verbose -Verbose "buildModule: ${{ steps.filter.outputs.buildModuleChanged }}" + shell: pwsh diff --git a/.github/actions/test/linux-packaging/action.yml b/.github/actions/test/linux-packaging/action.yml new file mode 100644 index 00000000000..b4a9c3b55c0 --- /dev/null +++ b/.github/actions/test/linux-packaging/action.yml @@ -0,0 +1,95 @@ +name: linux_packaging +description: 'Test very basic Linux packaging' + +# This isn't working yet +# It fails with + +# ERROR: While executing gem ... (Gem::FilePermissionError) +# You don't have write permissions for the /var/lib/gems/2.7.0 directory. +# WARNING: Installation of gem dotenv 2.8.1 failed! Must resolve manually. + +runs: + using: composite + steps: + - name: Capture Environment + if: success() || failure() + run: 'Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose' + shell: pwsh + - name: Download Build Artifacts + uses: actions/download-artifact@v4 + with: + path: "${{ github.workspace }}" + - name: Capture Artifacts Directory + continue-on-error: true + run: Get-ChildItem "${{ github.workspace }}/build/*" -Recurse + shell: pwsh + + - name: Bootstrap + run: |- + Import-Module ./build.psm1 + Start-PSBootstrap -Scenario Package + shell: pwsh + - name: Capture Artifacts Directory + continue-on-error: true + run: Import-Module ./build.psm1 + shell: pwsh + - name: Extract Files + uses: actions/github-script@v7.0.0 + env: + DESTINATION_FOLDER: "${{ github.workspace }}/bins" + ARCHIVE_FILE_PATTERNS: "${{ github.workspace }}/build/build.zip" + with: + script: |- + const fs = require('fs').promises + const path = require('path') + const target = path.resolve(process.env.DESTINATION_FOLDER) + const patterns = process.env.ARCHIVE_FILE_PATTERNS + const globber = await glob.create(patterns) + await io.mkdirP(path.dirname(target)) + for await (const file of globber.globGenerator()) { + if ((await fs.lstat(file)).isDirectory()) continue + await exec.exec(`7z x ${file} -o${target} -aoa`) + } + - name: Fix permissions + continue-on-error: true + run: |- + find "${{ github.workspace }}/bins" -type d -exec chmod +rwx {} \; + find "${{ github.workspace }}/bins" -type f -exec chmod +rw {} \; + shell: bash + - name: Capture Extracted Build ZIP + continue-on-error: true + run: Get-ChildItem "${{ github.workspace }}/bins/*" -Recurse -ErrorAction SilentlyContinue + shell: pwsh + - name: Packaging Tests + 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-CIFinish + shell: pwsh + - name: Upload packages + run: |- + Get-ChildItem "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}/*.deb" -Recurse | ForEach-Object { + $packagePath = $_.FullName + Write-Host "Uploading $packagePath" + Write-Host "##vso[artifact.upload containerfolder=deb;artifactname=deb]$packagePath" + } + Get-ChildItem "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}/*.rpm" -Recurse | ForEach-Object { + $packagePath = $_.FullName + Write-Host "Uploading $packagePath" + Write-Host "##vso[artifact.upload containerfolder=rpm;artifactname=rpm]$packagePath" + } + Get-ChildItem "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}/*.tar.gz" -Recurse | ForEach-Object { + $packagePath = $_.FullName + Write-Host "Uploading $packagePath" + Write-Host "##vso[artifact.upload containerfolder=rpm;artifactname=rpm]$packagePath" + } + shell: pwsh diff --git a/.github/actions/test/nix/action.yml b/.github/actions/test/nix/action.yml new file mode 100644 index 00000000000..b338c398340 --- /dev/null +++ b/.github/actions/test/nix/action.yml @@ -0,0 +1,112 @@ +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 + +runs: + using: composite + steps: + - name: Capture Environment + if: success() || failure() + run: |- + Import-Module ./build.psm1 + Write-LogGroupStart -Title 'Environment' + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + Write-LogGroupEnd -Title 'Environment' + shell: pwsh + + - name: Download Build Artifacts + uses: actions/download-artifact@v4 + 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@v4 + with: + global-json-file: ./global.json + + - name: Bootstrap + shell: pwsh + run: |- + Import-Module ./build.psm1 + Write-LogGroupStart -Title 'Bootstrap' + Import-Module ./tools/ci.psm1 + Invoke-CIInstall -SkipUser + Write-LogGroupEnd -Title 'Bootstrap' + + - name: Extract Files + uses: actions/github-script@v7.0.0 + 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..27b94f6ebcb --- /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@v4 + 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..523de3bebaa --- /dev/null +++ b/.github/actions/test/process-pester-results/process-pester-results.ps1 @@ -0,0 +1,68 @@ +# 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 + +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 +} + +@" + +# Summary of $Name + +- Total Tests: $testCaseCount +- Total Errors: $testErrorCount +- Total Failures: $testFailureCount +- Total Not Run: $testNotRunCount +- Total Inconclusive: $testInconclusiveCount +- Total Ignored: $testIgnoredCount +- Total Skipped: $testSkippedCount +- Total Invalid: $testInvalidCount + +"@ | Out-File -FilePath $ENV:GITHUB_STEP_SUMMARY -Append + +Write-Log "Summary written to $ENV:GITHUB_STEP_SUMMARY" + +Write-LogGroupStart -Title 'Test Results' +Get-Content $ENV:GITHUB_STEP_SUMMARY +Write-LogGroupEnd -Title 'Test Results' + +if ($testErrorCount -gt 0 -or $testFailureCount -gt 0) { + Write-Error "There were $testErrorCount/$testFailureCount errors/failures in the test results." + exit 1 +} +if ($testCaseCount -eq 0) { + Write-Error "No test cases were run." + exit 1 +} diff --git a/.github/actions/test/verify_xunit/action.yml b/.github/actions/test/verify_xunit/action.yml new file mode 100644 index 00000000000..fccca27182f --- /dev/null +++ b/.github/actions/test/verify_xunit/action.yml @@ -0,0 +1,21 @@ +name: verify_xunit +description: 'Verify xUnit Results' + +runs: + using: composite + steps: + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + path: "${{ github.workspace }}" + - name: Capture artifacts directory + continue-on-error: true + run: dir "${{ github.workspace }}\testResults-xunit\*" -Recurse + shell: pwsh + - name: Test + if: success() + run: |- + Import-Module .\tools\ci.psm1 + $xUnitTestResultsFile = "${{ github.workspace }}\testResults-xunit\xUnitTestResults.xml" + Test-XUnitTestResults -TestResultsFile $xUnitTestResultsFile + shell: pwsh diff --git a/.github/actions/test/windows/action.yml b/.github/actions/test/windows/action.yml new file mode 100644 index 00000000000..734e30208f0 --- /dev/null +++ b/.github/actions/test/windows/action.yml @@ -0,0 +1,83 @@ +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 + +runs: + using: composite + steps: + - name: Capture Environment + if: success() || failure() + run: |- + Import-Module ./build.psm1 + Write-LogGroupStart -Title 'Environment' + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + Write-LogGroupEnd -Title 'Environment' + shell: pwsh + + - name: Download Build Artifacts + uses: actions/download-artifact@v4 + 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@v4 + with: + global-json-file: .\global.json + + - 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/dependabot.yml b/.github/dependabot.yml index 38a493b7f36..fdb18f82c9d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,53 +1,16 @@ 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" + - package-ecosystem: docker + 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/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" - 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" - schedule: - interval: "daily" + interval: daily labels: - "CL-BuildPackaging" diff --git a/.github/policies/IssueManagement.CloseResolutions.yml b/.github/policies/IssueManagement.CloseResolutions.yml new file mode 100644 index 00000000000..23ab9422e1a --- /dev/null +++ b/.github/policies/IssueManagement.CloseResolutions.yml @@ -0,0 +1,137 @@ +id: CloseResolutionTags +name: GitOps.PullRequestIssueManagement +description: Closing issues with Resolution* +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + scheduledSearches: + - description: Close if marked as Resolution-Declined after one day of no activity + frequencies: + - hourly: + hour: 12 + filters: + - isIssue + - isOpen + - hasLabel: + label: Resolution-Declined + - noActivitySince: + days: 1 + actions: + - addReply: + reply: This issue has been marked as declined and has not had any activity for **1 day**. It has been closed for housekeeping purposes. + - closeIssue + + - description: Close if marked as Resolution-By Design after one day of no activity + frequencies: + - hourly: + hour: 12 + filters: + - isIssue + - isOpen + - hasLabel: + label: Resolution-By Design + - noActivitySince: + days: 1 + actions: + - addReply: + reply: This issue has been marked as by-design and has not had any activity for **1 day**. It has been closed for housekeeping purposes. + - closeIssue + + - description: Close if marked as Resolution-Won't Fix after one day of no activity + frequencies: + - hourly: + hour: 12 + filters: + - isIssue + - isOpen + - hasLabel: + label: Resolution-Won't Fix + - noActivitySince: + days: 1 + actions: + - addReply: + reply: This issue has been marked as won't fix and has not had any activity for **1 day**. It has been closed for housekeeping purposes. + - closeIssue + + - description: Close if marked as Resolution-No Activity after seven day of no activity, no reply + frequencies: + - hourly: + hour: 12 + filters: + - isOpen + - isIssue + - hasLabel: + label: Resolution-No Activity + - noActivitySince: + days: 7 + actions: + - closeIssue + + - description: Close if marked as Resolution-Duplicate after one day of no activity + frequencies: + - hourly: + hour: 3 + filters: + - isIssue + - isOpen + - hasLabel: + label: Resolution-Duplicate + - noActivitySince: + days: 1 + actions: + - addReply: + reply: This issue has been marked as duplicate and has not had any activity for **1 day**. It has been closed for housekeeping purposes. + - closeIssue + + - description: Close if marked as Resolution-External after one day of no activity + frequencies: + - hourly: + hour: 3 + filters: + - isIssue + - isOpen + - hasLabel: + label: Resolution-External + - noActivitySince: + days: 1 + actions: + - addReply: + reply: This issue has been marked as external and has not had any activity for **1 day**. It has been be closed for housekeeping purposes. + - closeIssue + + - description: Close if marked as Resolution-Answered after one day of no activity + frequencies: + - hourly: + hour: 12 + filters: + - isIssue + - isOpen + - hasLabel: + label: Resolution-Answered + - noActivitySince: + days: 1 + actions: + - addReply: + reply: This issue has been marked as answered and has not had any activity for **1 day**. It has been closed for housekeeping purposes. + - closeIssue + + - description: Close if marked as Resolution-Fixed after one day of no activity + frequencies: + - hourly: + hour: 12 + filters: + - isIssue + - isOpen + - hasLabel: + label: Resolution-Fixed + - noActivitySince: + days: 1 + actions: + - addReply: + reply: This issue has been marked as fixed and has not had any activity for **1 day**. It has been closed for housekeeping purposes. + - closeIssue +onFailure: +onSuccess: diff --git a/.github/policies/IssueManagement.IssueFeedbackForm.yml b/.github/policies/IssueManagement.IssueFeedbackForm.yml new file mode 100644 index 00000000000..f42be69ab1e --- /dev/null +++ b/.github/policies/IssueManagement.IssueFeedbackForm.yml @@ -0,0 +1,22 @@ +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 new file mode 100644 index 00000000000..fd254715ea9 --- /dev/null +++ b/.github/policies/IssueManagement.ResolveStale.yml @@ -0,0 +1,109 @@ +id: IssueManagement.ResolveStale +name: GitOps.PullRequestIssueManagement +description: Other issue management rules for closing stale and waiting on author requests +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + scheduledSearches: + - description: Close if marked as Waiting on Author and no activity in 7 days + frequencies: + - hourly: + hour: 12 + filters: + - isOpen + - isIssue + - hasLabel: + label: Waiting on Author + - noActivitySince: + days: 7 + actions: + - addReply: + reply: This issue has been marked as "Waiting on Author" and has not had any activity for **7 day**. It has been closed for housekeeping purposes. + - closeIssue + + - description: Label as Resolution-No Activity if not labeled with KeepOpen and no activity in 6 months + frequencies: + - hourly: + hour: 24 + filters: + - isIssue + - isOpen + - isNotLabeledWith: + label: KeepOpen + - isNotLabeledWith: + label: In-PR + - isNotLabeledWith: + label: Needs-Triage + - isNotLabeledWith: + label: Resolution-No Activity + - isNotLabeledWith: + label: Issue-Meta + - isNotLabeledWith: + label: Review - Needed + - isNotLabeledWith: + label: Review - Committee + - isNotLabeledWith: + label: Review - Maintainer + - isNotLabeledWith: + label: WG-NeedsReview + # Up for grabs labeled issues will get closed after a 6 months of no activity unless KeepOpen label is included + - noActivitySince: + days: 180 + actions: + - addLabel: + label: Resolution-No Activity + - addReply: + reply: "This issue has not had any activity in 6 months, if there is no further activity in 7 days, the issue will be closed automatically.\n\nActivity in this case refers only to comments on the issue. If the issue is closed and you are the author, you can re-open the issue using the button below. Please add more information to be considered during retriage. If you are not the author but the issue is impacting you after it has been closed, please submit a new issue with updated details and a link to this issue and the original." + eventResponderTasks: + - description: Remove no resolution label if anyone comments while in 7 day window + if: + - payloadType: Issue_Comment + - hasLabel: + label: Resolution-No Activity + - isOpen + then: + - removeLabel: + label: Resolution-No Activity + + - description: If new issue comment is author then remove waiting on author + if: + - payloadType: Issue_Comment + - isActivitySender: + issueAuthor: True + - hasLabel: + label: Waiting on Author + then: + - removeLabel: + label: Waiting on Author + + - description: Remove Stale label if issue comment + if: + - payloadType: Issue_Comment + - hasLabel: + label: Stale + then: + - removeLabel: + label: Stale + + - description: Remove Needs-Triage label if issue is closed + if: + - payloadType: Issues + - isAction: + action: Closed + then: + - removeLabel: + label: Needs-Triage + + - description: Remove Keep Open label if closed by someone + if: + - payloadType: Issues + - isAction: + action: Closed + then: + - removeLabel: + label: KeepOpen +onFailure: +onSuccess: diff --git a/.github/policies/PRManagement.yml b/.github/policies/PRManagement.yml new file mode 100644 index 00000000000..9deaf0262bb --- /dev/null +++ b/.github/policies/PRManagement.yml @@ -0,0 +1,226 @@ +id: PRManagement +name: GitOps.PullRequestIssueManagement +description: Collection of PR bot triaging behaviors +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + scheduledSearches: + - description: If Stale label and waiting on author and no activity since 10 days then close the PR + frequencies: + - hourly: + hour: 12 + filters: + - isPullRequest + - isOpen + - hasLabel: + label: Waiting on Author + - hasLabel: + label: Stale + - noActivitySince: + days: 10 + actions: + - closeIssue + + - description: If PR has Waiting on Author label and no activity in 15 days label as stale. + frequencies: + - hourly: + hour: 3 + filters: + - isPullRequest + - isOpen + - hasLabel: + label: Waiting on Author + - noActivitySince: + days: 15 + - isNotLabeledWith: + label: Stale + actions: + - addLabel: + label: Stale + - addReply: + reply: This pull request has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for **15 days**. It will be closed if no further activity occurs **within 10 days of this comment**. + + - description: Label Review - Needed if PR is opened an no activity in 7 days but no other labels on it + frequencies: + - hourly: + hour: 12 + filters: + - isPullRequest + - isOpen + - isNotLabeledWith: + label: Waiting on Author + - noActivitySince: + days: 7 + - isNotLabeledWith: + label: Stale + - isNotLabeledWith: + label: Review - Needed + - isNotLabeledWith: + label: Review - Committee + - isNotDraftPullRequest + actions: + - addLabel: + label: Review - Needed + - addReply: + reply: >- + This pull request has been automatically marked as Review Needed because it has been there has not been any activity for **7 days**. + + Maintainer, please provide feedback and/or mark it as `Waiting on Author` + + - description: Add waiting on Author label if is draft PR, if no activity label + frequencies: + - hourly: + hour: 12 + filters: + - isOpen + - isDraftPullRequest + - isNotLabeledWith: + label: Review - Committee + - isNotLabeledWith: + label: Waiting on Author + - isNotLabeledWith: + label: Stale + - noActivitySince: + days: 3 + actions: + - addLabel: + label: Waiting on Author + eventResponderTasks: + + - description: If PR has AutoMerge Label then enable Automerge to squash + if: + - payloadType: Pull_Request + - hasLabel: + label: AutoMerge + then: + - enableAutoMerge: + mergeMethod: Squash + + - description: If PR has label AutoMerge Removed then disable Automerge + if: + - payloadType: Pull_Request + - labelRemoved: + label: AutoMerge + then: + - disableAutoMerge + + - description: If PR review requests changes then add label waiting on Author and remove review needed + if: + - payloadType: Pull_Request_Review + - isAction: + action: Submitted + - isReviewState: + reviewState: Changes_requested + then: + - addLabel: + label: Waiting on Author + - removeLabel: + label: Review - Needed + + - description: Remove Waiting on author if has label and activity from author + if: + - payloadType: Pull_Request + - isActivitySender: + issueAuthor: True + - not: + isAction: + action: Closed + - hasLabel: + label: Waiting on Author + - not: + titleContains: + pattern: "(WIP|Work in progress|\U0001F6A7)" + isRegex: True + then: + - removeLabel: + label: Waiting on Author + + - description: remove waiting on author if review by author and has waiting on author + if: + - payloadType: Pull_Request_Review + - isActivitySender: + issueAuthor: True + - hasLabel: + label: Waiting on Author + then: + - removeLabel: + label: Waiting on Author + + - description: Remove Stale label if PR has activity from author which is not closure + if: + - payloadType: Pull_Request + - not: + isAction: + action: Closed + - hasLabel: + label: Stale + - isActivitySender: + issueAuthor: True + then: + - removeLabel: + label: Stale + + - description: Remove Stale label if PR is reviewed + if: + - payloadType: Pull_Request_Review + - hasLabel: + label: Stale + then: + - removeLabel: + label: Stale + + - description: Remove Review Needed if PR is created or done any action by Admins and iSazonov + if: + - payloadType: Pull_Request + - hasLabel: + label: Review - Needed + - or: + - isAction: + action: Null + - isAction: + action: Closed + - isAction: + action: Reopened + - isAction: + action: Assigned + - isAction: + action: Unassigned + - isAction: + action: Unlabeled + - or: + - activitySenderHasPermission: + permission: Admin + - isActivitySender: + user: iSazonov + issueAuthor: False + then: + - removeLabel: + label: Review - Needed + + - description: Remove Review - Needed if issue comment is by admin or iSazonov + if: + - payloadType: Issue_Comment + - hasLabel: + label: Review - Needed + - or: + - activitySenderHasPermission: + permission: Admin + - isActivitySender: + user: iSazonov + issueAuthor: False + then: + - removeLabel: + label: Review - Needed + + - description: If inPRLabel then label in PR + if: + - payloadType: Pull_Request + then: + - inPrLabel: + label: In-PR + +onFailure: +onSuccess: diff --git a/.github/prquantifier.yaml b/.github/prquantifier.yaml new file mode 100644 index 00000000000..ea891ba4988 --- /dev/null +++ b/.github/prquantifier.yaml @@ -0,0 +1,11 @@ +# https://github.com/microsoft/PullRequestQuantifier/blob/main/docs/prquantifier-yaml.md +Excluded: +# defaults +- '*.csproj' +- prquantifier.yaml +- package-lock.json +- '*.md' +- '*.sln' +# autogenerated files +- tools/cgmanifest.json +- assets/wix/files.wxs diff --git a/.github/workflows/AssignPrs.yml b/.github/workflows/AssignPrs.yml new file mode 100644 index 00000000000..a01c0bb0950 --- /dev/null +++ b/.github/workflows/AssignPrs.yml @@ -0,0 +1,30 @@ +name: Auto Assign PR Maintainer +on: + issues: + types: [opened, edited] +permissions: + contents: read + +jobs: + run: + if: github.repository_owner == 'PowerShell' + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: wow-actions/auto-assign@67fafa03df61d7e5f201734a2fa60d1ab111880d # v3.0.2 + if: github.event.issue.pull_request + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # using the `org/team_slug` or `/team_slug` syntax to add git team as reviewers + assignees: | + TravisEz13 + daxian-dbw + adityapatwardhan + iSazonov + SeeminglyScience + skipDraft: true + skipKeywords: wip, draft + addReviewers: false + numberOfAssignees: 1 diff --git a/.github/workflows/GHWorkflowHelper/GHWorkflowHelper.psm1 b/.github/workflows/GHWorkflowHelper/GHWorkflowHelper.psm1 new file mode 100644 index 00000000000..f0524ce6f23 --- /dev/null +++ b/.github/workflows/GHWorkflowHelper/GHWorkflowHelper.psm1 @@ -0,0 +1,27 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +function Set-GWVariable { + param( + [Parameter(Mandatory = $true)] + [string]$Name, + [Parameter(Mandatory = $true)] + [string]$Value + ) + + Write-Verbose "Setting CI variable $Name to $Value" -Verbose + + if ($env:GITHUB_ENV) { + "$Name=$Value" | Out-File $env:GITHUB_ENV -Append + } +} + +function Get-GWTempPath { + $temp = [System.IO.Path]::GetTempPath() + if ($env:RUNNER_TEMP) { + $temp = $env:RUNNER_TEMP + } + + Write-Verbose "Get CI Temp path: $temp" -Verbose + return $temp +} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index a3719e2d90f..00000000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: "CodeQL" - -on: - push: - branches: [master] - pull_request: - # The branches below must be a subset of the branches above - branches: [master] - -defaults: - run: - shell: pwsh - -env: - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-16.04 - - strategy: - fail-fast: false - matrix: - # Override automatic language detection by changing the below list - # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] - language: ['csharp'] - # Learn more... - # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - fetch-depth: '0' - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - - run: | - Get-ChildItem -Path env: - name: Capture Environment - - - run: | - Import-Module .\tools\ci.psm1 - Invoke-CIInstall -SkipUser - name: Bootstrap - - - run: | - Import-Module .\tools\ci.psm1 - Invoke-CIBuild - name: Build - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/createReminders.yml b/.github/workflows/createReminders.yml new file mode 100644 index 00000000000..ca3444376bd --- /dev/null +++ b/.github/workflows/createReminders.yml @@ -0,0 +1,21 @@ +name: 'Create reminder' + +on: + issue_comment: + types: [created, edited] + +permissions: + contents: read + +jobs: + reminder: + if: github.repository_owner == 'PowerShell' + + permissions: + issues: write # for agrc/create-reminder-action to set reminders on issues + pull-requests: write # for agrc/create-reminder-action to set reminders on PRs + runs-on: ubuntu-latest + + steps: + - name: check for reminder + uses: agrc/create-reminder-action@23bc0dd98d215ad257a482ebc1bf38449e44c4f8 # v1.1.20 diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml deleted file mode 100644 index 1535fced837..00000000000 --- a/.github/workflows/daily.yml +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. - -name: PowerShell Daily -on: - workflow_dispatch: - schedule: - # At 13:00 UTC every day. - - cron: '0 13 * * *' - -defaults: - run: - shell: pwsh - -env: - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - POWERSHELL_TELEMETRY_OPTOUT: 1 - -jobs: - update-dotnet-preview: - name: Update .NET preview - timeout-minutes: 15 - runs-on: windows-latest - if: github.repository == 'PowerShell/PowerShell' - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Sync tags - run: | - git fetch --prune --unshallow --tags - - name: Execute Update .NET script - run: | - $currentVersion = (Get-Content .\global.json | ConvertFrom-Json).sdk.version - Write-Verbose "OLD_VERSION=$currentVersion" -Verbose - "OLD_VERSION=$currentVersion" | Out-File $env:GITHUB_ENV -Append - - ./tools/UpdateDotnetRuntime.ps1 -UpdateMSIPackaging - $newVersion = (Get-Content .\global.json | ConvertFrom-Json).sdk.version - Write-Verbose "NEW_VERSION=$newVersion" -Verbose - "NEW_VERSION=$newVersion" | Out-File $env:GITHUB_ENV -Append - - if ($currentVersion -ne $newVersion) { - Write-Verbose "CREATE_PR=true" -Verbose - "CREATE_PR=true" | Out-File $env:GITHUB_ENV -Append - } - - name: Create Pull Request - uses: peter-evans/create-pull-request@v2 - id: cpr - if: env.CREATE_PR == 'true' - with: - commit-message: "Update .NET SDK version from `${{ env.OLD_VERSION }}` to `${{ env.NEW_VERSION }}`" - title: "Update .NET SDK version from `${{ env.OLD_VERSION }}` to `${{ env.NEW_VERSION }}`" - base: master - branch: dotnet_update - - diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 00000000000..b85021ab000 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,22 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, +# surfacing known-vulnerable versions of the packages declared or updated in the PR. +# Once installed, if the workflow run is marked as required, +# PRs introducing known-vulnerable packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +name: 'Dependency Review' +on: [pull_request] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: 'Checkout Repository' + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: 'Dependency Review' + uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1 diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml new file mode 100644 index 00000000000..befb416aeaf --- /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@v4 + + - name: Verify PR has label starting with 'cl-' + id: verify-labels + uses: actions/github-script@v7 + 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..db0ed56ca88 --- /dev/null +++ b/.github/workflows/linux-ci.yml @@ -0,0 +1,249 @@ +name: Linux-CI + +run-name: "${{ github.ref_name }} - ${{ github.run_number }}" + +on: + workflow_dispatch: + + push: + branches: + - master + - release/** + - github-mirror + paths: + - "**" + - "!.github/ISSUE_TEMPLATE/**" + - "!.dependabot/config.yml" + - "!.pipelines/**" + - "!test/perf/**" + pull_request: + branches: + - master + - release/** + - github-mirror +# Path filters for PRs need to go into the changes job + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ contains(github.ref, 'merge')}} + +env: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 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 }} + steps: + - name: checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Change Detection + id: filter + uses: "./.github/actions/infrastructure/path-filters" + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + ci_build: + name: Build PowerShell + runs-on: ubuntu-latest + needs: changes + if: ${{ needs.changes.outputs.source == 'true' }} + steps: + - name: checkout + uses: actions/checkout@v4 + 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' }} + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 + - name: Linux Unelevated CI + uses: "./.github/actions/test/nix" + with: + purpose: UnelevatedPesterTests + tagSet: CI + linux_test_elevated_ci: + name: Linux Elevated CI + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' }} + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 + - name: Linux Elevated CI + uses: "./.github/actions/test/nix" + with: + purpose: ElevatedPesterTests + tagSet: CI + linux_test_unelevated_others: + name: Linux Unelevated Others + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' }} + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 + - name: Linux Unelevated Others + uses: "./.github/actions/test/nix" + with: + purpose: UnelevatedPesterTests + tagSet: Others + linux_test_elevated_others: + name: Linux Elevated Others + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' }} + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 + - name: Linux Elevated Others + uses: "./.github/actions/test/nix" + with: + purpose: ElevatedPesterTests + tagSet: Others + verify_xunit: + name: Verify xUnit test results + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' }} + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 + - name: Verify xUnit test results + uses: "./.github/actions/test/verify_xunit" + + analyze: + permissions: + actions: read # for github/codeql-action/init to get workflow details + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/analyze to upload SARIF results + name: Analyze + runs-on: ubuntu-latest + needs: changes + if: ${{ needs.changes.outputs.source == 'true' }} + + strategy: + fail-fast: false + matrix: + # Override automatic language detection by changing the below list + # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] + language: ['csharp'] + # Learn more... + # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: '0' + + - uses: actions/setup-dotnet@v4 + with: + global-json-file: ./global.json + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + - run: | + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + name: Capture Environment + shell: pwsh + + - run: | + Import-Module .\tools\ci.psm1 + Invoke-CIInstall -SkipUser + name: Bootstrap + shell: pwsh + + - run: | + Import-Module .\tools\ci.psm1 + Invoke-CIBuild + name: Build + shell: pwsh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 + + ready_to_merge: + name: Linux ready to merge + needs: + - verify_xunit + - linux_test_elevated_ci + - linux_test_elevated_others + - linux_test_unelevated_ci + - linux_test_unelevated_others + - analyze + if: always() + uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@v1.0.0 + with: + needs_context: ${{ toJson(needs) }} + # TODO: Enable this when we have a Linux packaging workflow + + # ERROR: While executing gem ... (Gem::FilePermissionError) + # You don't have write permissions for the /var/lib/gems/2.7.0 directory. + # WARNING: Installation of gem dotenv 2.8.1 failed! Must resolve manually. + + # linux_packaging: + # name: Attempt Linux Packaging + # needs: ci_build + # runs-on: ubuntu-20.04 + # steps: + # - name: checkout + # uses: actions/checkout@v4 + # with: + # fetch-depth: 1000 + # - name: Verify xUnit test results + # uses: "./.github/actions/test/linux-packaging" diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml new file mode 100644 index 00000000000..18a684de2d9 --- /dev/null +++ b/.github/workflows/macos-ci.yml @@ -0,0 +1,185 @@ +name: macOS-CI + +run-name: "${{ github.ref_name }} - ${{ github.run_number }}" + +on: + push: + branches: + - master + - release/** + - github-mirror + paths: + - "**" + - "!.github/ISSUE_TEMPLATE/**" + - "!.dependabot/config.yml" + - "!.pipelines/**" + - "!test/perf/**" + pull_request: + branches: + - master + - release/** + - github-mirror +# Path filters for PRs need to go into the changes job + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ contains(github.ref, 'merge')}} + +env: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 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 }} + steps: + - name: checkout + uses: actions/checkout@v4 + + - 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-latest + needs: changes + if: ${{ needs.changes.outputs.source == 'true' }} + steps: + - name: checkout + uses: actions/checkout@v4 + 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' }} + runs-on: macos-latest + steps: + - name: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 + - name: macOS Unelevated CI + uses: "./.github/actions/test/nix" + with: + purpose: UnelevatedPesterTests + tagSet: CI + macos_test_elevated_ci: + name: macOS Elevated CI + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' }} + runs-on: macos-latest + steps: + - name: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 + - name: macOS Elevated CI + uses: "./.github/actions/test/nix" + with: + purpose: ElevatedPesterTests + tagSet: CI + macos_test_unelevated_others: + name: macOS Unelevated Others + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' }} + runs-on: macos-latest + steps: + - name: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 + - name: macOS Unelevated Others + uses: "./.github/actions/test/nix" + with: + purpose: UnelevatedPesterTests + tagSet: Others + macos_test_elevated_others: + name: macOS Elevated Others + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' }} + runs-on: macos-latest + steps: + - name: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 + - name: macOS Elevated Others + uses: "./.github/actions/test/nix" + with: + purpose: ElevatedPesterTests + tagSet: Others + verify_xunit: + name: Verify xUnit test results + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' }} + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 + - name: Verify xUnit test results + uses: "./.github/actions/test/verify_xunit" + PackageMac-macos_packaging: + name: macOS packaging (bootstrap only) + needs: + - changes + if: ${{ needs.changes.outputs.source == 'true' }} + runs-on: + - macos-latest + steps: + - name: checkout + uses: actions/checkout@v4 + - name: Bootstrap packaging + if: success() || failure() + run: |- + import-module ./build.psm1 + start-psbootstrap -Scenario package + shell: pwsh + ready_to_merge: + name: macos ready to merge + needs: + - verify_xunit + - PackageMac-macos_packaging + - macos_test_elevated_ci + - macos_test_elevated_others + - macos_test_unelevated_ci + - macos_test_unelevated_others + if: always() + uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@v1.0.0 + with: + needs_context: ${{ toJson(needs) }} diff --git a/.github/workflows/markdown-link/config.json b/.github/workflows/markdown-link/config.json new file mode 100644 index 00000000000..87d65922a91 --- /dev/null +++ b/.github/workflows/markdown-link/config.json @@ -0,0 +1,7 @@ +{ + "timeout": "40s", + "retryOn429": true, + "retryCount": 5, + "fallbackRetryDelay": "30s", + "aliveStatusCodes": [504, 503, 403, 200] +} diff --git a/.github/workflows/markdownLink.yml b/.github/workflows/markdownLink.yml new file mode 100644 index 00000000000..ecd45e9ba23 --- /dev/null +++ b/.github/workflows/markdownLink.yml @@ -0,0 +1,51 @@ +on: + pull_request: + branches: + - master + +name: Check modified markdown files +permissions: + contents: read + +jobs: + markdown-link-check: + runs-on: ubuntu-latest + if: github.repository_owner == 'PowerShell' + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: gaurav-nelson/github-action-markdown-link-check@5c5dfc0ac2e225883c0e5f03a85311ec2830d368 # v1 + with: + use-quiet-mode: 'yes' + use-verbose-mode: 'yes' + check-modified-files-only: 'yes' + config-file: .github/workflows/markdown-link/config.json + markdown-lint: + permissions: + contents: read + packages: read + statuses: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + # Full git history is needed to get a proper + # list of changed files within `super-linter` + fetch-depth: 0 + - name: Load super-linter configuration + # Use grep inverse matching to exclude eventual comments in the .env file + # because the GitHub Actions command to set environment variables doesn't + # support comments. + # Ref: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#setting-an-environment-variable + run: grep -v '^#' tools/super-linter/config/super-linter.env >> "$GITHUB_ENV" + - name: Lint Markdown + uses: super-linter/super-linter@12150456a73e248bdc94d0794898f94e23127c88 # v7.4.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Super-Linter correction instructions + if: failure() + uses: actions/github-script@v7.0.1 + with: + script: | + const message = "Super-Linter found issues in the changed files. Please check the logs for details. You can run the linter locally using the command: `./tools/super-lister/super-lister.ps1`."; + core.setFailed(message); diff --git a/.github/workflows/markdownLinkDaily.yml b/.github/workflows/markdownLinkDaily.yml new file mode 100644 index 00000000000..87cdbdfd122 --- /dev/null +++ b/.github/workflows/markdownLinkDaily.yml @@ -0,0 +1,33 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +name: PowerShell Daily Markdown Link Verification + +on: + workflow_dispatch: + schedule: + # At 13:00 UTC every day. + - cron: '0 13 * * *' + +permissions: + contents: read + +jobs: + markdown-link-check: + runs-on: ubuntu-latest + if: github.repository == 'PowerShell/PowerShell' + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Check Links + uses: gaurav-nelson/github-action-markdown-link-check@5c5dfc0ac2e225883c0e5f03a85311ec2830d368 # v1 + with: + use-quiet-mode: 'yes' + use-verbose-mode: 'yes' + config-file: .github/workflows/markdown-link/config.json + - name: Microsoft Teams Notifier + uses: skitionek/notify-microsoft-teams@e7a2493ac87dad8aa7a62f079f295e54ff511d88 # master + if: failure() + with: + webhook_url: ${{ secrets.PS_BUILD_TEAMS_CHANNEL }} + overwrite: "{title: `Failure in .github/markdownLinkDaily.yml validating links. Look at ${workflow_link}`}" diff --git a/.github/workflows/processReminders.yml b/.github/workflows/processReminders.yml new file mode 100644 index 00000000000..5f179dda381 --- /dev/null +++ b/.github/workflows/processReminders.yml @@ -0,0 +1,21 @@ +name: 'Process reminders' + +on: + schedule: + - cron: '*/15 * * * *' + workflow_dispatch: + +permissions: + contents: read + +jobs: + reminder: + if: github.repository_owner == 'PowerShell' + permissions: + issues: write # for agrc/reminder-action to set reminders on issues + pull-requests: write # for agrc/reminder-action to set reminders on PRs + runs-on: ubuntu-latest + + steps: + - name: check reminders and notify + uses: agrc/reminder-action@c0ccba185490ad2c0f1de824d625f71d705a6312 # v1.0.17 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml new file mode 100644 index 00000000000..02d555446f9 --- /dev/null +++ b/.github/workflows/scorecards.yml @@ -0,0 +1,72 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '20 7 * * 2' + push: + branches: ["master"] + +# Declare default permissions as read only. +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. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + contents: read + actions: read + + steps: + - name: "Checkout code" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # - you are installing Scorecards on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 + with: + sarif_file: results.sarif diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml new file mode 100644 index 00000000000..3aaa19fcdf6 --- /dev/null +++ b/.github/workflows/windows-ci.yml @@ -0,0 +1,169 @@ +name: Windows-CI +on: + workflow_dispatch: + push: + branches: + - master + - release/** + - github-mirror + paths: + - "**" + - "!.vsts-ci/misc-analysis.yml" + - "!.github/ISSUE_TEMPLATE/**" + - "!.dependabot/config.yml" + - "!test/perf/**" + - "!.pipelines/**" + pull_request: + branches: + - master + - release/** + - github-mirror + +# Path filters for PRs need to go into the changes job + +concurrency: + group: ${{ github.workflow }}-${{ 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_SKIP_FIRST_TIME_EXPERIENCE: 1 + GIT_CONFIG_PARAMETERS: "'core.autocrlf=false'" + NugetSecurityAnalysisWarningLevel: none + POWERSHELL_TELEMETRY_OPTOUT: 1 + __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none +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 }} + steps: + - name: checkout + uses: actions/checkout@v4 + + - 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' }} + runs-on: windows-latest + steps: + - name: checkout + uses: actions/checkout@v4 + 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' }} + runs-on: windows-latest + steps: + - name: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 + - name: Windows Unelevated CI + uses: "./.github/actions/test/windows" + with: + purpose: UnelevatedPesterTests + tagSet: CI + windows_test_elevated_ci: + name: Windows Elevated CI + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' }} + runs-on: windows-latest + steps: + - name: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 + - name: Windows Elevated CI + uses: "./.github/actions/test/windows" + with: + purpose: ElevatedPesterTests + tagSet: CI + windows_test_unelevated_others: + name: Windows Unelevated Others + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' }} + runs-on: windows-latest + steps: + - name: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 + - name: Windows Unelevated Others + uses: "./.github/actions/test/windows" + with: + purpose: UnelevatedPesterTests + tagSet: Others + windows_test_elevated_others: + name: Windows Elevated Others + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' }} + runs-on: windows-latest + steps: + - name: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 + - name: Windows Elevated Others + uses: "./.github/actions/test/windows" + with: + purpose: ElevatedPesterTests + tagSet: Others + verify_xunit: + name: Verify xUnit test results + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' }} + runs-on: windows-latest + steps: + - name: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 + - name: Verify xUnit test results + uses: "./.github/actions/test/verify_xunit" + ready_to_merge: + name: windows ready to merge + needs: + - verify_xunit + - windows_test_elevated_ci + - windows_test_elevated_others + - windows_test_unelevated_ci + - windows_test_unelevated_others + if: always() + uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@v1.0.0 + with: + needs_context: ${{ toJson(needs) }} diff --git a/.gitignore b/.gitignore index fb19bcffa77..ccadde27182 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ project.lock.json # dotnet cli install/uninstall scripts dotnet-install.ps1 dotnet-install.sh +dotnet-install.sh.* dotnet-uninstall-pkgs.sh dotnet-uninstall-debian-packages.sh @@ -89,3 +90,31 @@ StyleCop.Cache # Ignore SelfSignedCertificate autogenerated files test/tools/Modules/SelfSignedCertificate/ + +# BenchmarkDotNet artifacts +test/perf/BenchmarkDotNet.Artifacts/ + +# Test generated module +test/tools/Modules/Microsoft.PowerShell.NamedPipeConnection/ + +# Test generated startup profile +StartupProfileData-NonInteractive + +# Ignore logfiles +logfile/* + +# Ignore nuget.config because it is dynamically generated +nuget.config + +# Ignore MSBuild Binary Logs +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 + +# Ignore CTRF report files +crtf/* diff --git a/.globalconfig b/.globalconfig index e5cb35160fb..d51e5cfacfa 100644 --- a/.globalconfig +++ b/.globalconfig @@ -1,1554 +1,2257 @@ is_global = true # CA1000: Do not declare static members on generic types +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1000 dotnet_diagnostic.CA1000.severity = warning -dotnet_code_quality.ca1000.api_surface = all +dotnet_code_quality.CA1000.api_surface = all # CA1001: Types that own disposable fields should be disposable +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1001 dotnet_diagnostic.CA1001.severity = silent # CA1002: Do not expose generic lists +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1002 dotnet_diagnostic.CA1002.severity = none # CA1003: Use generic event handler instances +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1003 dotnet_diagnostic.CA1003.severity = warning -dotnet_code_quality.ca1003.api_surface = private, internal +dotnet_code_quality.CA1003.api_surface = private, internal # CA1005: Avoid excessive parameters on generic types +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1005 dotnet_diagnostic.CA1005.severity = none # CA1008: Enums should have zero value +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1008 dotnet_diagnostic.CA1008.severity = none +dotnet_code_quality.CA1008.api_surface = public # CA1010: Generic interface should also be implemented +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1010 dotnet_diagnostic.CA1010.severity = silent +dotnet_code_quality.CA1010.api_surface = public # CA1012: Abstract types should not have public constructors +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1012 dotnet_diagnostic.CA1012.severity = warning -dotnet_code_quality.ca1012.api_surface = all +dotnet_code_quality.CA1012.api_surface = all # CA1014: Mark assemblies with CLSCompliant +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1014 dotnet_diagnostic.CA1014.severity = none # CA1016: Mark assemblies with assembly version +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1016 dotnet_diagnostic.CA1016.severity = warning # CA1017: Mark assemblies with ComVisible +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1017 dotnet_diagnostic.CA1017.severity = none # CA1018: Mark attributes with AttributeUsageAttribute +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1018 dotnet_diagnostic.CA1018.severity = warning # CA1019: Define accessors for attribute arguments +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1019 dotnet_diagnostic.CA1019.severity = none # CA1021: Avoid out parameters +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1021 dotnet_diagnostic.CA1021.severity = none # CA1024: Use properties where appropriate +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1024 dotnet_diagnostic.CA1024.severity = none +dotnet_code_quality.CA1024.api_surface = public # CA1027: Mark enums with FlagsAttribute +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1027 dotnet_diagnostic.CA1027.severity = none +dotnet_code_quality.CA1027.api_surface = public # CA1028: Enum Storage should be Int32 +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1028 dotnet_diagnostic.CA1028.severity = none +dotnet_code_quality.CA1028.api_surface = public # CA1030: Use events where appropriate +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1030 dotnet_diagnostic.CA1030.severity = none +dotnet_code_quality.CA1030.api_surface = public # CA1031: Do not catch general exception types +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1031 dotnet_diagnostic.CA1031.severity = none # CA1032: Implement standard exception constructors +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1032 dotnet_diagnostic.CA1032.severity = none # CA1033: Interface methods should be callable by child types +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1033 dotnet_diagnostic.CA1033.severity = none # CA1034: Nested types should not be visible +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1034 dotnet_diagnostic.CA1034.severity = none # CA1036: Override methods on comparable types +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1036 dotnet_diagnostic.CA1036.severity = silent +dotnet_code_quality.CA1036.api_surface = public # CA1040: Avoid empty interfaces +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1040 dotnet_diagnostic.CA1040.severity = none +dotnet_code_quality.CA1040.api_surface = public # CA1041: Provide ObsoleteAttribute message +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1041 dotnet_diagnostic.CA1041.severity = warning +dotnet_code_quality.CA1041.api_surface = public # CA1043: Use Integral Or String Argument For Indexers -dotnet_diagnostic.CA1043.severity = none +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1043 +dotnet_diagnostic.CA1043.severity = warning +dotnet_code_quality.CA1043.api_surface = all # CA1044: Properties should not be write only +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1044 dotnet_diagnostic.CA1044.severity = none +dotnet_code_quality.CA1044.api_surface = public # CA1045: Do not pass types by reference +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1045 dotnet_diagnostic.CA1045.severity = none # CA1046: Do not overload equality operator on reference types +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1046 dotnet_diagnostic.CA1046.severity = none # CA1047: Do not declare protected member in sealed type +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1047 dotnet_diagnostic.CA1047.severity = warning # CA1050: Declare types in namespaces +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1050 dotnet_diagnostic.CA1050.severity = warning # CA1051: Do not declare visible instance fields +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1051 dotnet_diagnostic.CA1051.severity = silent +dotnet_code_quality.CA1051.api_surface = public # CA1052: Static holder types should be Static or NotInheritable +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1052 dotnet_diagnostic.CA1052.severity = warning -dotnet_code_quality.ca1052.api_surface = private, internal +dotnet_code_quality.CA1052.api_surface = all # CA1054: URI-like parameters should not be strings +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1054 dotnet_diagnostic.CA1054.severity = none +dotnet_code_quality.CA1054.api_surface = public # CA1055: URI-like return values should not be strings +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1055 dotnet_diagnostic.CA1055.severity = none +dotnet_code_quality.CA1055.api_surface = public # CA1056: URI-like properties should not be strings +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1056 dotnet_diagnostic.CA1056.severity = none +dotnet_code_quality.CA1056.api_surface = public # CA1058: Types should not extend certain base types +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1058 dotnet_diagnostic.CA1058.severity = none +dotnet_code_quality.CA1058.api_surface = public # CA1060: Move pinvokes to native methods class +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1060 dotnet_diagnostic.CA1060.severity = none # CA1061: Do not hide base class methods +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1061 dotnet_diagnostic.CA1061.severity = warning # CA1062: Validate arguments of public methods +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1062 dotnet_diagnostic.CA1062.severity = none # CA1063: Implement IDisposable Correctly +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1063 dotnet_diagnostic.CA1063.severity = none +dotnet_code_quality.CA1063.api_surface = public # CA1064: Exceptions should be public +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1064 dotnet_diagnostic.CA1064.severity = none # CA1065: Do not raise exceptions in unexpected locations +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1065 dotnet_diagnostic.CA1065.severity = warning # CA1066: Implement IEquatable when overriding Object.Equals +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1066 dotnet_diagnostic.CA1066.severity = none # CA1067: Override Object.Equals(object) when implementing IEquatable -dotnet_diagnostic.CA1067.severity = suggestion +# # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1067 +dotnet_diagnostic.CA1067.severity = warning # CA1068: CancellationToken parameters must come last +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1068 dotnet_diagnostic.CA1068.severity = warning # CA1069: Enums values should not be duplicated +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1069 dotnet_diagnostic.CA1069.severity = suggestion # CA1070: Do not declare event fields as virtual +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1070 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 # CA1303: Do not pass literals as localized parameters +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1303 dotnet_diagnostic.CA1303.severity = none # CA1304: Specify CultureInfo +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1304 dotnet_diagnostic.CA1304.severity = silent # CA1305: Specify IFormatProvider +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305 dotnet_diagnostic.CA1305.severity = silent # CA1307: Specify StringComparison for clarity +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1307 dotnet_diagnostic.CA1307.severity = none # CA1308: Normalize strings to uppercase +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1308 dotnet_diagnostic.CA1308.severity = none # CA1309: Use ordinal string comparison +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1309 dotnet_diagnostic.CA1309.severity = silent # CA1310: Specify StringComparison for correctness +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1310 dotnet_diagnostic.CA1310.severity = silent # CA1401: P/Invokes should not be visible -dotnet_diagnostic.CA1401.severity = suggestion +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1401 +dotnet_diagnostic.CA1401.severity = warning # CA1416: Validate platform compatibility +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416 dotnet_diagnostic.CA1416.severity = warning # CA1417: Do not use 'OutAttribute' on string parameters for P/Invokes +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1417 dotnet_diagnostic.CA1417.severity = warning +# CA1418: Use valid platform string +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1418 +dotnet_diagnostic.CA1418.severity = warning + # CA1501: Avoid excessive inheritance +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1501 dotnet_diagnostic.CA1501.severity = none # CA1502: Avoid excessive complexity +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1502 dotnet_diagnostic.CA1502.severity = none # CA1505: Avoid unmaintainable code +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1505 dotnet_diagnostic.CA1505.severity = none # CA1506: Avoid excessive class coupling +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1506 dotnet_diagnostic.CA1506.severity = none # CA1507: Use nameof to express symbol names +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1507 dotnet_diagnostic.CA1507.severity = suggestion # CA1508: Avoid dead conditional code +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1508 dotnet_diagnostic.CA1508.severity = none # CA1509: Invalid entry in code metrics rule specification file +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1509 dotnet_diagnostic.CA1509.severity = none # CA1700: Do not name enum values 'Reserved' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1700 dotnet_diagnostic.CA1700.severity = none # CA1707: Identifiers should not contain underscores +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1707 dotnet_diagnostic.CA1707.severity = silent # CA1708: Identifiers should differ by more than case +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1708 dotnet_diagnostic.CA1708.severity = silent +dotnet_code_quality.CA1708.api_surface = public # CA1710: Identifiers should have correct suffix +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1710 dotnet_diagnostic.CA1710.severity = silent +dotnet_code_quality.CA1710.api_surface = public # CA1711: Identifiers should not have incorrect suffix +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1711 dotnet_diagnostic.CA1711.severity = silent +dotnet_code_quality.CA1711.api_surface = public # CA1712: Do not prefix enum values with type name +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1712 dotnet_diagnostic.CA1712.severity = silent # CA1713: Events should not have 'Before' or 'After' prefix +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1713 dotnet_diagnostic.CA1713.severity = none # CA1715: Identifiers should have correct prefix +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1715 dotnet_diagnostic.CA1715.severity = silent +dotnet_code_quality.CA1715.api_surface = public # CA1716: Identifiers should not match keywords +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1716 dotnet_diagnostic.CA1716.severity = silent +dotnet_code_quality.CA1716.api_surface = public # CA1720: Identifier contains type name +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1720 dotnet_diagnostic.CA1720.severity = silent +dotnet_code_quality.CA1720.api_surface = public # CA1721: Property names should not match get methods +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1721 dotnet_diagnostic.CA1721.severity = none +dotnet_code_quality.CA1721.api_surface = public # CA1724: Type names should not match namespaces +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1724 dotnet_diagnostic.CA1724.severity = none # CA1725: Parameter names should match base declaration +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1725 dotnet_diagnostic.CA1725.severity = silent +dotnet_code_quality.CA1725.api_surface = public # CA1801: Review unused parameters +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1801 dotnet_diagnostic.CA1801.severity = none +dotnet_code_quality.CA1801.api_surface = all # CA1802: Use literals where appropriate +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1802 dotnet_diagnostic.CA1802.severity = none +dotnet_code_quality.CA1802.api_surface = public # CA1805: Do not initialize unnecessarily +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1805 dotnet_diagnostic.CA1805.severity = suggestion # CA1806: Do not ignore method results +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1806 dotnet_diagnostic.CA1806.severity = suggestion # CA1810: Initialize reference type static fields inline +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1810 dotnet_diagnostic.CA1810.severity = none # CA1812: Avoid uninstantiated internal classes -dotnet_diagnostic.CA1812.severity = none +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1812 +dotnet_diagnostic.CA1812.severity = warning # CA1813: Avoid unsealed attributes +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1813 dotnet_diagnostic.CA1813.severity = none # CA1814: Prefer jagged arrays over multidimensional +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1814 dotnet_diagnostic.CA1814.severity = none # CA1815: Override equals and operator equals on value types +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1815 dotnet_diagnostic.CA1815.severity = none +dotnet_code_quality.CA1815.api_surface = public # CA1816: Dispose methods should call SuppressFinalize +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1816 dotnet_diagnostic.CA1816.severity = warning # CA1819: Properties should not return arrays +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1819 dotnet_diagnostic.CA1819.severity = none +dotnet_code_quality.CA1819.api_surface = public # CA1820: Test for empty strings using string length +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1820 dotnet_diagnostic.CA1820.severity = none # CA1821: Remove empty Finalizers +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1821 dotnet_diagnostic.CA1821.severity = warning # CA1822: Mark members as static +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822 dotnet_diagnostic.CA1822.severity = warning -dotnet_code_quality.ca1822.api_surface = private +dotnet_code_quality.CA1822.api_surface = private # CA1823: Avoid unused private fields +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1823 dotnet_diagnostic.CA1823.severity = none # CA1824: Mark assemblies with NeutralResourcesLanguageAttribute +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1824 dotnet_diagnostic.CA1824.severity = warning # CA1825: Avoid zero-length array allocations +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1825 dotnet_diagnostic.CA1825.severity = warning # CA1826: Do not use Enumerable methods on indexable collections +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1826 dotnet_diagnostic.CA1826.severity = warning # CA1827: Do not use Count() or LongCount() when Any() can be used +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1827 dotnet_diagnostic.CA1827.severity = warning # CA1828: Do not use CountAsync() or LongCountAsync() when AnyAsync() can be used +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1828 dotnet_diagnostic.CA1828.severity = warning # CA1829: Use Length/Count property instead of Count() when available +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1829 dotnet_diagnostic.CA1829.severity = warning # CA1830: Prefer strongly-typed Append and Insert method overloads on StringBuilder +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1830 dotnet_diagnostic.CA1830.severity = warning # CA1831: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1831 dotnet_diagnostic.CA1831.severity = warning # CA1832: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1832 dotnet_diagnostic.CA1832.severity = warning # CA1833: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1833 dotnet_diagnostic.CA1833.severity = warning # CA1834: Consider using 'StringBuilder.Append(char)' when applicable +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1834 dotnet_diagnostic.CA1834.severity = warning # CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1835 dotnet_diagnostic.CA1835.severity = suggestion # CA1836: Prefer IsEmpty over Count +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1836 dotnet_diagnostic.CA1836.severity = warning # CA1837: Use 'Environment.ProcessId' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1837 dotnet_diagnostic.CA1837.severity = warning # CA1838: Avoid 'StringBuilder' parameters for P/Invokes +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1838 dotnet_diagnostic.CA1838.severity = silent +# CA1839: Use 'Environment.ProcessPath' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1839 +dotnet_diagnostic.CA1839.severity = warning + +# CA1840: Use 'Environment.CurrentManagedThreadId' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1840 +dotnet_diagnostic.CA1840.severity = warning + +# CA1841: Prefer Dictionary.Contains methods +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841 +dotnet_diagnostic.CA1841.severity = warning + +# CA1842: Do not use 'WhenAll' with a single task +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1842 +dotnet_diagnostic.CA1842.severity = warning + +# CA1843: Do not use 'WaitAll' with a single task +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1843 +dotnet_diagnostic.CA1843.severity = warning + +# CA1844: Provide memory-based overrides of async methods when subclassing 'Stream' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1844 +dotnet_diagnostic.CA1844.severity = warning + +# CA1845: Use span-based 'string.Concat' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1845 +dotnet_diagnostic.CA1845.severity = warning + +# CA1846: Prefer 'AsSpan' over 'Substring' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1846 +dotnet_diagnostic.CA1846.severity = warning + +# CA1847: Use char literal for a single character lookup +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1847 +dotnet_diagnostic.CA1847.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 + # CA2000: Dispose objects before losing scope +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 dotnet_diagnostic.CA2000.severity = none # CA2002: Do not lock on objects with weak identity +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2002 dotnet_diagnostic.CA2002.severity = none # CA2007: Consider calling ConfigureAwait on the awaited task +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007 dotnet_diagnostic.CA2007.severity = none # CA2008: Do not create tasks without passing a TaskScheduler +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2008 dotnet_diagnostic.CA2008.severity = none # CA2009: Do not call ToImmutableCollection on an ImmutableCollection value +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2009 dotnet_diagnostic.CA2009.severity = warning # CA2011: Avoid infinite recursion +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2011 dotnet_diagnostic.CA2011.severity = warning # CA2012: Use ValueTasks correctly +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2012 dotnet_diagnostic.CA2012.severity = warning # CA2013: Do not use ReferenceEquals with value types +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2013 dotnet_diagnostic.CA2013.severity = warning # CA2014: Do not use stackalloc in loops +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2014 dotnet_diagnostic.CA2014.severity = warning # CA2015: Do not define finalizers for types derived from MemoryManager +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2015 dotnet_diagnostic.CA2015.severity = warning # CA2016: Forward the 'CancellationToken' parameter to methods that take one +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2016 dotnet_diagnostic.CA2016.severity = suggestion # CA2100: Review SQL queries for security vulnerabilities +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2100 dotnet_diagnostic.CA2100.severity = none # CA2101: Specify marshaling for P/Invoke string arguments +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2101 dotnet_diagnostic.CA2101.severity = suggestion # CA2109: Review visible event handlers +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2109 dotnet_diagnostic.CA2109.severity = none # CA2119: Seal methods that satisfy private interfaces +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2119 dotnet_diagnostic.CA2119.severity = none # CA2153: Do Not Catch Corrupted State Exceptions +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2153 dotnet_diagnostic.CA2153.severity = none # CA2200: Rethrow to preserve stack details +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2200 dotnet_diagnostic.CA2200.severity = warning # CA2201: Do not raise reserved exception types +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2201 dotnet_diagnostic.CA2201.severity = silent # CA2207: Initialize value type static fields inline +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2207 dotnet_diagnostic.CA2207.severity = warning # CA2208: Instantiate argument exceptions correctly +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2208 dotnet_diagnostic.CA2208.severity = suggestion +dotnet_code_quality.CA2208.api_surface = all # CA2211: Non-constant fields should not be visible +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2211 dotnet_diagnostic.CA2211.severity = warning # CA2213: Disposable fields should be disposed +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2213 dotnet_diagnostic.CA2213.severity = none # CA2214: Do not call overridable methods in constructors +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2214 dotnet_diagnostic.CA2214.severity = none # CA2215: Dispose methods should call base class dispose +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2215 dotnet_diagnostic.CA2215.severity = silent # CA2216: Disposable types should declare finalizer +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2216 dotnet_diagnostic.CA2216.severity = warning # CA2217: Do not mark enums with FlagsAttribute +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2217 dotnet_diagnostic.CA2217.severity = none +dotnet_code_quality.CA2217.api_surface = public # CA2218: Override GetHashCode on overriding Equals +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2218 dotnet_diagnostic.CA2218.severity = suggestion # CA2219: Do not raise exceptions in finally clauses +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2219 dotnet_diagnostic.CA2219.severity = suggestion # CA2224: Override Equals on overloading operator equals +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2224 dotnet_diagnostic.CA2224.severity = suggestion # CA2225: Operator overloads have named alternates +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2225 dotnet_diagnostic.CA2225.severity = none +dotnet_code_quality.CA2225.api_surface = public # CA2226: Operators should have symmetrical overloads +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2226 dotnet_diagnostic.CA2226.severity = none +dotnet_code_quality.CA2226.api_surface = public # CA2227: Collection properties should be read only +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2227 dotnet_diagnostic.CA2227.severity = none # CA2229: Implement serialization constructors +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2229 dotnet_diagnostic.CA2229.severity = silent # CA2231: Overload operator equals on overriding value type Equals +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2231 dotnet_diagnostic.CA2231.severity = suggestion +dotnet_code_quality.CA2231.api_surface = public # CA2234: Pass system uri objects instead of strings +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2234 dotnet_diagnostic.CA2234.severity = none +dotnet_code_quality.CA2234.api_surface = public # CA2235: Mark all non-serializable fields +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2235 dotnet_diagnostic.CA2235.severity = none # CA2237: Mark ISerializable types with serializable +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2237 dotnet_diagnostic.CA2237.severity = none # CA2241: Provide correct arguments to formatting methods +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2241 dotnet_diagnostic.CA2241.severity = suggestion # CA2242: Test for NaN correctly +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2242 dotnet_diagnostic.CA2242.severity = suggestion # CA2243: Attribute string literals should parse correctly -dotnet_diagnostic.CA2243.severity = none +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2243 +dotnet_diagnostic.CA2243.severity = warning # CA2244: Do not duplicate indexed element initializations +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2244 dotnet_diagnostic.CA2244.severity = suggestion # CA2245: Do not assign a property to itself +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2245 dotnet_diagnostic.CA2245.severity = suggestion # CA2246: Assigning symbol and its member in the same statement +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2246 dotnet_diagnostic.CA2246.severity = suggestion # CA2247: Argument passed to TaskCompletionSource constructor should be TaskCreationOptions enum instead of TaskContinuationOptions enum +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2247 dotnet_diagnostic.CA2247.severity = warning # CA2248: Provide correct 'enum' argument to 'Enum.HasFlag' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2248 dotnet_diagnostic.CA2248.severity = suggestion # CA2249: Consider using 'string.Contains' instead of 'string.IndexOf' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2249 dotnet_diagnostic.CA2249.severity = warning +# CA2250: Use 'ThrowIfCancellationRequested' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2250 +dotnet_diagnostic.CA2250.severity = warning + +# CA2251: Use 'string.Equals' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2251 +dotnet_diagnostic.CA2251.severity = warning + +# CA2252: This API requires opting into preview features +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2252 +dotnet_diagnostic.CA2251.severity = none + # CA2300: Do not use insecure deserializer BinaryFormatter +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2300 dotnet_diagnostic.CA2300.severity = none # CA2301: Do not call BinaryFormatter.Deserialize without first setting BinaryFormatter.Binder +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2301 dotnet_diagnostic.CA2301.severity = none # CA2302: Ensure BinaryFormatter.Binder is set before calling BinaryFormatter.Deserialize +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2302 dotnet_diagnostic.CA2302.severity = none # CA2305: Do not use insecure deserializer LosFormatter +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2305 dotnet_diagnostic.CA2305.severity = none # CA2310: Do not use insecure deserializer NetDataContractSerializer +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2310 dotnet_diagnostic.CA2310.severity = none # CA2311: Do not deserialize without first setting NetDataContractSerializer.Binder +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2311 dotnet_diagnostic.CA2311.severity = none # CA2312: Ensure NetDataContractSerializer.Binder is set before deserializing +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2312 dotnet_diagnostic.CA2312.severity = none # CA2315: Do not use insecure deserializer ObjectStateFormatter +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2315 dotnet_diagnostic.CA2315.severity = none # CA2321: Do not deserialize with JavaScriptSerializer using a SimpleTypeResolver +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2321 dotnet_diagnostic.CA2321.severity = none # CA2322: Ensure JavaScriptSerializer is not initialized with SimpleTypeResolver before deserializing +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2322 dotnet_diagnostic.CA2322.severity = none # CA2326: Do not use TypeNameHandling values other than None +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2326 dotnet_diagnostic.CA2326.severity = none # CA2327: Do not use insecure JsonSerializerSettings +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2327 dotnet_diagnostic.CA2327.severity = none # CA2328: Ensure that JsonSerializerSettings are secure +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2328 dotnet_diagnostic.CA2328.severity = none # CA2329: Do not deserialize with JsonSerializer using an insecure configuration +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2329 dotnet_diagnostic.CA2329.severity = none # CA2330: Ensure that JsonSerializer has a secure configuration when deserializing +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2330 dotnet_diagnostic.CA2330.severity = none # CA2350: Do not use DataTable.ReadXml() with untrusted data +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2350 dotnet_diagnostic.CA2350.severity = none # CA2351: Do not use DataSet.ReadXml() with untrusted data +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2351 dotnet_diagnostic.CA2351.severity = none # CA2352: Unsafe DataSet or DataTable in serializable type can be vulnerable to remote code execution attacks +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2352 dotnet_diagnostic.CA2352.severity = none # CA2353: Unsafe DataSet or DataTable in serializable type +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2353 dotnet_diagnostic.CA2353.severity = none # CA2354: Unsafe DataSet or DataTable in deserialized object graph can be vulnerable to remote code execution attacks +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2354 dotnet_diagnostic.CA2354.severity = none # CA2355: Unsafe DataSet or DataTable type found in deserializable object graph +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2355 dotnet_diagnostic.CA2355.severity = none # CA2356: Unsafe DataSet or DataTable type in web deserializable object graph +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2356 dotnet_diagnostic.CA2356.severity = none # CA2361: Ensure autogenerated class containing DataSet.ReadXml() is not used with untrusted data +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2361 dotnet_diagnostic.CA2361.severity = none # CA2362: Unsafe DataSet or DataTable in autogenerated serializable type can be vulnerable to remote code execution attacks +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2362 dotnet_diagnostic.CA2362.severity = none # CA3001: Review code for SQL injection vulnerabilities +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3001 dotnet_diagnostic.CA3001.severity = none # CA3002: Review code for XSS vulnerabilities +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3002 dotnet_diagnostic.CA3002.severity = none # CA3003: Review code for file path injection vulnerabilities +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3003 dotnet_diagnostic.CA3003.severity = none # CA3004: Review code for information disclosure vulnerabilities +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3004 dotnet_diagnostic.CA3004.severity = none # CA3005: Review code for LDAP injection vulnerabilities +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3005 dotnet_diagnostic.CA3005.severity = none # CA3006: Review code for process command injection vulnerabilities +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3006 dotnet_diagnostic.CA3006.severity = none # CA3007: Review code for open redirect vulnerabilities +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3007 dotnet_diagnostic.CA3007.severity = none # CA3008: Review code for XPath injection vulnerabilities +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3008 dotnet_diagnostic.CA3008.severity = none # CA3009: Review code for XML injection vulnerabilities +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3009 dotnet_diagnostic.CA3009.severity = none # CA3010: Review code for XAML injection vulnerabilities +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3010 dotnet_diagnostic.CA3010.severity = none # CA3011: Review code for DLL injection vulnerabilities +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3011 dotnet_diagnostic.CA3011.severity = none # CA3012: Review code for regex injection vulnerabilities +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3012 dotnet_diagnostic.CA3012.severity = none # CA3061: Do Not Add Schema By URL +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3061 dotnet_diagnostic.CA3061.severity = silent # CA3075: Insecure DTD processing in XML +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3075 dotnet_diagnostic.CA3075.severity = silent # CA3076: Insecure XSLT script processing. +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3076 dotnet_diagnostic.CA3076.severity = silent # CA3077: Insecure Processing in API Design, XmlDocument and XmlTextReader +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3077 dotnet_diagnostic.CA3077.severity = silent # CA3147: Mark Verb Handlers With Validate Antiforgery Token +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca3147 dotnet_diagnostic.CA3147.severity = silent # CA5350: Do Not Use Weak Cryptographic Algorithms +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5350 dotnet_diagnostic.CA5350.severity = silent # CA5351: Do Not Use Broken Cryptographic Algorithms +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5351 dotnet_diagnostic.CA5351.severity = silent # CA5358: Review cipher mode usage with cryptography experts +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5358 dotnet_diagnostic.CA5358.severity = none # CA5359: Do Not Disable Certificate Validation +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5359 dotnet_diagnostic.CA5359.severity = silent # CA5360: Do Not Call Dangerous Methods In Deserialization +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5360 dotnet_diagnostic.CA5360.severity = silent # CA5361: Do Not Disable SChannel Use of Strong Crypto +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5361 dotnet_diagnostic.CA5361.severity = none # CA5362: Potential reference cycle in deserialized object graph +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5362 dotnet_diagnostic.CA5362.severity = none # CA5363: Do Not Disable Request Validation +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5363 dotnet_diagnostic.CA5363.severity = silent # CA5364: Do Not Use Deprecated Security Protocols +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5364 dotnet_diagnostic.CA5364.severity = silent # CA5365: Do Not Disable HTTP Header Checking +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5365 dotnet_diagnostic.CA5365.severity = silent # CA5366: Use XmlReader For DataSet Read Xml +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5366 dotnet_diagnostic.CA5366.severity = silent # CA5367: Do Not Serialize Types With Pointer Fields +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5367 dotnet_diagnostic.CA5367.severity = none # CA5368: Set ViewStateUserKey For Classes Derived From Page +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5368 dotnet_diagnostic.CA5368.severity = silent # CA5369: Use XmlReader For Deserialize +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5369 dotnet_diagnostic.CA5369.severity = silent # CA5370: Use XmlReader For Validating Reader +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5370 dotnet_diagnostic.CA5370.severity = silent # CA5371: Use XmlReader For Schema Read +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5371 dotnet_diagnostic.CA5371.severity = silent # CA5372: Use XmlReader For XPathDocument +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5372 dotnet_diagnostic.CA5372.severity = silent # CA5373: Do not use obsolete key derivation function +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5373 dotnet_diagnostic.CA5373.severity = silent # CA5374: Do Not Use XslTransform +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5374 dotnet_diagnostic.CA5374.severity = silent # CA5375: Do Not Use Account Shared Access Signature +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5375 dotnet_diagnostic.CA5375.severity = none # CA5376: Use SharedAccessProtocol HttpsOnly +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5376 dotnet_diagnostic.CA5376.severity = none # CA5377: Use Container Level Access Policy +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5377 dotnet_diagnostic.CA5377.severity = none # CA5378: Do not disable ServicePointManagerSecurityProtocols +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5378 dotnet_diagnostic.CA5378.severity = none # CA5379: Do Not Use Weak Key Derivation Function Algorithm +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5379 dotnet_diagnostic.CA5379.severity = silent # CA5380: Do Not Add Certificates To Root Store +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5380 dotnet_diagnostic.CA5380.severity = none # CA5381: Ensure Certificates Are Not Added To Root Store +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5381 dotnet_diagnostic.CA5381.severity = none # CA5382: Use Secure Cookies In ASP.Net Core +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5382 dotnet_diagnostic.CA5382.severity = none # CA5383: Ensure Use Secure Cookies In ASP.Net Core +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5383 dotnet_diagnostic.CA5383.severity = none # CA5384: Do Not Use Digital Signature Algorithm (DSA) +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5384 dotnet_diagnostic.CA5384.severity = silent # CA5385: Use Rivest–Shamir–Adleman (RSA) Algorithm With Sufficient Key Size +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5385 dotnet_diagnostic.CA5385.severity = silent # CA5386: Avoid hardcoding SecurityProtocolType value +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5386 dotnet_diagnostic.CA5386.severity = none # CA5387: Do Not Use Weak Key Derivation Function With Insufficient Iteration Count +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5387 dotnet_diagnostic.CA5387.severity = none # CA5388: Ensure Sufficient Iteration Count When Using Weak Key Derivation Function +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5388 dotnet_diagnostic.CA5388.severity = none # CA5389: Do Not Add Archive Item's Path To The Target File System Path +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5389 dotnet_diagnostic.CA5389.severity = none # CA5390: Do not hard-code encryption key +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5390 dotnet_diagnostic.CA5390.severity = none # CA5391: Use antiforgery tokens in ASP.NET Core MVC controllers +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5391 dotnet_diagnostic.CA5391.severity = none # CA5392: Use DefaultDllImportSearchPaths attribute for P/Invokes +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5392 dotnet_diagnostic.CA5392.severity = none # CA5393: Do not use unsafe DllImportSearchPath value +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5393 dotnet_diagnostic.CA5393.severity = none # CA5394: Do not use insecure randomness +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5394 dotnet_diagnostic.CA5394.severity = none # CA5395: Miss HttpVerb attribute for action methods +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5395 dotnet_diagnostic.CA5395.severity = none # CA5396: Set HttpOnly to true for HttpCookie +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5396 dotnet_diagnostic.CA5396.severity = none # CA5397: Do not use deprecated SslProtocols values +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5397 dotnet_diagnostic.CA5397.severity = silent # CA5398: Avoid hardcoded SslProtocols values +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5398 dotnet_diagnostic.CA5398.severity = none # CA5399: HttpClients should enable certificate revocation list checks +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5399 dotnet_diagnostic.CA5399.severity = none # CA5400: Ensure HttpClient certificate revocation list check is not disabled +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5400 dotnet_diagnostic.CA5400.severity = none # CA5401: Do not use CreateEncryptor with non-default IV +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5401 dotnet_diagnostic.CA5401.severity = none -# CA5402: Use CreateEncryptor with the default IV +# CA5402: Use CreateEncryptor with the default IV +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5402 dotnet_diagnostic.CA5402.severity = none # CA5403: Do not hard-code certificate +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5403 dotnet_diagnostic.CA5403.severity = none # IL3000: Avoid using accessing Assembly file path when publishing as a single-file +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/il3000 dotnet_diagnostic.IL3000.severity = warning # IL3001: Avoid using accessing Assembly file path when publishing as a single-file +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/il3001 dotnet_diagnostic.IL3001.severity = warning +# IL3002: Using member with RequiresAssemblyFilesAttribute can break functionality when embedded in a single-file app +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/il3002 +dotnet_diagnostic.IL3002.severity = warning + +# DOC100: PlaceTextInParagraphs +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC100.md +dotnet_diagnostic.DOC100.severity = none + +# DOC101: UseChildBlocksConsistently +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC101.md +dotnet_diagnostic.DOC101.severity = none + +# DOC102: UseChildBlocksConsistentlyAcrossElementsOfTheSameKind +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC102.md +dotnet_diagnostic.DOC102.severity = none + +# DOC103: UseUnicodeCharacters +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC103.md +dotnet_diagnostic.DOC103.severity = none + +# DOC104: UseSeeLangword +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC104.md +dotnet_diagnostic.DOC104.severity = suggestion + +# DOC105: UseParamref +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC105.md +dotnet_diagnostic.DOC105.severity = none + +# DOC106: UseTypeparamref +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC106.md +dotnet_diagnostic.DOC106.severity = none + +# DOC107: UseSeeCref +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC107.md +dotnet_diagnostic.DOC107.severity = none + +# DOC108: AvoidEmptyParagraphs +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC108.md +dotnet_diagnostic.DOC108.severity = none + +# DOC200: UseXmlDocumentationSyntax +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC200.md +dotnet_diagnostic.DOC200.severity = none + +# DOC201: ItemShouldHaveDescription +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC201.md +dotnet_diagnostic.DOC201.severity = none + +# DOC202: UseSectionElementsCorrectly +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC202.md +dotnet_diagnostic.DOC202.severity = none + +# DOC203: UseBlockElementsCorrectly +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC203.md +dotnet_diagnostic.DOC203.severity = none + +# DOC204: UseInlineElementsCorrectly +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC204.md +dotnet_diagnostic.DOC204.severity = none + +# DOC207: UseSeeLangwordCorrectly +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC207.md +dotnet_diagnostic.DOC207.severity = none + +# DOC209: UseSeeHrefCorrectly +# https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC209.md +dotnet_diagnostic.DOC209.severity = none + # IDE0001: SimplifyNames +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0001 dotnet_diagnostic.IDE0001.severity = silent # IDE0002: SimplifyMemberAccess +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0002 dotnet_diagnostic.IDE0002.severity = silent # IDE0003: RemoveQualification +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0003 dotnet_diagnostic.IDE0003.severity = silent # IDE0004: RemoveUnnecessaryCast +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0004 dotnet_diagnostic.IDE0004.severity = silent # IDE0005: RemoveUnnecessaryImports +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0005 dotnet_diagnostic.IDE0005.severity = silent # IDE0006: IntellisenseBuildFailed +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0006 dotnet_diagnostic.IDE0006.severity = silent # IDE0007: UseImplicitType +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0007 dotnet_diagnostic.IDE0007.severity = silent # IDE0008: UseExplicitType +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0008 dotnet_diagnostic.IDE0008.severity = silent # IDE0009: AddQualification +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0009 dotnet_diagnostic.IDE0009.severity = silent # IDE0010: PopulateSwitchStatement +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0010 dotnet_diagnostic.IDE0010.severity = silent # IDE0011: AddBraces +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0011 dotnet_diagnostic.IDE0011.severity = silent # IDE0016: UseThrowExpression +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0016 dotnet_diagnostic.IDE0016.severity = silent # IDE0017: UseObjectInitializer +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0017 dotnet_diagnostic.IDE0017.severity = silent # IDE0018: InlineDeclaration +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0018 dotnet_diagnostic.IDE0018.severity = silent # IDE0019: InlineAsTypeCheck +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0019 dotnet_diagnostic.IDE0019.severity = silent # IDE0020: InlineIsTypeCheck +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0020 dotnet_diagnostic.IDE0020.severity = silent # IDE0021: UseExpressionBodyForConstructors +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0021 dotnet_diagnostic.IDE0021.severity = silent # IDE0022: UseExpressionBodyForMethods +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0022 dotnet_diagnostic.IDE0022.severity = silent # IDE0023: UseExpressionBodyForConversionOperators +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0023 dotnet_diagnostic.IDE0023.severity = silent # IDE0024: UseExpressionBodyForOperators +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0024 dotnet_diagnostic.IDE0024.severity = silent # IDE0025: UseExpressionBodyForProperties +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0025 dotnet_diagnostic.IDE0025.severity = silent # IDE0026: UseExpressionBodyForIndexers +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0026 dotnet_diagnostic.IDE0026.severity = silent # IDE0027: UseExpressionBodyForAccessors +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0027 dotnet_diagnostic.IDE0027.severity = silent # IDE0028: UseCollectionInitializer +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0028 dotnet_diagnostic.IDE0028.severity = silent # IDE0029: UseCoalesceExpression -dotnet_diagnostic.IDE0029.severity = silent +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0029 +dotnet_diagnostic.IDE0029.severity = warning # IDE0030: UseCoalesceExpressionForNullable -dotnet_diagnostic.IDE0030.severity = silent +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0030 +dotnet_diagnostic.IDE0030.severity = warning # IDE0031: UseNullPropagation +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0031 dotnet_diagnostic.IDE0031.severity = warning # IDE0032: UseAutoProperty +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0032 dotnet_diagnostic.IDE0032.severity = silent # IDE0033: UseExplicitTupleName +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0033 dotnet_diagnostic.IDE0033.severity = silent # IDE0034: UseDefaultLiteral +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0034 dotnet_diagnostic.IDE0034.severity = silent # IDE0035: RemoveUnreachableCode +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0035 dotnet_diagnostic.IDE0035.severity = silent # IDE0036: OrderModifiers +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0036 dotnet_diagnostic.IDE0036.severity = warning # IDE0037: UseInferredMemberName +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0037 dotnet_diagnostic.IDE0037.severity = silent # IDE0038: InlineIsTypeWithoutNameCheck +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0038 dotnet_diagnostic.IDE0038.severity = silent # IDE0039: UseLocalFunction +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0039 dotnet_diagnostic.IDE0039.severity = silent # IDE0040: AddAccessibilityModifiers +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0040 dotnet_diagnostic.IDE0040.severity = warning # IDE0041: UseIsNullCheck +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0041 dotnet_diagnostic.IDE0041.severity = warning # IDE0042: UseDeconstruction +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0042 dotnet_diagnostic.IDE0042.severity = silent # IDE0043: ValidateFormatString +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0043 dotnet_diagnostic.IDE0043.severity = silent # IDE0044: MakeFieldReadonly -dotnet_diagnostic.IDE0044.severity = silent +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0044 +dotnet_diagnostic.IDE0044.severity = warning # IDE0045: UseConditionalExpressionForAssignment +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0045 dotnet_diagnostic.IDE0045.severity = silent # IDE0046: UseConditionalExpressionForReturn +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0046 dotnet_diagnostic.IDE0046.severity = silent # IDE0047: RemoveUnnecessaryParentheses +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0047 dotnet_diagnostic.IDE0047.severity = silent # IDE0048: AddRequiredParentheses +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0048 dotnet_diagnostic.IDE0048.severity = suggestion # IDE0049: PreferBuiltInOrFrameworkType -dotnet_diagnostic.IDE0049.severity = silent +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0049 +dotnet_diagnostic.IDE0049.severity = warning # IDE0050: ConvertAnonymousTypeToTuple +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0050 dotnet_diagnostic.IDE0050.severity = silent # IDE0051: RemoveUnusedMembers +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0051 dotnet_diagnostic.IDE0051.severity = silent # IDE0052: RemoveUnreadMembers +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0052 dotnet_diagnostic.IDE0052.severity = silent # IDE0053: UseExpressionBodyForLambdaExpressions +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0053 dotnet_diagnostic.IDE0053.severity = silent # IDE0054: UseCompoundAssignment +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0054 dotnet_diagnostic.IDE0054.severity = warning # IDE0055: Formatting +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0055 dotnet_diagnostic.IDE0055.severity = silent # IDE0056: UseIndexOperator +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0056 dotnet_diagnostic.IDE0056.severity = silent # IDE0057: UseRangeOperator +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0057 dotnet_diagnostic.IDE0057.severity = silent # IDE0058: ExpressionValueIsUnused +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0058 dotnet_diagnostic.IDE0058.severity = silent # IDE0059: ValueAssignedIsUnused +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0059 dotnet_diagnostic.IDE0059.severity = silent # IDE0060: UnusedParameter +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0060 dotnet_diagnostic.IDE0060.severity = silent # IDE0061: UseExpressionBodyForLocalFunctions +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0061 dotnet_diagnostic.IDE0061.severity = silent # IDE0062: MakeLocalFunctionStatic +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0062 dotnet_diagnostic.IDE0062.severity = warning # IDE0063: UseSimpleUsingStatement +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0063 dotnet_diagnostic.IDE0063.severity = silent # IDE0064: MakeStructFieldsWritable +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0064 dotnet_diagnostic.IDE0064.severity = warning # IDE0065: MoveMisplacedUsingDirectives +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0065 dotnet_diagnostic.IDE0065.severity = silent # IDE0066: ConvertSwitchStatementToExpression +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0066 dotnet_diagnostic.IDE0066.severity = silent -# IDE0067: DisposeObjectsBeforeLosingScope -dotnet_diagnostic.IDE0067.severity = silent - -# IDE0068: UseRecommendedDisposePattern -dotnet_diagnostic.IDE0068.severity = silent - -# IDE0069: DisposableFieldsShouldBeDisposed -dotnet_diagnostic.IDE0069.severity = silent - # IDE0070: UseSystemHashCode -dotnet_diagnostic.IDE0070.severity = silent +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0070 +dotnet_diagnostic.IDE0070.severity = warning # IDE0071: SimplifyInterpolation +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0071 dotnet_diagnostic.IDE0071.severity = silent # IDE0072: PopulateSwitchExpression +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0072 dotnet_diagnostic.IDE0072.severity = silent # IDE0073: FileHeaderMismatch +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0073 dotnet_diagnostic.IDE0073.severity = suggestion # IDE0074: UseCoalesceCompoundAssignment +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0074 dotnet_diagnostic.IDE0074.severity = warning # IDE0075: SimplifyConditionalExpression +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0075 dotnet_diagnostic.IDE0075.severity = warning # IDE0076: InvalidSuppressMessageAttribute +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0076 dotnet_diagnostic.IDE0076.severity = warning # IDE0077: LegacyFormatSuppressMessageAttribute +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0077 dotnet_diagnostic.IDE0077.severity = warning # IDE0078: UsePatternCombinators +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0078 dotnet_diagnostic.IDE0078.severity = silent # IDE0079: RemoveUnnecessarySuppression +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0079 dotnet_diagnostic.IDE0079.severity = silent # IDE0080: RemoveConfusingSuppressionForIsExpression +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0080 dotnet_diagnostic.IDE0080.severity = silent # IDE0081: RemoveUnnecessaryByVal +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0081 dotnet_diagnostic.IDE0081.severity = silent # IDE0082: ConvertTypeOfToNameOf +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0082 dotnet_diagnostic.IDE0082.severity = warning # IDE0083: UseNotPattern +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0083 dotnet_diagnostic.IDE0083.severity = silent # IDE0084: UseIsNotExpression +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0084 dotnet_diagnostic.IDE0084.severity = silent +# IDE0090: UseNew +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0090 +dotnet_diagnostic.IDE0090.severity = suggestion + +# IDE0100: RemoveRedundantEquality +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0100 +dotnet_diagnostic.IDE0100.severity = warning + +# IDE0110: RemoveUnnecessaryDiscard +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0110 +dotnet_diagnostic.IDE0110.severity = suggestion + +# IDE0120: SimplifyLINQExpression +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0120 +dotnet_diagnostic.IDE0120.severity = warning + +# IDE0130: NamespaceDoesNotMatchFolderStructure +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0130 +dotnet_diagnostic.IDE0130.severity = silent + # IDE1001: AnalyzerChanged +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1001 dotnet_diagnostic.IDE1001.severity = silent # IDE1002: AnalyzerDependencyConflict +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1002 dotnet_diagnostic.IDE1002.severity = silent # IDE1003: MissingAnalyzerReference +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1003 dotnet_diagnostic.IDE1003.severity = silent # IDE1004: ErrorReadingRuleset +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1004 dotnet_diagnostic.IDE1004.severity = silent # IDE1005: InvokeDelegateWithConditionalAccess +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1005 dotnet_diagnostic.IDE1005.severity = warning # IDE1006: NamingRule +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1006 dotnet_diagnostic.IDE1006.severity = silent # IDE1007: UnboundIdentifier +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1007 dotnet_diagnostic.IDE1007.severity = silent # IDE1008: UnboundConstructor +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1008 dotnet_diagnostic.IDE1008.severity = silent +# IDE2000: MultipleBlankLines +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000 +dotnet_diagnostic.IDE2000.severity = warning + +# IDE2001: EmbeddedStatementsMustBeOnTheirOwnLine +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2001 +dotnet_diagnostic.IDE2001.severity = warning + +# IDE2002: ConsecutiveBracesMustNotHaveBlankLinesBetweenThem +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2002 +dotnet_diagnostic.IDE2002.severity = warning + +# IDE2003: ConsecutiveStatementPlacement +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2003 +dotnet_diagnostic.IDE2003.severity = warning + +# IDE2004: BlankLineNotAllowedAfterConstructorInitializerColon +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2004 +dotnet_diagnostic.IDE2004.severity = warning + # SA0001: XML comment analysis disabled +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA0001.md dotnet_diagnostic.SA0001.severity = none # SA0002: Invalid settings file +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA0002.md dotnet_diagnostic.SA0002.severity = none # SA1000: Keywords should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1000.md dotnet_diagnostic.SA1000.severity = warning # SA1001: Commas should be spaced correctly -dotnet_diagnostic.SA1001.severity = none +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1001.md +dotnet_diagnostic.SA1001.severity = warning # SA1002: Semicolons should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1002.md dotnet_diagnostic.SA1002.severity = warning # SA1003: Symbols should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1003.md dotnet_diagnostic.SA1003.severity = warning # SA1004: Documentation lines should begin with single space +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1004.md dotnet_diagnostic.SA1004.severity = none # SA1005: Single line comments should begin with single space +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1005.md dotnet_diagnostic.SA1005.severity = none # SA1006: Preprocessor keywords should not be preceded by space +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1006.md dotnet_diagnostic.SA1006.severity = warning # SA1007: Operator keyword should be followed by space +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1007.md dotnet_diagnostic.SA1007.severity = warning # SA1008: Opening parenthesis should be spaced correctly -dotnet_diagnostic.SA1008.severity = none +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1008.md +dotnet_diagnostic.SA1008.severity = warning # SA1009: Closing parenthesis should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1009.md dotnet_diagnostic.SA1009.severity = none # SA1010: Opening square brackets should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1010.md dotnet_diagnostic.SA1010.severity = none # SA1011: Closing square brackets should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1011.md dotnet_diagnostic.SA1011.severity = none # SA1012: Opening braces should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1012.md dotnet_diagnostic.SA1012.severity = none # SA1013: Closing braces should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1013.md dotnet_diagnostic.SA1013.severity = none # SA1014: Opening generic brackets should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1014.md dotnet_diagnostic.SA1014.severity = none # SA1015: Closing generic brackets should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1015.md dotnet_diagnostic.SA1015.severity = none # SA1016: Opening attribute brackets should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1016.md dotnet_diagnostic.SA1016.severity = none # SA1017: Closing attribute brackets should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1017.md dotnet_diagnostic.SA1017.severity = none # SA1018: Nullable type symbols should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1018.md dotnet_diagnostic.SA1018.severity = none # SA1019: Member access symbols should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1019.md dotnet_diagnostic.SA1019.severity = none # SA1020: Increment decrement symbols should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1020.md dotnet_diagnostic.SA1020.severity = none # SA1021: Negative signs should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1021.md dotnet_diagnostic.SA1021.severity = none # SA1022: Positive signs should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1022.md dotnet_diagnostic.SA1022.severity = none # SA1023: Dereference and access of symbols should be spaced correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1023.md dotnet_diagnostic.SA1023.severity = none # SA1024: Colons Should Be Spaced Correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1024.md dotnet_diagnostic.SA1024.severity = none # SA1025: Code should not contain multiple whitespace in a row +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1025.md dotnet_diagnostic.SA1025.severity = none # SA1026: Code should not contain space after new or stackalloc keyword in implicitly typed array allocation +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1026.md dotnet_diagnostic.SA1026.severity = none # SA1027: Use tabs correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1027.md dotnet_diagnostic.SA1027.severity = none # SA1028: Code should not contain trailing whitespace +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md dotnet_diagnostic.SA1028.severity = none # SA1100: Do not prefix calls with base unless local implementation exists +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1100.md dotnet_diagnostic.SA1100.severity = none # SA1101: Prefix local calls with this +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1101.md dotnet_diagnostic.SA1101.severity = none # SA1102: Query clause should follow previous clause +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1102.md dotnet_diagnostic.SA1102.severity = none # SA1103: Query clauses should be on separate lines or all on one line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1103.md dotnet_diagnostic.SA1103.severity = none # SA1104: Query clause should begin on new line when previous clause spans multiple lines +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1104.md dotnet_diagnostic.SA1104.severity = none # SA1105: Query clauses spanning multiple lines should begin on own line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1105.md dotnet_diagnostic.SA1105.severity = none # SA1106: Code should not contain empty statements +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1106.md dotnet_diagnostic.SA1106.severity = warning # SA1107: Code should not contain multiple statements on one line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1107.md dotnet_diagnostic.SA1107.severity = none # SA1108: Block statements should not contain embedded comments +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1108.md dotnet_diagnostic.SA1108.severity = none # SA1110: Opening parenthesis or bracket should be on declaration line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1110.md dotnet_diagnostic.SA1110.severity = none # SA1111: Closing parenthesis should be on line of last parameter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1111.md dotnet_diagnostic.SA1111.severity = none # SA1112: Closing parenthesis should be on line of opening parenthesis +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1112.md dotnet_diagnostic.SA1112.severity = none # SA1113: Comma should be on the same line as previous parameter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1113.md dotnet_diagnostic.SA1113.severity = none # SA1114: Parameter list should follow declaration +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1114.md dotnet_diagnostic.SA1114.severity = none # SA1115: Parameter should follow comma +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1115.md dotnet_diagnostic.SA1115.severity = none # SA1116: Split parameters should start on line after declaration +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1116.md dotnet_diagnostic.SA1116.severity = none # SA1117: Parameters should be on same line or separate lines +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1117.md dotnet_diagnostic.SA1117.severity = none # SA1118: Parameter should not span multiple lines +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1118.md dotnet_diagnostic.SA1118.severity = none # SA1119: Statement should not use unnecessary parenthesis +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1119.md dotnet_diagnostic.SA1119.severity = none # SA1120: Comments should contain text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1120.md dotnet_diagnostic.SA1120.severity = none # SA1121: Use built-in type alias +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1121.md dotnet_diagnostic.SA1121.severity = none # SA1122: Use string.Empty for empty strings +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1122.md dotnet_diagnostic.SA1122.severity = warning # SA1123: Do not place regions within elements +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1123.md dotnet_diagnostic.SA1123.severity = none # SA1124: Do not use regions +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1124.md dotnet_diagnostic.SA1124.severity = none # SA1125: Use shorthand for nullable types +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1125.md dotnet_diagnostic.SA1125.severity = none # SA1127: Generic type constraints should be on their own line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1127.md dotnet_diagnostic.SA1127.severity = none # SA1128: Put constructor initializers on their own line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1128.md dotnet_diagnostic.SA1128.severity = none # SA1129: Do not use default value type constructor +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1129.md dotnet_diagnostic.SA1129.severity = none # SA1130: Use lambda syntax +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1130.md dotnet_diagnostic.SA1130.severity = none # SA1131: Use readable conditions +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1131.md dotnet_diagnostic.SA1131.severity = warning # SA1132: Do not combine fields +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1132.md dotnet_diagnostic.SA1132.severity = none # SA1133: Do not combine attributes +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1133.md dotnet_diagnostic.SA1133.severity = none # SA1134: Attributes should not share line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1134.md dotnet_diagnostic.SA1134.severity = none # SA1135: Using directives should be qualified +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1135.md dotnet_diagnostic.SA1135.severity = none # SA1136: Enum values should be on separate lines +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1136.md dotnet_diagnostic.SA1136.severity = none # SA1137: Elements should have the same indentation +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1137.md dotnet_diagnostic.SA1137.severity = none # SA1139: Use literal suffix notation instead of casting +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1139.md dotnet_diagnostic.SA1139.severity = none # SA1141: Use tuple syntax +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1141.md dotnet_diagnostic.SA1141.severity = none # SA1142: Refer to tuple fields by name +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1142.md dotnet_diagnostic.SA1142.severity = none # SA1200: Using directives should be placed correctly +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1200.md dotnet_diagnostic.SA1200.severity = none # SA1201: Elements should appear in the correct order +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1201.md dotnet_diagnostic.SA1201.severity = none # SA1202: Elements should be ordered by access +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1202.md dotnet_diagnostic.SA1202.severity = none # SA1203: Constants should appear before fields +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1203.md dotnet_diagnostic.SA1203.severity = none # SA1204: Static elements should appear before instance elements +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1204.md dotnet_diagnostic.SA1204.severity = none # SA1205: Partial elements should declare access +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1205.md 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 # SA1207: Protected should come before internal +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1207.md dotnet_diagnostic.SA1207.severity = none # SA1208: System using directives should be placed before other using directives +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1208.md dotnet_diagnostic.SA1208.severity = none # SA1209: Using alias directives should be placed after other using directives +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1209.md dotnet_diagnostic.SA1209.severity = none # SA1210: Using directives should be ordered alphabetically by namespace +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1210.md dotnet_diagnostic.SA1210.severity = none # SA1211: Using alias directives should be ordered alphabetically by alias name +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1211.md dotnet_diagnostic.SA1211.severity = none # SA1212: Property accessors should follow order +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1212.md dotnet_diagnostic.SA1212.severity = warning # SA1213: Event accessors should follow order +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1213.md dotnet_diagnostic.SA1213.severity = warning # SA1214: Readonly fields should appear before non-readonly fields +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1214.md dotnet_diagnostic.SA1214.severity = none # SA1216: Using static directives should be placed at the correct location +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1216.md dotnet_diagnostic.SA1216.severity = warning # SA1217: Using static directives should be ordered alphabetically +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1217.md dotnet_diagnostic.SA1217.severity = warning # SA1300: Element should begin with upper-case letter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md dotnet_diagnostic.SA1300.severity = none # SA1302: Interface names should begin with I +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1302.md dotnet_diagnostic.SA1302.severity = none # SA1303: Const field names should begin with upper-case letter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1303.md dotnet_diagnostic.SA1303.severity = none # SA1304: Non-private readonly fields should begin with upper-case letter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1304.md dotnet_diagnostic.SA1304.severity = none # SA1305: Field names should not use Hungarian notation +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1305.md dotnet_diagnostic.SA1305.severity = none # SA1306: Field names should begin with lower-case letter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1306.md dotnet_diagnostic.SA1306.severity = none # SA1307: Accessible fields should begin with upper-case letter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1307.md dotnet_diagnostic.SA1307.severity = none # SA1308: Variable names should not be prefixed +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1308.md dotnet_diagnostic.SA1308.severity = none # SA1309: Field names should not begin with underscore +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1309.md dotnet_diagnostic.SA1309.severity = none # SA1310: Field names should not contain underscore +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1310.md dotnet_diagnostic.SA1310.severity = none # SA1311: Static readonly fields should begin with upper-case letter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1311.md dotnet_diagnostic.SA1311.severity = none # SA1312: Variable names should begin with lower-case letter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1312.md dotnet_diagnostic.SA1312.severity = none # SA1313: Parameter names should begin with lower-case letter +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1313.md dotnet_diagnostic.SA1313.severity = none # SA1314: Type parameter names should begin with T +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1314.md dotnet_diagnostic.SA1314.severity = warning # SA1316: Tuple element names should use correct casing +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1316.md dotnet_diagnostic.SA1316.severity = none # SA1400: Access modifier should be declared +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1400.md dotnet_diagnostic.SA1400.severity = none # SA1401: Fields should be private +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md dotnet_diagnostic.SA1401.severity = none # SA1402: File may only contain a single type +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1402.md dotnet_diagnostic.SA1402.severity = none # SA1403: File may only contain a single namespace +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1403.md dotnet_diagnostic.SA1403.severity = none # SA1404: Code analysis suppression should have justification +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1404.md dotnet_diagnostic.SA1404.severity = none # SA1405: Debug.Assert should provide message text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1405.md dotnet_diagnostic.SA1405.severity = none # SA1406: Debug.Fail should provide message text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1406.md dotnet_diagnostic.SA1406.severity = none # SA1407: Arithmetic expressions should declare precedence +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1407.md dotnet_diagnostic.SA1407.severity = none # SA1408: Conditional expressions should declare precedence +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1408.md dotnet_diagnostic.SA1408.severity = none # SA1410: Remove delegate parenthesis when possible +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1410.md dotnet_diagnostic.SA1410.severity = none # SA1411: Attribute constructor should not use unnecessary parenthesis +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1411.md dotnet_diagnostic.SA1411.severity = none # SA1412: Store files as UTF-8 with byte order mark +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1412.md dotnet_diagnostic.SA1412.severity = none # SA1413: Use trailing comma in multi-line initializers +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1413.md dotnet_diagnostic.SA1413.severity = none # SA1414: Tuple types in signatures should have element names +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1414.md dotnet_diagnostic.SA1414.severity = none # SA1500: Braces for multi-line statements should not share line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1500.md dotnet_diagnostic.SA1500.severity = none # SA1501: Statement should not be on a single line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1501.md dotnet_diagnostic.SA1501.severity = none # SA1502: Element should not be on a single line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1502.md dotnet_diagnostic.SA1502.severity = none # SA1503: Braces should not be omitted +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1503.md dotnet_diagnostic.SA1503.severity = none # SA1504: All accessors should be single-line or multi-line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1504.md dotnet_diagnostic.SA1504.severity = warning # SA1505: Opening braces should not be followed by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1505.md dotnet_diagnostic.SA1505.severity = none # SA1506: Element documentation headers should not be followed by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1506.md dotnet_diagnostic.SA1506.severity = none # SA1507: Code should not contain multiple blank lines in a row +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1507.md dotnet_diagnostic.SA1507.severity = warning # SA1508: Closing braces should not be preceded by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1508.md dotnet_diagnostic.SA1508.severity = none # SA1509: Opening braces should not be preceded by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1509.md dotnet_diagnostic.SA1509.severity = none # SA1510: Chained statement blocks should not be preceded by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1510.md dotnet_diagnostic.SA1510.severity = none # SA1511: While-do footer should not be preceded by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1511.md dotnet_diagnostic.SA1511.severity = none # SA1512: Single-line comments should not be followed by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1512.md dotnet_diagnostic.SA1512.severity = none # SA1513: Closing brace should be followed by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md dotnet_diagnostic.SA1513.severity = none # SA1514: Element documentation header should be preceded by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1514.md dotnet_diagnostic.SA1514.severity = none # SA1515: Single-line comment should be preceded by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1515.md dotnet_diagnostic.SA1515.severity = none # SA1516: Elements should be separated by blank line +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1516.md dotnet_diagnostic.SA1516.severity = warning # SA1517: Code should not contain blank lines at start of file +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1517.md dotnet_diagnostic.SA1517.severity = warning # SA1518: Use line endings correctly at end of file +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1518.md dotnet_diagnostic.SA1518.severity = warning # SA1519: Braces should not be omitted from multi-line child statement +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1519.md dotnet_diagnostic.SA1519.severity = none # SA1520: Use braces consistently +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1520.md dotnet_diagnostic.SA1520.severity = none # SA1600: Elements should be documented +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1600.md dotnet_diagnostic.SA1600.severity = none # SA1601: Partial elements should be documented +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1601.md dotnet_diagnostic.SA1601.severity = none # SA1602: Enumeration items should be documented +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1602.md dotnet_diagnostic.SA1602.severity = none # SA1604: Element documentation should have summary +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1604.md dotnet_diagnostic.SA1604.severity = none # SA1605: Partial element documentation should have summary +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1605.md dotnet_diagnostic.SA1605.severity = none # SA1606: Element documentation should have summary text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1606.md dotnet_diagnostic.SA1606.severity = none # SA1607: Partial element documentation should have summary text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1607.md dotnet_diagnostic.SA1607.severity = none # SA1608: Element documentation should not have default summary +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1608.md dotnet_diagnostic.SA1608.severity = none # SA1609: Property documentation should have value +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1609.md dotnet_diagnostic.SA1609.severity = none # SA1610: Property documentation should have value text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1610.md dotnet_diagnostic.SA1610.severity = none # SA1611: Element parameters should be documented +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1611.md dotnet_diagnostic.SA1611.severity = none # SA1612: Element parameter documentation should match element parameters +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1612.md dotnet_diagnostic.SA1612.severity = none # SA1613: Element parameter documentation should declare parameter name +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1613.md dotnet_diagnostic.SA1613.severity = none # SA1614: Element parameter documentation should have text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1614.md dotnet_diagnostic.SA1614.severity = none # SA1615: Element return value should be documented +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1615.md dotnet_diagnostic.SA1615.severity = none # SA1616: Element return value documentation should have text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1616.md dotnet_diagnostic.SA1616.severity = none # SA1617: Void return value should not be documented +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1617.md dotnet_diagnostic.SA1617.severity = none # SA1618: Generic type parameters should be documented +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1618.md dotnet_diagnostic.SA1618.severity = none # SA1619: Generic type parameters should be documented partial class +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1619.md dotnet_diagnostic.SA1619.severity = none # SA1620: Generic type parameter documentation should match type parameters +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1620.md dotnet_diagnostic.SA1620.severity = none # SA1621: Generic type parameter documentation should declare parameter name +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1621.md dotnet_diagnostic.SA1621.severity = none # SA1622: Generic type parameter documentation should have text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1622.md dotnet_diagnostic.SA1622.severity = none # SA1623: Property summary documentation should match accessors +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1623.md dotnet_diagnostic.SA1623.severity = none # SA1624: Property summary documentation should omit accessor with restricted access +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1624.md dotnet_diagnostic.SA1624.severity = none # SA1625: Element documentation should not be copied and pasted +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1625.md dotnet_diagnostic.SA1625.severity = none # SA1626: Single-line comments should not use documentation style slashes +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1626.md dotnet_diagnostic.SA1626.severity = none # SA1627: Documentation text should not be empty +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1627.md dotnet_diagnostic.SA1627.severity = none # SA1629: Documentation text should end with a period +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1629.md dotnet_diagnostic.SA1629.severity = none # SA1633: File should have header +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1633.md dotnet_diagnostic.SA1633.severity = none # SA1634: File header should show copyright +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1634.md dotnet_diagnostic.SA1634.severity = none # SA1635: File header should have copyright text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1635.md dotnet_diagnostic.SA1635.severity = none # SA1636: File header copyright text should match +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1636.md dotnet_diagnostic.SA1636.severity = none # SA1637: File header should contain file name +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1637.md dotnet_diagnostic.SA1637.severity = none # SA1638: File header file name documentation should match file name +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1638.md dotnet_diagnostic.SA1638.severity = none # SA1639: File header should have summary +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1639.md dotnet_diagnostic.SA1639.severity = none # SA1640: File header should have valid company text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1640.md dotnet_diagnostic.SA1640.severity = none # SA1641: File header company name text should match +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1641.md dotnet_diagnostic.SA1641.severity = none # SA1642: Constructor summary documentation should begin with standard text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1642.md dotnet_diagnostic.SA1642.severity = none # SA1643: Destructor summary documentation should begin with standard text +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1643.md dotnet_diagnostic.SA1643.severity = warning # SA1648: inheritdoc should be used with inheriting class +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1648.md dotnet_diagnostic.SA1648.severity = none # SA1649: File name should match first type name +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1649.md dotnet_diagnostic.SA1649.severity = none # SA1651: Do not use placeholder elements +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1651.md dotnet_diagnostic.SA1651.severity = none # SX1101: Do not prefix local calls with 'this.' +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SX1101.md dotnet_diagnostic.SX1101.severity = none # SX1309: Field names should begin with underscore +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SX1309.md dotnet_diagnostic.SX1309.severity = none # SX1309S: Static field names should begin with underscore +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SX1309S.md dotnet_diagnostic.SX1309S.severity = none diff --git a/.mailmap b/.mailmap new file mode 100644 index 00000000000..0bc786ac445 --- /dev/null +++ b/.mailmap @@ -0,0 +1 @@ +Andy Jordan diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 00000000000..1d3c5b1ac92 --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1 @@ +.github/SECURITY.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..00555349c35 --- /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-mariner-20-l", + "imageVersion": "v11" + } + } + ] + } + } + } + ] + } + ], + "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..25a5686b33e --- /dev/null +++ b/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 @@ -0,0 +1,393 @@ +<# +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 { + Write-Failure "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 + + if ($releaseVersion.Contains('-')) { + $channel = 'preview' + $packageNames = @('powershell-preview') + } + else { + $channel = 'stable' + $packageNames = @('powershell') + } + + if ($lts) { + $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..f20e8a31114 --- /dev/null +++ b/.pipelines/MSIXBundle-vPack-Official.yml @@ -0,0 +1,147 @@ +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: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false +- name: 'ReleaseTagVar' + type: string + displayName: 'Release Tag Var:' + default: 'fromBranch' + +name: msixbundle_vPack_$(date:yyMM).$(date:dd)$(rev:rrr) + +variables: + CDP_DEFINITION_BUILD_COUNT: $[counter('', 0)] + system.debug: ${{ parameters.debug }} + BuildSolution: $(Build.SourcesDirectory)\dirs.proj + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + BuildConfiguration: Release + WindowsContainerImage: 'onebranch.azurecr.io/windows/ltsc2019/vse2022:latest' + Codeql.Enabled: false # pipeline is not building artifacts; it repackages existing artifacts into a vpack + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + POWERSHELL_TELEMETRY_OPTOUT: 1 + +resources: + repositories: + - repository: templates + 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 + parameters: + platform: + name: 'windows_undocked' # windows undocked + + 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 + # APIScan requires a non-Ready-To-Run build + apiscan: + enabled: false + asyncSDL: + enabled: false + tsaOptionsFile: .config/tsaoptions.json + + stages: + - stage: build + jobs: + - job: main + pool: + type: windows + + variables: + ob_outputDirectory: '$(BUILD.SOURCESDIRECTORY)\out' + ob_createvpack_enabled: ${{ parameters.createVPack }} + ob_createvpack_packagename: 'PowerShell.app' + ob_createvpack_owneralias: 'dongbow' + ob_createvpack_description: 'VPack for the PowerShell Application' + ob_createvpack_targetDestinationDirectory: '$(Destination)' + ob_createvpack_propsFile: false + ob_createvpack_provData: true + ob_createvpack_metadata: '$(Build.SourceVersion)' + ob_createvpack_versionAs: string + ob_createvpack_version: '$(version)' + ob_createvpack_verbose: true + + steps: + - template: .pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + UseJson: no + + - pwsh: | + Write-Verbose -Verbose 'PowerShell Version: $(version)' + if('$(version)' -match '-') { + throw "Don't release a preview build msixbundle package" + } + displayName: Stop any preview release + + - download: PSPackagesOfficial + artifact: 'drop_msixbundle_CreateMSIXBundle' + displayName: Download package + + - pwsh: | + $payloadDir = '$(Pipeline.Workspace)\PSPackagesOfficial\drop_msixbundle_CreateMSIXBundle' + Get-ChildItem $payloadDir -Recurse | Out-String -Width 150 + $vstsCommandString = "vso[task.setvariable variable=PayloadDir]$payloadDir" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + displayName: 'Capture Artifact Listing' + + - pwsh: | + $bundlePackage = Get-ChildItem '$(PayloadDir)\*.msixbundle' + Write-Verbose -Verbose ("MSIX bundle package: " + $bundlePackage.FullName -join ', ') + if ($bundlePackage.Count -ne 1) { + throw "Expected to find 1 MSIX bundle package, but found $($bundlePackage.Count)" + } + + if (-not (Test-Path '$(ob_outputDirectory)' -PathType Container)) { + $null = New-Item '$(ob_outputDirectory)' -ItemType Directory -ErrorAction Stop + } + + $targetPath = Join-Path '$(ob_outputDirectory)' 'Microsoft.PowerShell_8wekyb3d8bbwe.msixbundle' + Copy-Item -Verbose -Path $bundlePackage.FullName -Destination $targetPath + displayName: 'Stage msixbundle for vpack' + + - pwsh: | + Write-Verbose "VPack Version: $(ob_createvpack_version)" -Verbose + $vpackFiles = Get-ChildItem -Path $(ob_outputDirectory)\* -Recurse + if($vpackFiles.Count -eq 0) { + throw "No files found in $(ob_outputDirectory)" + } + $vpackFiles | Out-String -Width 150 + displayName: Debug Output Directory and Version + condition: succeededOrFailed() diff --git a/.pipelines/PowerShell-Coordinated_Packages-Official.yml b/.pipelines/PowerShell-Coordinated_Packages-Official.yml new file mode 100644 index 00000000000..902c31f8a96 --- /dev/null +++ b/.pipelines/PowerShell-Coordinated_Packages-Official.yml @@ -0,0 +1,305 @@ +name: bins-$(BUILD.SOURCEBRANCHNAME)-$(Build.BuildId) +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 + +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: + - 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 + + +extends: + template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates + parameters: + customTags: 'ES365AIMigrationTooling' + featureFlags: + LinuxHostVersion: + Network: KS3 + WindowsHostVersion: + Network: KS3 + 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' + 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 + + stages: + - stage: prep + jobs: + - job: SetVars + displayName: Set Variables + pool: + type: windows + + variables: + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT/BuildJson' + - name: ob_sdl_codeSignValidation_enabled + value: false + - name: ob_sdl_codeql_compiled_enabled + value: false + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + - name: ob_signing_setup_enabled + value: false + - name: ob_sdl_sbom_enabled + value: false + + steps: + - checkout: self + clean: true + env: + ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase + + - pwsh: | + Get-ChildItem Env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture environment variables + env: + ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase + + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: yes + UseJson: no + + - stage: macos + displayName: macOS - build and sign + dependsOn: ['prep'] + jobs: + - template: /.pipelines/templates/mac.yml@self + parameters: + buildArchitecture: x64 + - template: /.pipelines/templates/mac.yml@self + parameters: + buildArchitecture: arm64 + + - stage: linux + displayName: linux - build and sign + dependsOn: ['prep'] + jobs: + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-x64' + JobName: 'linux_x64' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-x64' + JobName: 'linux_x64_minSize' + BuildConfiguration: 'minSize' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-arm' + JobName: 'linux_arm' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-arm64' + JobName: 'linux_arm64' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'fxdependent-linux-x64' + JobName: 'linux_fxd_x64_mariner' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'fxdependent-linux-arm64' + JobName: 'linux_fxd_arm64_mariner' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'fxdependent-noopt-linux-musl-x64' + JobName: 'linux_fxd_x64_alpine' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'fxdependent' + JobName: 'linux_fxd' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-musl-x64' + JobName: 'linux_x64_alpine' + + - stage: windows + displayName: windows - build and sign + dependsOn: ['prep'] + condition: and(succeeded(),eq('${{ parameters.RUN_WINDOWS }}','true')) + jobs: + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: x64 + BuildConfiguration: release + JobName: build_windows_x64_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: x64 + BuildConfiguration: minSize + JobName: build_windows_x64_minSize_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: x86 + JobName: build_windows_x86_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: arm64 + JobName: build_windows_arm64_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: fxdependent + JobName: build_windows_fxdependent_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: fxdependentWinDesktop + JobName: build_windows_fxdependentWinDesktop_release + + - stage: test_and_release_artifacts + displayName: Test and Release Artifacts + dependsOn: ['prep'] + condition: and(succeeded(),eq('${{ parameters.RUN_TEST_AND_RELEASE }}','true')) + jobs: + - template: /.pipelines/templates/testartifacts.yml@self + + - job: release_json + displayName: Create and Upload release.json + pool: + type: windows + variables: + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + steps: + - checkout: self + clean: true + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + - powershell: | + $metadata = Get-Content '$(Build.SourcesDirectory)/PowerShell/tools/metadata.json' -Raw | ConvertFrom-Json + $LTS = $metadata.LTSRelease.Package + @{ ReleaseVersion = "$(Version)"; LTSRelease = $LTS } | ConvertTo-Json | Out-File "$(Build.StagingDirectory)\release.json" + Get-Content "$(Build.StagingDirectory)\release.json" + + if (-not (Test-Path "$(ob_outputDirectory)\metadata")) { + New-Item -ItemType Directory -Path "$(ob_outputDirectory)\metadata" + } + + Copy-Item -Path "$(Build.StagingDirectory)\release.json" -Destination "$(ob_outputDirectory)\metadata" -Force + displayName: Create and upload release.json file to build artifact + retryCountOnTaskFailure: 2 + - template: /.pipelines/templates/step/finalize.yml@self diff --git a/.pipelines/PowerShell-Packages-Official.yml b/.pipelines/PowerShell-Packages-Official.yml new file mode 100644 index 00000000000..487e8cb9c6a --- /dev/null +++ b/.pipelines/PowerShell-Packages-Official.yml @@ -0,0 +1,259 @@ +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: pkgs-$(BUILD.SOURCEBRANCHNAME)-$(Build.BuildId) + +variables: + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] # needed for onebranch.pipeline.version task + - name: system.debug + value: ${{ parameters.debug }} + - name: ENABLE_PRS_DELAYSIGN + value: 1 + - name: ROOT + value: $(Build.SourcesDirectory) + - name: ForceAzureBlobDelete + value: ${{ parameters.ForceAzureBlobDelete }} + - name: NUGET_XMLDOC_MODE + value: none + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - name: ReleaseTagVar + value: ${{ parameters.ReleaseTagVar }} + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: WindowsContainerImage + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' # Docker image which is used to build the project + - name: LinuxContainerImage + value: mcr.microsoft.com/onebranch/cbl-mariner/build:2.0 + - group: mscodehub-feed-read-general + - group: mscodehub-feed-read-akv + - name: branchCounterKey + value: $[format('{0:yyyyMMdd}-{1}', pipeline.startTime,variables['Build.SourceBranch'])] + - name: branchCounter + value: $[counter(variables['branchCounterKey'], 1)] + - group: MSIXSigningProfile + +resources: + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated Binaries-Official' + trigger: + branches: + include: + - master + - releases/* + + repositories: + - repository: templates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +extends: + template: v2/OneBranch.Official.CrossPlat.yml@templates + parameters: + cloudvault: + enabled: false + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: KS3 + linuxEsrpSigning: 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 + 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' + 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 + stages: + - stage: prep + jobs: + - template: /.pipelines/templates/checkAzureContainer.yml@self + + - stage: mac_package + dependsOn: [prep] + jobs: + - template: /.pipelines/templates/mac-package-build.yml@self + parameters: + buildArchitecture: x64 + + - template: /.pipelines/templates/mac-package-build.yml@self + parameters: + buildArchitecture: arm64 + + - stage: windows_package + dependsOn: [prep] + jobs: + - template: /.pipelines/templates/windows-package-build.yml@self + parameters: + runtime: x64 + + - template: /.pipelines/templates/windows-package-build.yml@self + parameters: + runtime: arm64 + + - template: /.pipelines/templates/windows-package-build.yml@self + parameters: + runtime: x86 + + - template: /.pipelines/templates/windows-package-build.yml@self + parameters: + runtime: fxdependent + + - template: /.pipelines/templates/windows-package-build.yml@self + parameters: + runtime: fxdependentWinDesktop + + - template: /.pipelines/templates/windows-package-build.yml@self + parameters: + runtime: minsize + + - stage: linux_package + dependsOn: [prep] + jobs: + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64' + signedDrop: 'drop_linux_sign_linux_x64' + packageType: deb + jobName: deb + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_fxd_x64_mariner' + signedDrop: 'drop_linux_sign_linux_fxd_x64_mariner' + packageType: rpm-fxdependent #mariner-x64 + jobName: mariner_x64 + signingProfile: 'CP-459159-pgpdetached' + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_fxd_arm64_mariner' + signedDrop: 'drop_linux_sign_linux_fxd_arm64_mariner' + packageType: rpm-fxdependent-arm64 #mariner-arm64 + jobName: mariner_arm64 + signingProfile: 'CP-459159-pgpdetached' + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64' + signedDrop: 'drop_linux_sign_linux_x64' + packageType: rpm + jobName: rpm + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_arm' + signedDrop: 'drop_linux_sign_linux_arm' + packageType: tar-arm + jobName: tar_arm + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_arm64' + signedDrop: 'drop_linux_sign_linux_arm64' + packageType: tar-arm64 + jobName: tar_arm64 + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64_alpine' + signedDrop: 'drop_linux_sign_linux_x64_alpine' + packageType: tar-alpine + jobName: tar_alpine + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_fxd' + signedDrop: 'drop_linux_sign_linux_fxd' + packageType: fxdependent + jobName: fxdependent + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64' + signedDrop: 'drop_linux_sign_linux_x64' + packageType: tar + jobName: tar + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_fxd_x64_alpine' + signedDrop: 'drop_linux_sign_linux_fxd_x64_alpine' + packageType: tar-alpine-fxdependent + jobName: tar_alpine_fxd + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64_minSize' + signedDrop: 'drop_linux_sign_linux_x64_minSize' + packageType: min-size + jobName: minSize + + - stage: nupkg + dependsOn: [prep] + jobs: + - template: /.pipelines/templates/nupkg.yml@self + + - stage: msixbundle + displayName: 'Create MSIX Bundle' + dependsOn: [windows_package] + jobs: + - template: /.pipelines/templates/package-create-msix.yml@self + + - stage: upload + dependsOn: [mac_package, windows_package, linux_package, nupkg, msixbundle] + jobs: + - template: /.pipelines/templates/uploadToAzure.yml@self diff --git a/.pipelines/PowerShell-Release-Official-Azure.yml b/.pipelines/PowerShell-Release-Official-Azure.yml new file mode 100644 index 00000000000..2d644c7a5dd --- /dev/null +++ b/.pipelines/PowerShell-Release-Official-Azure.yml @@ -0,0 +1,103 @@ +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)-$(Build.BuildId) + +variables: + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] + - name: system.debug + value: ${{ parameters.debug }} + - name: ENABLE_PRS_DELAYSIGN + value: 1 + - name: ROOT + value: $(Build.SourcesDirectory) + - name: REPOROOT + value: $(Build.SourcesDirectory) + - name: OUTPUTROOT + value: $(REPOROOT)\out + - name: NUGET_XMLDOC_MODE + value: none + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\.config\tsaoptions.json + - name: WindowsContainerImage + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' + - name: LinuxContainerImage + value: mcr.microsoft.com/onebranch/cbl-mariner/build:2.0 + - group: PoolNames + +resources: + repositories: + - repository: templates + 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@templates + parameters: + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: Netlock + linuxEsrpSigning: 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 + asyncSdl: + enabled: true + tsaOptionsFile: .config/tsaoptions.json + 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 + policheck: + break: true # always break the build on policheck issues. You can disable it by setting to 'false' + tsaOptionsFile: .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 new file mode 100644 index 00000000000..0c41442da9f --- /dev/null +++ b/.pipelines/PowerShell-Release-Official.yml @@ -0,0 +1,436 @@ +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: release-$(BUILD.SOURCEBRANCHNAME)-$(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 + +resources: + repositories: + - repository: templates + 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 Binaries-Official' + + - pipeline: PSPackagesOfficial + source: 'PowerShell-Packages-Official' + trigger: + branches: + include: + - master + - releases/* + +extends: + template: v2/OneBranch.Official.CrossPlat.yml@templates + parameters: + release: + category: NonAzure + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: KS3 + 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 + asyncSdl: + enabled: true + tsaOptionsFile: .config/tsaoptions.json + 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 + 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: + - stage: setReleaseTagAndChangelog + displayName: 'Set Release Tag and Upload Changelog' + jobs: + - template: /.pipelines/templates/release-SetTagAndChangelog.yml@self + + - stage: validateSdk + displayName: 'Validate SDK' + dependsOn: [] + jobs: + - template: /.pipelines/templates/release-validate-sdk.yml@self + parameters: + jobName: "windowsSDK" + displayName: "Windows SDK Validation" + imageName: PSMMS2019-Secure + poolName: $(windowsPool) + + - template: /.pipelines/templates/release-validate-sdk.yml@self + parameters: + jobName: "MacOSSDK" + displayName: "MacOS SDK Validation" + imageName: macOS-latest + poolName: Azure Pipelines + + - template: /.pipelines/templates/release-validate-sdk.yml@self + parameters: + jobName: "LinuxSDK" + displayName: "Linux SDK Validation" + imageName: PSMMSUbuntu20.04-Secure + poolName: $(ubuntuPool) + + - stage: gbltool + displayName: 'Validate Global tools' + dependsOn: [] + jobs: + - template: /.pipelines/templates/release-validate-globaltools.yml@self + parameters: + jobName: "WindowsGlobalTools" + displayName: "Windows Global Tools Validation" + jobtype: windows + + - template: /.pipelines/templates/release-validate-globaltools.yml@self + parameters: + jobName: "LinuxGlobalTools" + displayName: "Linux Global Tools Validation" + jobtype: linux + globalToolExeName: 'pwsh' + globalToolPackageName: 'PowerShell.Linux.x64' + + - stage: fxdpackages + displayName: 'Validate FXD Packages' + dependsOn: [] + jobs: + - template: /.pipelines/templates/release-validate-fxdpackages.yml@self + parameters: + jobName: 'winfxd' + displayName: 'Validate Win Fxd Packages' + jobtype: 'windows' + artifactName: 'drop_windows_package_package_win_fxdependent' + packageNamePattern: '**/*win-fxdependent.zip' + + - template: /.pipelines/templates/release-validate-fxdpackages.yml@self + parameters: + jobName: 'winfxdDesktop' + displayName: 'Validate WinDesktop Fxd Packages' + jobtype: 'windows' + artifactName: 'drop_windows_package_package_win_fxdependentWinDesktop' + packageNamePattern: '**/*win-fxdependentwinDesktop.zip' + + - template: /.pipelines/templates/release-validate-fxdpackages.yml@self + parameters: + jobName: 'linuxfxd' + displayName: 'Validate Linux Fxd Packages' + jobtype: 'linux' + artifactName: 'drop_linux_package_fxdependent' + packageNamePattern: '**/*linux-x64-fxdependent.tar.gz' + + - template: /.pipelines/templates/release-validate-fxdpackages.yml@self + parameters: + jobName: 'linuxArm64fxd' + displayName: 'Validate Linux ARM64 Fxd Packages' + jobtype: 'linux' + artifactName: 'drop_linux_package_fxdependent' + # this is really an architecture independent package + packageNamePattern: '**/*linux-x64-fxdependent.tar.gz' + arm64: 'yes' + enableCredScan: false + + - stage: validatePackages + displayName: 'Validate Packages' + dependsOn: [] + jobs: + - template: /.pipelines/templates/release-validate-packagenames.yml@self + + - stage: ManualValidation + dependsOn: [] + displayName: Manual Validation + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Validate Windows Packages + jobName: ValidateWinPkg + instructions: | + Validate zip package on windows + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Validate OSX Packages + jobName: ValidateOsxPkg + instructions: | + Validate tar.gz package on osx-arm64 + + - stage: ReleaseAutomation + dependsOn: [] + displayName: 'Release Automation' + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Start Release Automation + jobName: StartRA + instructions: | + Kick off Release automation build at: https://dev.azure.com/powershell-rel/Release-Automation/_build?definitionId=10&_a=summary + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Triage results + jobName: TriageRA + dependsOnJob: StartRA + instructions: | + Triage ReleaseAutomation results + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Signoff Tests + dependsOnJob: TriageRA + jobName: SignoffTests + instructions: | + Signoff ReleaseAutomation results + + - stage: UpdateChangeLog + displayName: Update the changelog + dependsOn: + - ManualValidation + - ReleaseAutomation + - validatePackages + - fxdpackages + - gbltool + - validateSdk + + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Make sure the changelog is updated + jobName: MergeChangeLog + instructions: | + Update and merge the changelog for the release. + This step is required for creating GitHub draft release. + + - stage: PublishGitHubReleaseAndNuget + displayName: Publish GitHub and Nuget Release + dependsOn: + - setReleaseTagAndChangelog + - UpdateChangeLog + variables: + ob_release_environment: Production + jobs: + - template: /.pipelines/templates/release-githubNuget.yml@self + parameters: + skipPublish: ${{ parameters.SkipPublish }} + + - stage: PushGitTagAndMakeDraftPublic + displayName: Push Git Tag and Make Draft Public + dependsOn: PublishGitHubReleaseAndNuget + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Push Git Tag + jobName: PushGitTag + instructions: | + Push the git tag to upstream + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Make Draft Public + dependsOnJob: PushGitTag + jobName: DraftPublic + instructions: | + Make the GitHub Release Draft Public + + - stage: BlobPublic + displayName: Make Blob Public + dependsOn: + - UpdateChangeLog + - PushGitTagAndMakeDraftPublic + jobs: + - template: /.pipelines/templates/release-MakeBlobPublic.yml@self + parameters: + SkipPSInfraInstallers: ${{ parameters.SkipPSInfraInstallers }} + + - stage: PublishPMC + displayName: Publish PMC + dependsOn: PushGitTagAndMakeDraftPublic + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Publish to PMC + jobName: ReleaseToPMC + instructions: | + Run PowerShell-Release-Official-Azure.yml pipeline to publish to PMC + + - stage: UpdateDotnetDocker + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Update DotNet SDK Docker images + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Update .NET SDK docker images + jobName: DotnetDocker + instructions: | + Create PR for updating dotnet-docker images to use latest PowerShell version. + 1. Fork and clone https://github.com/dotnet/dotnet-docker.git + 2. git checkout upstream/nightly -b updatePS + 3. dotnet run --project .\eng\update-dependencies\ specific --product-version powershell= --compute-shas + 4. create PR targeting nightly branch + + - stage: UpdateWinGet + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Add manifest entry to winget + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Add manifest entry to winget + jobName: UpdateWinGet + instructions: | + This is typically done by the community 1-2 days after the release. + + - stage: PublishMsix + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Publish MSIX to store + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Publish the MSIX Bundle package to store + jobName: PublishMsix + instructions: | + Ask Steve to release MSIX bundle package to Store + + - stage: PublishVPack + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Release vPack + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Start 2 vPack Release pipelines + jobName: PublishVPack + instructions: | + 1. Kick off PowerShell-vPack-Official pipeline + 2. Kick off PowerShell-MSIXBundle-VPack pipeline + + # Need to verify if the Az PS / CLI team still uses this. Skippinng for this release. + # - stage: ReleaseDeps + # dependsOn: GitHubTasks + # displayName: Update pwsh.deps.json links + # jobs: + # - template: templates/release-UpdateDepsJson.yml + + - stage: UploadBuildInfoJson + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Upload BuildInfo.json + jobs: + - template: /.pipelines/templates/release-upload-buildinfo.yml@self + + - stage: ReleaseSymbols + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Release Symbols + jobs: + - template: /.pipelines/templates/release-symbols.yml@self + + - stage: ChangesToMaster + displayName: Ensure changes are in GH master + dependsOn: + - PublishPMC + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Make sure changes are in master + jobName: MergeToMaster + instructions: | + Make sure that changes README.md and metadata.json are merged into master on GitHub. + + - stage: ReleaseToMU + displayName: Release to MU + dependsOn: PushGitTagAndMakeDraftPublic # This only needs the blob to be available + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Release to MU + instructions: | + Notify the PM team to start the process of releasing to MU. + + - stage: ReleaseClose + displayName: Finish Release + dependsOn: + - ReleaseToMU + - ReleaseSymbols + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Retain Build + jobName: RetainBuild + instructions: | + Retain the build + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Delete release branch + jobName: DeleteBranch + instructions: | + Delete release diff --git a/.pipelines/PowerShell-vPack-Official.yml b/.pipelines/PowerShell-vPack-Official.yml new file mode 100644 index 00000000000..36b6505dd04 --- /dev/null +++ b/.pipelines/PowerShell-vPack-Official.yml @@ -0,0 +1,224 @@ +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: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false +- name: 'architecture' + type: string + displayName: 'Select the vpack architecture:' + values: + - x64 + - x86 + - arm64 + default: x64 +- name: 'VPackPublishOverride' + type: string + displayName: 'VPack Publish Override Version (can leave blank):' + default: ' ' +- name: 'ReleaseTagVar' + type: string + displayName: 'Release Tag Var:' + default: 'fromBranch' + +name: vPack_${{ parameters.architecture }}_$(date:yyMM).$(date:dd)$(rev:rrr) + +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 + +resources: + repositories: + - repository: templates + 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 + parameters: + platform: + name: 'windows_undocked' # windows undocked + + 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 + # APIScan requires a non-Ready-To-Run build + apiscan: + enabled: false + asyncSDL: + enabled: false + tsaOptionsFile: .config/tsaoptions.json + stages: + - stage: main + jobs: + - job: main + pool: + type: windows + + variables: + ob_outputDirectory: '$(BUILD.SOURCESDIRECTORY)\out' + ob_createvpack_enabled: ${{ parameters.createVPack }} + ob_createvpack_packagename: 'PowerShell.${{ parameters.architecture }}' + ob_createvpack_description: PowerShell ${{ parameters.architecture }} $(version) + ob_createvpack_owneralias: tplunk + ob_createvpack_versionAs: string + ob_createvpack_version: '$(version)' + ob_createvpack_propsFile: true + ob_createvpack_verbose: true + + steps: + - template: .pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: yes + UseJson: no + + - pwsh: | + if($env:RELEASETAGVAR -match '-') { + throw "Don't release a preview build without coordinating with Windows Engineering Build Tools Team" + } + displayName: Stop any preview release + + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + packageType: sdk + version: 3.1.x + installationPath: $(Agent.ToolsDirectory)/dotnet + + - pwsh: | + $packageArtifactName = 'drop_windows_package_package_win_${{ parameters.architecture }}' + $vstsCommandString = "vso[task.setvariable variable=PackageArtifactName]$packageArtifactName" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + + $packageArtifactPath = '$(Pipeline.Workspace)\PSPackagesOfficial' + $vstsCommandString = "vso[task.setvariable variable=PackageArtifactPath]$packageArtifactPath" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + displayName: 'Set package artifact variables' + + - download: PSPackagesOfficial + artifact: $(PackageArtifactName) + displayName: Download package + + - pwsh: 'Get-ChildItem $(PackageArtifactPath)\* -recurse | Select-Object -ExpandProperty Name' + displayName: 'Capture Artifact Listing' + + - pwsh: | + $message = @() + $packages = Get-ChildItem $(PackageArtifactPath)\* -recurse -include *.zip, *.msi + + if($packages.count -eq 0) {throw "No packages found in $(PackageArtifactPath)"} + + $packages | ForEach-Object { + if($_.Name -notmatch 'PowerShell-\d+\.\d+\.\d+\-([a-z]*.\d+\-)?win\-(fxdependent|x64|arm64|x86|fxdependentWinDesktop)\.(msi|zip){1}') + { + $messageInstance = "$($_.Name) is not a valid package name" + $message += $messageInstance + Write-Warning $messageInstance + } + } + + if($message.count -gt 0){throw ($message | out-string)} + displayName: 'Validate Zip and MSI Package Names' + + - pwsh: | + Get-ChildItem $(PackageArtifactPath)\* -recurse -include *.zip | ForEach-Object { + if($_.Name -match 'PowerShell-\d+\.\d+\.\d+\-([a-z]*.\d+\-)?win\-(${{ parameters.architecture }})\.(zip){1}') + { + Expand-Archive -Path $_.FullName -DestinationPath $(ob_outputDirectory) + } + } + displayName: 'Extract Zip to ob_outputDirectory' + + - pwsh: | + Write-Verbose "VPack Version: $(ob_createvpack_version)" -Verbose + Get-ChildItem -Path $(ob_outputDirectory)\* -Recurse + Get-Content $(ob_outputdirectory)\preview.json -ErrorAction SilentlyContinue | Write-Host + displayName: Debug Output Directory and Version + condition: succeededOrFailed() + + - pwsh: | + Write-Host "Using VPackPublishOverride variable" + $vpackVersion = '${{ parameters.VPackPublishOverride }}' + $vstsCommandString = "vso[task.setvariable variable=ob_createvpack_version]$vpackVersion" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + condition: ne('${{ parameters.VPackPublishOverride }}', ' ') + displayName: 'Set ob_createvpack_version with VPackPublishOverride' + + - pwsh: | + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture Environment + condition: succeededOrFailed() + + - pwsh: | + Write-Verbose "VPack Version: $(ob_createvpack_version)" -Verbose + $vpackFiles = Get-ChildItem -Path $(ob_outputDirectory)\* -Recurse + if($vpackFiles.Count -eq 0) { + throw "No files found in $(ob_outputDirectory)" + } + $vpackFiles + displayName: Debug Output Directory and Version + condition: succeededOrFailed() + + - task: onebranch.pipeline.signing@1 + displayName: 'Onebranch Signing' + inputs: + command: 'sign' + signing_environment: 'azure-ado' + cp_code: $(windows_build_tools_cert_id) + files_to_sign: '**/*.exe;**/System.Management.Automation.dll' + search_root: $(ob_outputDirectory) diff --git a/.pipelines/apiscan-gen-notice.yml b/.pipelines/apiscan-gen-notice.yml new file mode 100644 index 00000000000..7596f79998e --- /dev/null +++ b/.pipelines/apiscan-gen-notice.yml @@ -0,0 +1,115 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +name: apiscan-genNotice-$(BUILD.SOURCEBRANCHNAME)-$(Build.BuildId) +trigger: none + +parameters: + - name: FORCE_CODEQL + displayName: Debugging - Enable CodeQL and set cadence to 1 hour + type: boolean + default: false + - name: SkipVerifyPackages + type: boolean + default: false + +variables: + # PAT permissions NOTE: Declare a SymbolServerPAT variable in this group with a 'microsoft' organizanization scoped PAT with 'Symbols' Read permission. + # A PAT in the wrong org will give a single Error 203. No PAT will give a single Error 401, and individual pdbs may be missing even if permissions are correct. + - group: symbols + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] + # 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: LinuxContainerImage + value: onebranch.azurecr.io/linux/ubuntu-2004:latest + - name: WindowsContainerImage + value: onebranch.azurecr.io/windows/ltsc2022/vse2022:latest + - ${{ if eq(parameters['FORCE_CODEQL'],'true') }}: + # Cadence is hours before CodeQL will allow a re-upload of the database + - name: CodeQL.Cadence + value: 0 + - name: CODEQL_ENABLED + ${{ if or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(parameters['FORCE_CODEQL'],'true')) }}: + value: true + ${{ else }}: + value: false + - name: Codeql.TSAEnabled + value: $(CODEQL_ENABLED) + # AnalyzeInPipeline: false = upload results + # AnalyzeInPipeline: true = do not upload results + - name: Codeql.AnalyzeInPipeline + ${{ if or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(parameters['FORCE_CODEQL'],'true')) }}: + value: false + ${{ else }}: + value: true + +resources: + repositories: + - repository: templates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +extends: + template: v2/OneBranch.NonOfficial.CrossPlat.yml@templates + parameters: + featureFlags: + WindowsHostVersion: + Version: 2022 + globalSdl: + codeql: + compiled: + enabled: $(CODEQL_ENABLED) + tsaEnabled: $(CODEQL_ENABLED) # This enables TSA bug filing only for CodeQL 3000 + armory: + enabled: false + sbom: + enabled: false + cg: + enabled: true + ignoreDirectories: '.devcontainer,demos,docker,docs,src,test,tools/packaging' + tsa: + enabled: true # onebranch publish all SDL results to TSA. If TSA is disabled all SDL tools will forced into 'break' build mode. + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + binskim: + 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: + enabled: true + softwareName: "PowerShell" # Default is repo name + versionNumber: "7.5" # Default is build number + isLargeApp: false # Default: false. + symbolsFolder: $(SymbolsServerUrl);$(ob_outputDirectory) +#softwareFolder - relative path to a folder to be scanned. Default value is root of artifacts folder + tsaOptionsFile: .config\tsaoptions.json + psscriptanalyzer: + enabled: true + policyName: Microsoft + break: false + + stages: + - stage: APIScan + displayName: 'ApiScan' + dependsOn: [] + jobs: + - template: /.pipelines/templates/compliance/apiscan.yml@self + parameters: + parentJobs: [] + - stage: notice + displayName: Generate Notice File + dependsOn: [] + jobs: + - template: /.pipelines/templates/compliance/generateNotice.yml@self + parameters: + parentJobs: [] + SkipVerifyPackages: ${{ parameters.SkipVerifyPackages }} diff --git a/.pipelines/templates/SetVersionVariables.yml b/.pipelines/templates/SetVersionVariables.yml new file mode 100644 index 00000000000..9f692373f6c --- /dev/null +++ b/.pipelines/templates/SetVersionVariables.yml @@ -0,0 +1,79 @@ +parameters: + ReleaseTagVar: v6.2.0 + ReleaseTagVarName: ReleaseTagVar + CreateJson: 'no' + UseJson: 'yes' + +steps: +- ${{ if eq(parameters['UseJson'],'yes') }}: + - task: DownloadBuildArtifacts@0 + inputs: + artifactName: 'drop_prep_SetVars' + itemPattern: '*.json' + downloadPath: '$(System.ArtifactsDirectory)' + displayName: Download Build Info Json + env: + ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + +- powershell: | + $path = "./build.psm1" + if($env:REPOROOT){ + Write-Verbose "reporoot already set to ${env:REPOROOT}" -Verbose + exit 0 + } + if(Test-Path -Path $path) + { + Write-Verbose "reporoot detected at: ." -Verbose + $repoRoot = '.' + } + else{ + $path = "./PowerShell/build.psm1" + if(Test-Path -Path $path) + { + Write-Verbose "reporoot detect at: ./PowerShell" -Verbose + $repoRoot = './PowerShell' + } + } + if($repoRoot) { + $vstsCommandString = "vso[task.setvariable variable=repoRoot]$repoRoot" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + } else { + Write-Verbose -Verbose "repo not found" + } + displayName: 'Set repo Root' + env: + ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + +- powershell: | + $createJson = ("${{ parameters.CreateJson }}" -ne "no") + + $REPOROOT = $env:REPOROOT + + if (-not (Test-Path $REPOROOT/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" + } + } + + $releaseTag = & "$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' + env: + ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + +- powershell: | + Get-ChildItem -Path Env: | Out-String -Width 150 + displayName: Capture environment + condition: succeededOrFailed() + env: + ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue diff --git a/.pipelines/templates/approvalJob.yml b/.pipelines/templates/approvalJob.yml new file mode 100644 index 00000000000..ac3b8bc2ab2 --- /dev/null +++ b/.pipelines/templates/approvalJob.yml @@ -0,0 +1,36 @@ +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: + type: agentless + 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/.pipelines/templates/checkAzureContainer.yml b/.pipelines/templates/checkAzureContainer.yml new file mode 100644 index 00000000000..a6a86214d07 --- /dev/null +++ b/.pipelines/templates/checkAzureContainer.yml @@ -0,0 +1,85 @@ +jobs: +- job: DeleteBlob + variables: + - group: Azure Blob variable group + - group: AzureBlobServiceConnection + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT/BuildJson' + - name: ob_sdl_sbom_enabled + value: false + - name: ob_sdl_codeSignValidation_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 + - name: ob_sdl_codeql_compiled_enabled + value: false + + displayName: Delete blob is exists + pool: + type: windows + steps: + - checkout: self + clean: true + 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 + + - template: /.pipelines/templates/cloneToOfficialPath.yml@self + + - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self + parameters: + repoRoot: $(PowerShellRoot) + + - pwsh: | + if (-not (Test-Path -Path $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json)) { + Get-ChildItem -Path $(Build.SourcesDirectory) -Recurse + throw 'tsaoptions.json not found' + } + displayName: 'Check tsaoptions.json' + + - pwsh: | + if (-not (Test-Path -Path $(Build.SourcesDirectory)\PowerShell\.config\suppress.json)) { + Get-ChildItem -Path $(Build.SourcesDirectory) -Recurse + throw 'suppress.json not found' + } + displayName: 'Check suppress.json' + + - task: AzurePowerShell@5 + displayName: Check if blob exists and delete if specified + inputs: + azureSubscription: az-blob-cicd-infra + scriptType: inlineScript + azurePowerShellVersion: LatestVersion + pwsh: true + inline: | + $containersToDelete = @('$(AzureVersion)', '$(AzureVersion)-private', '$(AzureVersion)-nuget', '$(AzureVersion)-gc') + + $containersToDelete | ForEach-Object { + $containerName = $_ + try { + $container = Get-AzStorageContainer -Container $containerName -Context (New-AzStorageContext -StorageAccountName '$(StorageAccount)') -ErrorAction Stop + if ($container -ne $null -and '$(ForceAzureBlobDelete)' -eq 'false') { + throw "Azure blob container $containerName already exists. To overwrite, use ForceAzureBlobDelete parameter" + } + elseif ($container -ne $null -and '$(ForceAzureBlobDelete)' -eq 'true') { + Write-Verbose -Verbose "Removing container $containerName due to ForceAzureBlobDelete parameter" + Remove-AzStorageContainer -Name $containerName -Context (New-AzStorageContext -StorageAccountName '$(StorageAccount)') -Force + } + } + catch { + if ($_.FullyQualifiedErrorId -eq 'ResourceNotFoundException,Microsoft.WindowsAzure.Commands.Storage.Blob.Cmdlet.GetAzureStorageContainerCommand') { + Write-Verbose -Verbose "Container $containerName does not exists." + } + else { + throw $_ + } + } + } + - template: /.pipelines/templates/step/finalize.yml@self diff --git a/.pipelines/templates/cloneToOfficialPath.yml b/.pipelines/templates/cloneToOfficialPath.yml new file mode 100644 index 00000000000..844d8b8028d --- /dev/null +++ b/.pipelines/templates/cloneToOfficialPath.yml @@ -0,0 +1,19 @@ +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 + env: + ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase diff --git a/.pipelines/templates/compliance/apiscan.yml b/.pipelines/templates/compliance/apiscan.yml new file mode 100644 index 00000000000..17f07a597b5 --- /dev/null +++ b/.pipelines/templates/compliance/apiscan.yml @@ -0,0 +1,190 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +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 + - 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 + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: repoRoot + value: '$(Build.SourcesDirectory)\PowerShell' + - 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: Codeql.SourceRoot + value: $(repoRoot) + + pool: + type: windows + + # APIScan can take a long time + timeoutInMinutes: 180 + + steps: + - checkout: self + clean: true + fetchTags: true + fetchDepth: 1000 + displayName: Checkout PowerShell + retryCountOnTaskFailure: 1 + env: + ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase + + - template: ../SetVersionVariables.yml + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: yes + UseJson: no + + - template: ../insert-nuget-config-azfeed.yml + parameters: + repoRoot: '$(repoRoot)' + + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + useGlobalJson: true + packageType: 'sdk' + workingDirectory: $(Build.SourcesDirectory)" + + - 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 + workingDirectory: '$(repoRoot)' + retryCountOnTaskFailure: 2 + + - task: AzurePowerShell@5 + displayName: Download winverify-private Artifacts + inputs: + azureSubscription: az-blob-cicd-infra + scriptType: inlineScript + azurePowerShellVersion: LatestVersion + workingDirectory: '$(repoRoot)' + pwsh: true + inline: | + # download smybols for getfilesiginforedist.dll + $downloadsDirectory = '$(Build.ArtifactStagingDirectory)/downloads' + $uploadedDirectory = '$(Build.ArtifactStagingDirectory)/uploaded' + $storageAccountName = "pscoretestdata" + $containerName = 'winverify-private' + $winverifySymbolsPath = New-Item -ItemType Directory -Path '$(System.ArtifactsDirectory)/winverify-symbols' -Force + $dllName = 'getfilesiginforedist.dll' + $winverifySymbolsDllPath = Join-Path $winverifySymbolsPath $dllName + + $context = New-AzStorageContext -StorageAccountName $storageAccountName -UseConnectedAccount + + Get-AzStorageBlobContent -Container $containerName -Blob $dllName -Destination $winverifySymbolsDllPath -Context $context + + - pwsh: | + Get-ChildItem -Path '$(System.ArtifactsDirectory)/winverify-symbols' + displayName: Capture winverify-private Artifacts + workingDirectory: '$(repoRoot)' + condition: succeededOrFailed() + + - task: CodeQL3000Init@0 # Add CodeQL Init task right before your 'Build' step. + displayName: 🔏 CodeQL 3000 Init + condition: eq(variables['CODEQL_ENABLED'], 'true') + inputs: + Language: csharp + + - pwsh: | + Import-Module .\build.psm1 -force + Find-DotNet + Start-PSBuild -Configuration StaticAnalysis -PSModuleRestore -Clean -Runtime fxdependent-win-desktop + + $OutputFolder = Split-Path (Get-PSOutput) + + Write-Verbose -Verbose -Message "Deleting ref folder from output folder" + if (Test-Path $OutputFolder/ref) { + Remove-Item -Recurse -Force $OutputFolder/ref + } + + Copy-Item -Path "$OutputFolder\*" -Destination '$(ob_outputDirectory)' -Recurse -Verbose + workingDirectory: '$(repoRoot)' + displayName: 'Build PowerShell Source' + + - pwsh: | + # Only key windows runtimes + Get-ChildItem -Path '$(ob_outputDirectory)\runtimes\*' -File -Recurse | Where-Object {$_.FullName -notmatch '.*\/runtimes\/win'} | Foreach-Object { + Write-Verbose -Verbose -Message "Deleting $($_.FullName)" + Remove-Item -Force -Verbose -Path $_.FullName + } + + # Temporarily remove runtimes/win-x64 due to issues with that runtime + Get-ChildItem -Path '$(ob_outputDirectory)\runtimes\*' -File -Recurse | Where-Object {$_.FullName -match '.*\/runtimes\/win-x86\/'} | Foreach-Object { + Write-Verbose -Verbose -Message "Deleting $($_.FullName)" + Remove-Item -Force -Verbose -Path $_.FullName + } + + 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') + + - pwsh: | + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + workingDirectory: '$(repoRoot)' + 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:ob_outputDirectory\*" --recurse-subdirectories + displayName: 'Download Symbols for binaries' + retryCountOnTaskFailure: 2 + workingDirectory: '$(repoRoot)' + + - pwsh: | + Get-ChildItem '$(ob_outputDirectory)' -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' + workingDirectory: '$(repoRoot)' + displayName: 'Create release file hash artifact' + + - pwsh: | + Copy-Item -Path '$(Build.SourcesDirectory)/ReleaseFileHash.csv' -Destination '$(ob_outputDirectory)' -Verbose + displayName: 'Publish Build File Hash artifact' + + - pwsh: | + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture Environment + condition: succeededOrFailed() + workingDirectory: '$(repoRoot)' diff --git a/.pipelines/templates/compliance/generateNotice.yml b/.pipelines/templates/compliance/generateNotice.yml new file mode 100644 index 00000000000..7de316e8b49 --- /dev/null +++ b/.pipelines/templates/compliance/generateNotice.yml @@ -0,0 +1,121 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +parameters: + - name: parentJobs + type: jobList + - name: SkipVerifyPackages + type: boolean + +jobs: +- job: generateNotice + variables: + - name: runCodesignValidationInjection + value : false + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT/notice' + - name: ob_sdl_apiscan_enabled + value: false + - name: repoRoot + value: '$(Build.SourcesDirectory)\PowerShell' + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + + displayName: Generate Notice + dependsOn: + ${{ parameters.parentJobs }} + pool: + type: windows + + 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: '$(repoRoot)\tools' + + - pwsh: | + $(repoRoot)/tools/clearlyDefined/ClearlyDefined.ps1 -TestAndHarvest + displayName: Verify that packages have license data + condition: eq(${{ parameters.SkipVerifyPackages }}, false) + + - task: msospo.ospo-extension.8d7f9abb-6896-461d-9e25-4f74ed65ddb2.notice@0 + displayName: 'NOTICE File Generator' + inputs: + outputfile: '$(ob_outputDirectory)\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 $(repoRoot)\assets\additionalAttributions.txt | Out-File '$(ob_outputDirectory)\ThirdPartyNotices.txt' -Encoding utf8NoBOM -Force -Append + Get-Content -Raw -Path $(repoRoot)\assets\additionalAttributions.txt + displayName: Append Additional Attributions + continueOnError: true + + - pwsh: | + Get-Content -Raw -Path '$(ob_outputDirectory)\ThirdPartyNotices.txt' + displayName: Capture Notice + continueOnError: true + + - task: AzurePowerShell@5 + displayName: Upload Notice + inputs: + azureSubscription: az-blob-cicd-infra + scriptType: inlineScript + azurePowerShellVersion: LatestVersion + workingDirectory: '$(repoRoot)' + pwsh: true + inline: | + try { + $downloadsDirectory = '$(Build.ArtifactStagingDirectory)/downloads' + $uploadedDirectory = '$(Build.ArtifactStagingDirectory)/uploaded' + $storageAccountName = "pscoretestdata" + $containerName = '$(tpnContainer)' + $blobName = 'ThirdPartyNotices.txt' + $noticePath = "$(ob_outputDirectory)\$blobName" + + Write-Verbose -Verbose "creating context ($storageAccountName) ..." + $context = New-AzStorageContext -StorageAccountName $storageAccountName -UseConnectedAccount + + Write-Verbose -Verbose "checking if container ($containerName) exists ..." + $containerExists = Get-AzStorageContainer -Name $containerName -Context $context -ErrorAction SilentlyContinue + if (-not $containerExists) { + Write-Verbose -Verbose "Creating container ..." + $null = New-AzStorageContainer -Name $containerName -Context $context + Write-Verbose -Verbose "Blob container $containerName created successfully." + } + + Write-Verbose -Verbose "Setting blob ($blobName) content ($noticePath) ..." + $null = Set-AzStorageBlobContent -File $noticePath -Container $containerName -Blob $blobName -Context $context -confirm:$false -force + Write-Verbose -Verbose "Done" + } catch { + Get-Error + throw + } diff --git a/.pipelines/templates/insert-nuget-config-azfeed.yml b/.pipelines/templates/insert-nuget-config-azfeed.yml new file mode 100644 index 00000000000..20057440c00 --- /dev/null +++ b/.pipelines/templates/insert-nuget-config-azfeed.yml @@ -0,0 +1,53 @@ +parameters: +- name: "repoRoot" + default: $(REPOROOT) +- name: "ob_restore_phase" + type: boolean + default: true + +steps: +- task: NuGetAuthenticate@1 + displayName: Install Azure Artifacts Credential Provider + inputs: + forceReinstallCredentialProvider: true + +- pwsh: | + try { + $configPath = "${env:NugetConfigDir}/nuget.config" + Import-Module ${{ parameters.repoRoot }}/build.psm1 -Force + + Write-Verbose -Verbose "Running: Switch-PSNugetConfig -Source Private -UserName '$(AzDevopsFeedUserNameKVPAT)' -ClearTextPAT '$(powershellPackageReadPat)'" + Switch-PSNugetConfig -Source Private -UserName '$(AzDevopsFeedUserNameKVPAT)' -ClearTextPAT '$(powershellPackageReadPat)' + + if(-not (Test-Path $configPath)) + { + throw "nuget.config is not created" + } + } + catch { + Get-Error + throw + } + displayName: 'Switch to production Azure DevOps feed for all nuget.configs' + condition: and(succeededOrFailed(), ne(variables['UseAzDevOpsFeed'], '')) + env: + NugetConfigDir: ${{ parameters.repoRoot }}/src/Modules + ob_restore_phase: ${{ parameters.ob_restore_phase }} + +- pwsh: | + Get-ChildItem ${{ parameters.repoRoot }}/nuget.config -Recurse | Foreach-Object { + Write-Verbose -Verbose "--- START $($_.fullname) ---" + get-content $_.fullname | Out-String -width 9999 -Stream | write-Verbose -Verbose + Write-Verbose -Verbose "--- END $($_.fullname) ---" + } + displayName: 'Capture all nuget.config files' + condition: and(succeededOrFailed(), ne(variables['UseAzDevOpsFeed'], '')) + env: + ob_restore_phase: ${{ parameters.ob_restore_phase }} + +- pwsh: | + Get-ChildItem -Path env:VSS* | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture VSS* Environment + condition: and(succeededOrFailed(), ne(variables['UseAzDevOpsFeed'], '')) + env: + ob_restore_phase: ${{ parameters.ob_restore_phase }} diff --git a/.pipelines/templates/linux-package-build.yml b/.pipelines/templates/linux-package-build.yml new file mode 100644 index 00000000000..95996a597fe --- /dev/null +++ b/.pipelines/templates/linux-package-build.yml @@ -0,0 +1,185 @@ +parameters: + unsignedDrop: 'drop_linux_build_linux_x64' + signedeDrop: 'drop_linux_sign_linux_x64' + packageType: deb + jobName: 'deb' + signingProfile: 'CP-450779-pgpdetached' + +jobs: +- job: ${{ parameters.jobName }} + displayName: Package linux ${{ parameters.packageType }} + condition: succeeded() + pool: + type: linux + + variables: + - name: runCodesignValidationInjection + value: false + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - group: DotNetPrivateBuildAccess + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: ob_sdl_binskim_enabled + value: true + - name: PackageType + value: ${{ parameters.packageType }} + - name: signedDrop + value: ${{ parameters.signedDrop }} + - name: unsignedDrop + value: ${{ parameters.unsignedDrop }} + - 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: SigningProfile + value: ${{ parameters.signingProfile }} + + 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 + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: yes + UseJson: no + + - template: shouldSign.yml + + - template: cloneToOfficialPath.yml + parameters: + nativePathRoot: '$(Agent.TempDirectory)' + + - download: CoOrdinatedBuildPipeline + artifact: ${{ parameters.unsignedDrop }} + displayName: 'Download unsigned artifacts' + env: + ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + + - download: CoOrdinatedBuildPipeline + artifact: ${{ parameters.signedDrop }} + displayName: 'Download signed artifacts' + env: + ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + + - pwsh: | + Write-Verbose -Verbose "Unsigned artifacts" + Get-ChildItem "$(Pipeline.Workspace)/CoOrdinatedBuildPipeline/${{ parameters.unsignedDrop }}" -Recurse + + Write-Verbose -Verbose "Signed artifacts" + Get-ChildItem "$(Pipeline.Workspace)/CoOrdinatedBuildPipeline/${{ parameters.signedDrop }}" -Recurse + 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: | + $packageType = '$(PackageType)' + Write-Verbose -Verbose "packageType = $packageType" + + $signedDrop = '$(signedDrop)' + Write-Verbose -Verbose "signedDrop = $signedDrop" + + $unsignedDrop = '$(unsignedDrop)' + Write-Verbose -Verbose "unsignedDrop = $unsignedDrop" + + Write-Verbose -Message "Init..." -Verbose + + $repoRoot = "$env:REPOROOT" + Import-Module "$repoRoot/build.psm1" + Import-Module "$repoRoot/tools/packaging" + + Start-PSBootstrap -Scenario Package + + $psOptionsPath = "$(Pipeline.Workspace)/CoOrdinatedBuildPipeline/${unsignedDrop}/psoptions/psoptions.json" + + if (-not (Test-Path $psOptionsPath)) { + throw "psOptionsPath file not found at $psOptionsPath" + } + + Restore-PSOptions $psOptionsPath + Write-Verbose -Message "Restoring PSOptions from $psoptionsFilePath" -Verbose + Get-PSOptions | Write-Verbose -Verbose + + $signedFolder, $pkgFilter = switch ($packageType) { + 'tar-arm' { 'Signed-linux-arm', 'powershell*.tar.gz' } + 'tar-arm64' { 'Signed-linux-arm64', 'powershell*.tar.gz' } + 'tar-alpine' { 'Signed-linux-musl-x64', 'powershell*.tar.gz' } + 'fxdependent' { 'Signed-fxdependent', 'powershell*.tar.gz' } + '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' } + 'rpm-fxdependent' { 'Signed-fxdependent-linux-x64', 'powershell*.rpm' } + 'rpm-fxdependent-arm64' { 'Signed-fxdependent-linux-arm64', 'powershell*.rpm' } + 'rpm' { 'Signed-linux-x64', 'powershell*.rpm' } + 'min-size' { 'Signed-linux-x64', 'powershell*.tar.gz' } + } + + $signedFilesPath = "$(Pipeline.Workspace)/CoOrdinatedBuildPipeline/${signedDrop}/${signedFolder}" + Write-Verbose -Verbose "signedFilesPath: $signedFilesPath" + + Write-Verbose -Message "checking pwsh exists in $signedFilesPath" -Verbose + if (-not (Test-Path "$signedFilesPath/pwsh")) { + throw "pwsh not found in $signedFilesPath" + } + + $metadata = Get-Content "$repoRoot/tools/metadata.json" -Raw | ConvertFrom-Json + $LTS = $metadata.LTSRelease.Package + + if ($LTS) { + Write-Verbose -Message "LTS Release: $LTS" + } + + if (-not (Test-Path $(ob_outputDirectory))) { + New-Item -ItemType Directory -Path $(ob_outputDirectory) -Force + } + + $packageType = '$(PackageType)' + Write-Verbose -Verbose "packageType = $packageType" + + Start-PSPackage -Type $packageType -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath + + $vstsCommandString = "vso[task.setvariable variable=PackageFilter]$pkgFilter" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + displayName: 'Package ${{ parameters.packageType}}' + 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 deb and rpm packages + inputs: + command: 'sign' + signing_profile: '$(SigningProfile)' + files_to_sign: '**/*.rpm;**/*.deb' + search_root: '$(Pipeline.Workspace)' + + - pwsh: | + $pkgFilter = '$(PackageFilter)' + Write-Verbose -Verbose "pkgFilter: $pkgFilter" + + $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 + displayName: 'Copy artifacts to output directory' + env: + __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) + + - pwsh: | + Get-ChildItem -Path $(ob_outputDirectory) -Recurse + displayName: 'List artifacts' diff --git a/.pipelines/templates/linux.yml b/.pipelines/templates/linux.yml new file mode 100644 index 00000000000..398e8fe5fef --- /dev/null +++ b/.pipelines/templates/linux.yml @@ -0,0 +1,205 @@ +parameters: + Runtime: 'linux-x64' + BuildConfiguration: 'release' + JobName: 'build_linux' + +jobs: +- job: build_${{ parameters.JobName }} + displayName: Build_Linux_${{ parameters.Runtime }}_${{ parameters.BuildConfiguration }} + condition: succeeded() + pool: + type: linux + variables: + - name: runCodesignValidationInjection + value: false + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE + value: 1 + - group: DotNetPrivateBuildAccess + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: ob_sdl_codeSignValidation_enabled + value: false + - 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: BUILDCONFIGURATION + value: ${{ parameters.BuildConfiguration }} + - name: Runtime + value: ${{ parameters.Runtime }} + - name: ob_sdl_sbom_packageName + value: 'Microsoft.Powershell.Linux.${{ parameters.Runtime }}' + # We add this manually, so we need it disabled the OneBranch auto-injected one. + - name: ob_sdl_codeql_compiled_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 + + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + + - template: /.pipelines/templates/cloneToOfficialPath.yml@self + + - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self + parameters: + repoRoot: $(PowerShellRoot) + + - task: CodeQL3000Init@0 # Add CodeQL Init task right before your 'Build' step. + condition: eq(variables['CODEQL_ENABLED'], 'true') + 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 + + - task: UseDotNet@2 + inputs: + useGlobalJson: true + workingDirectory: $(PowerShellRoot) + env: + ob_restore_phase: true + + - pwsh: | + $runtime = $env:RUNTIME + + $params = @{} + if ($env:BUILDCONFIGURATION -eq 'minSize') { + Write-Verbose -Message "Building for minimal size" + $params['ForMinimalSize'] = $true + } + + Write-Verbose -Message "Building PowerShell with Runtime: $runtime" + Import-Module -Name $(PowerShellRoot)/build.psm1 -Force + $buildWithSymbolsPath = New-Item -ItemType Directory -Path $(Pipeline.Workspace)/Symbols_$(Runtime) -Force + + $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 @params -Clean -PSModuleRestore @ReleaseTagParam + + $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) { + Write-Error -Message "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)" + } + } + + Write-Verbose -Verbose "Completed building PowerShell for '$env:BUILDCONFIGURATION' configuration" + displayName: 'Build Linux - $(Runtime)' + 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. + condition: eq(variables['CODEQL_ENABLED'], 'true') + env: + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + + - pwsh: | + $platform = 'linux' + $vstsCommandString = "vso[task.setvariable variable=ArtifactPlatform]$platform" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + displayName: Set artifact platform + + - pwsh: | + $pathForUpload = New-Item -ItemType Directory -Path '$(ob_outputDirectory)/Unsigned-$(Runtime)' -Force + Write-Verbose -Verbose -Message "pathForUpload: $pathForUpload" + Copy-Item -Path '$(Pipeline.Workspace)/Symbols_$(Runtime)/*' -Destination $pathForUpload -Recurse -Force -Verbose + displayName: Copy unsigned files for upload + + - template: /.pipelines/templates/step/finalize.yml@self + +- job: sign_${{ parameters.JobName }} + displayName: Sign_Linux_${{ parameters.Runtime }}_${{ parameters.BuildConfiguration }} + condition: succeeded() + dependsOn: build_${{ parameters.JobName }} + pool: + type: windows + variables: + - name: runCodesignValidationInjection + value: false + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE + value: 1 + - group: DotNetPrivateBuildAccess + - group: certificate_logical_to_actual + - 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 + - name: BuildConfiguration + value: ${{ parameters.BuildConfiguration }} + - name: Runtime + value: ${{ parameters.Runtime }} + - name: ob_sdl_codeql_compiled_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 + + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + + - template: /.pipelines/templates/cloneToOfficialPath.yml@self + + - task: DownloadPipelineArtifact@2 + inputs: + artifact: drop_linux_build_${{ parameters.JobName }} + path: $(Pipeline.Workspace)/drop_linux_build + displayName: Download build + + - pwsh: | + Get-ChildItem -Path $(Pipeline.Workspace)/drop_linux_build -Recurse + displayName: Capture downloaded files + + - pwsh: | + $pwshPath = Get-ChildItem -Path $(Pipeline.Workspace)/drop_linux_build -File -Recurse | Where-Object { $_.Name -eq 'pwsh' } + $rootPath = Split-Path -Path $pwshPath.FullName -Parent + Write-Verbose -Verbose "Setting vso[task.setvariable variable=DropRootPath]$rootPath" + Write-Host "##vso[task.setvariable variable=DropRootPath]$rootPath" + displayName: Set drop root path + + - template: /.pipelines/templates/obp-file-signing.yml@self + parameters: + binPath: $(DropRootPath) + + - template: /.pipelines/templates/step/finalize.yml@self diff --git a/.pipelines/templates/mac-package-build.yml b/.pipelines/templates/mac-package-build.yml new file mode 100644 index 00000000000..669ab3c8437 --- /dev/null +++ b/.pipelines/templates/mac-package-build.yml @@ -0,0 +1,215 @@ +parameters: + parentJob: '' + buildArchitecture: x64 + +jobs: +- job: package_macOS_${{ parameters.buildArchitecture }} + displayName: Package macOS ${{ parameters.buildArchitecture }} + condition: succeeded() + pool: + type: linux + isCustom: true + name: Azure Pipelines + vmImage: 'macOS-latest' + + variables: + - name: HOMEBREW_NO_ANALYTICS + value: 1 + - name: runCodesignValidationInjection + value: false + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - group: DotNetPrivateBuildAccess + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: ob_sdl_binskim_enabled + value: true + - name: ob_sdl_credscan_suppressionsfileforartifacts + value: $(Build.SourcesDirectory)/PowerShell/.config/suppress.json + - name: BuildArch + value: ${{ parameters.buildArchitecture }} + + steps: + - checkout: self + clean: true + + - pwsh: | + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture environment + + - 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@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: yes + UseJson: no + + - template: shouldSign.yml + + - template: cloneToOfficialPath.yml + parameters: + nativePathRoot: '$(Agent.TempDirectory)' + + - download: CoOrdinatedBuildPipeline + artifact: macosBinResults-${{ parameters.buildArchitecture }} + + - download: CoOrdinatedBuildPipeline + artifact: drop_macos_sign_${{ parameters.buildArchitecture }} + + - pwsh: | + Write-Verbose -Verbose "unsigned artifacts" + Get-ChildItem "$(Pipeline.Workspace)/CoOrdinatedBuildPipeline/macosBinResults-${{ parameters.buildArchitecture }}" -Recurse + + Write-Verbose -Verbose "unsigned artifacts" + Get-ChildItem "$(Pipeline.Workspace)/CoOrdinatedBuildPipeline/drop_macos_sign_${{ parameters.buildArchitecture }}" -Recurse + displayName: 'Capture Downloaded Artifacts' + # Diagnostics is not critical it passes every time it runs + continueOnError: true + + - pwsh: | + # Add -SkipReleaseChecks as a mitigation to unblock release. + # macos-10.15 does not allow creating a folder under root. Hence, moving the folder. + + $buildArch = '${{ parameters.buildArchitecture }}' + + Write-Verbose -Message "Init..." -Verbose + $repoRoot = $env:REPOROOT + Set-Location $repoRoot + Import-Module "$repoRoot/build.psm1" + Import-Module "$repoRoot/tools/packaging" + + $unsignedFilesPath = "$(Pipeline.Workspace)/CoOrdinatedBuildPipeline/macosBinResults-$buildArch" + $signedFilesPath = "$(Pipeline.Workspace)/CoOrdinatedBuildPipeline/drop_macos_sign_$buildArch/Signed-$buildArch" + + Write-Verbose -Message "checking pwsh exists in $signedFilesPath" -Verbose + if (-not (Test-Path $signedFilesPath/pwsh)) { + throw "pwsh not found in $signedFilesPath" + } + + $psoptionsPath = Get-ChildItem -Path $unsignedFilesPath -Filter 'psoptions.json' -Recurse -File | Select-Object -ExpandProperty FullName + Write-Verbose -Message "Restoring PSOptions from $psoptionsPath" -Verbose + + Restore-PSOptions -PSOptionsPath "$psoptionsPath" + Get-PSOptions | Write-Verbose -Verbose + + $metadata = Get-Content "$repoRoot/tools/metadata.json" -Raw | ConvertFrom-Json + $LTS = $metadata.LTSRelease.Package + + if ($LTS) { + Write-Verbose -Message "LTS Release: $LTS" + } + + Start-PSBootstrap -Scenario Package + + $macosRuntime = "osx-$buildArch" + + Start-PSPackage -Type osxpkg -SkipReleaseChecks -MacOSRuntime $macosRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS:$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" + + 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" + + displayName: 'Package ${{ parameters.buildArchitecture}}' + env: + __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) + +- job: sign_package_macOS_${{ parameters.buildArchitecture }} + displayName: Sign Package macOS ${{ parameters.buildArchitecture }} + dependsOn: package_macOS_${{ parameters.buildArchitecture }} + condition: succeeded() + pool: + type: windows + + variables: + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: ob_sdl_binskim_enabled + value: true + - name: ob_sdl_credscan_suppressionsfileforartifacts + value: $(Build.SourcesDirectory)/PowerShell/.config/suppress.json + - name: BuildArch + value: ${{ parameters.buildArchitecture }} + - group: mscodehub-macos-package-signing + + steps: + - download: current + artifact: macos-pkgs + + - pwsh: | + $buildArch = '${{ parameters.buildArchitecture }}' + $macosRuntime = "osx-$buildArch" + $pkgNameFilter = "powershell-*$macosRuntime.pkg" + $pkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $pkgNameFilter -Recurse -File + + if ($pkgPath.Count -eq 0) { + throw "No package found for $macosRuntime" + } + + foreach($p in $pkgPath) { + $file = $p.FullName + $fileName = $p.BaseName + Write-Verbose -verbose "Compressing $file" + $zipFile = "$(Pipeline.Workspace)\${fileName}.zip" + Write-Verbose -Verbose "Zip file: $zipFile" + Compress-Archive -Path $file -Destination $zipFile + } + + Write-Verbose -Verbose "Compressed files:" + Get-ChildItem -Path $(Pipeline.Workspace) -Filter "*.zip" -File | Write-Verbose -Verbose + displayName: Compress package files for signing + + - task: onebranch.pipeline.signing@1 + displayName: 'OneBranch CodeSigning Package' + inputs: + command: 'sign' + files_to_sign: '**/*-osx-*.zip' + search_root: '$(Pipeline.Workspace)' + inline_operation: | + [ + { + "KeyCode": "$(KeyCode)", + "OperationCode": "MacAppDeveloperSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": { + "Hardening": "Enable", + "OpusInfo": "http://microsoft.com" + } + } + ] + + - pwsh: | + $signedPkg = Get-ChildItem -Path $(Pipeline.Workspace) -Filter "*osx*.zip" -File + + $signedPkg | ForEach-Object { + Write-Verbose -Verbose "Signed package zip: $_" + + if (-not (Test-Path $_)) { + throw "Package not found: $_" + } + + if (-not (Test-Path $(ob_outputDirectory))) { + $null = New-Item -Path $(ob_outputDirectory) -ItemType Directory + } + + Expand-Archive -Path $_ -DestinationPath $(ob_outputDirectory) -Verbose + } + + Write-Verbose -Verbose "Expanded pkg file:" + Get-ChildItem -Path $(ob_outputDirectory) | Write-Verbose -Verbose + displayName: Expand signed file diff --git a/.pipelines/templates/mac.yml b/.pipelines/templates/mac.yml new file mode 100644 index 00000000000..310c5695979 --- /dev/null +++ b/.pipelines/templates/mac.yml @@ -0,0 +1,152 @@ +parameters: + buildArchitecture: 'x64' +jobs: +- job: build_macOS_${{ parameters.buildArchitecture }} + displayName: Build macOS ${{ parameters.buildArchitecture }} + condition: succeeded() + pool: + type: linux + isCustom: true + name: Azure Pipelines + vmImage: 'macOS-latest' + + variables: + - name: HOMEBREW_NO_ANALYTICS + value: 1 + - name: runCodesignValidationInjection + value: false + - name: NugetSecurityAnalysisWarningLevel + value: none + - group: DotNetPrivateBuildAccess + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: PowerShellRoot + value: $(Build.SourcesDirectory) + + steps: + - checkout: self + clean: true + 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) + - 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' + + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + useGlobalJson: true + packageType: 'sdk' + workingDirectory: $(PowerShellRoot) + + - pwsh: | + Import-Module $(PowerShellRoot)/build.psm1 -Force + Start-PSBootstrap -Scenario Package + displayName: 'Bootstrap VM' + env: + __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) + + - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self + 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. + + Import-Module ./build.psm1 -Force + + $ReleaseTagParam = @{} + + if ($env:RELEASETAGVAR) { + $ReleaseTagParam['ReleaseTag'] = $env:RELEASETAGVAR + } + + Start-PSBuild -Runtime 'osx-${{ parameters.buildArchitecture }}' -Configuration Release -PSModuleRestore -Clean -Output $(OB_OUTPUTDIRECTORY) @ReleaseTagParam + $artifactName = "macosBinResults-${{ parameters.buildArchitecture }}" + + $psOptPath = "$(OB_OUTPUTDIRECTORY)/psoptions.json" + Save-PSOptions -PSOptionsPath $psOptPath + + # 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)" + + $env:AzDevOpsFeedPAT2 = $null + displayName: 'Build' + env: + __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) + + - template: /.pipelines/templates/step/finalize.yml@self + +- job: sign_${{ parameters.buildArchitecture }} + displayName: Sign_macOS_${{ parameters.buildArchitecture }} + condition: succeeded() + dependsOn: build_macOS_${{ parameters.buildArchitecture }} + pool: + type: windows + variables: + - name: NugetSecurityAnalysisWarningLevel + value: none + - group: DotNetPrivateBuildAccess + - group: certificate_logical_to_actual + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: ob_sdl_codeSignValidation_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: BuildArchitecture + value: ${{ parameters.buildArchitecture }} + - name: ob_sdl_codeql_compiled_enabled + value: false + - name: ob_sdl_sbom_packageName + value: 'Microsoft.Powershell.MacOS.${{parameters.buildArchitecture}}' + + steps: + - checkout: self + clean: true + 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) + + - template: /.pipelines/templates/cloneToOfficialPath.yml@self + + - task: DownloadPipelineArtifact@2 + inputs: + artifact: 'macosBinResults-$(BuildArchitecture)' + path: '$(Pipeline.Workspace)\Symbols' + displayName: Download build + + - pwsh: | + Get-ChildItem "$(Pipeline.Workspace)\*" -Recurse + displayName: 'Capture Downloaded Artifacts' + # Diagnostics is not critical it passes every time it runs + continueOnError: true + + - pwsh: | + $runtime = '$(BuildArchitecture)' + Write-Host "sending.. vso[task.setvariable variable=Runtime]$runtime" + Write-Host "##vso[task.setvariable variable=Runtime]$runtime" + + $rootPath = "$(Pipeline.Workspace)\Symbols" + Write-Verbose -Verbose "Setting vso[task.setvariable variable=DropRootPath]$rootPath" + Write-Host "##vso[task.setvariable variable=DropRootPath]$rootPath" + displayName: Expand symbols zip + + - template: /.pipelines/templates/obp-file-signing.yml@self + parameters: + binPath: $(DropRootPath) + + - template: /.pipelines/templates/step/finalize.yml@self diff --git a/.pipelines/templates/nupkg.yml b/.pipelines/templates/nupkg.yml new file mode 100644 index 00000000000..dc43e841332 --- /dev/null +++ b/.pipelines/templates/nupkg.yml @@ -0,0 +1,309 @@ +jobs: +- job: build_nupkg + displayName: Package NuPkgs + condition: succeeded() + pool: + type: windows + + variables: + - name: runCodesignValidationInjection + value: false + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - 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 + - group: mscodehub-feed-read-general + - group: mscodehub-feed-read-akv + - group: DotNetPrivateBuildAccess + + 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 + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: yes + UseJson: no + + - template: shouldSign.yml + + - template: cloneToOfficialPath.yml + parameters: + nativePathRoot: '$(Agent.TempDirectory)' + + - download: CoOrdinatedBuildPipeline + artifact: drop_windows_build_windows_fxdependent_release + displayName: 'Download drop_windows_build_windows_fxdependent_release' + env: + ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + + - download: CoOrdinatedBuildPipeline + artifact: drop_windows_build_windows_fxdependentWinDesktop_release + displayName: 'Download drop_windows_build_windows_fxdependentWinDesktop_release' + env: + ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + + - download: CoOrdinatedBuildPipeline + artifact: drop_linux_sign_linux_fxd + displayName: 'Download drop_linux_sign_linux_fxd' + env: + ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + + - download: CoOrdinatedBuildPipeline + artifact: drop_linux_sign_linux_fxd_x64_alpine + displayName: 'Download drop_linux_sign_linux_fxd_x64_alpine' + env: + ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + + - pwsh: | + Write-Verbose -Verbose "drop_windows_build_windows_fxdependent_release" + Get-ChildItem -Path $(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_windows_build_windows_fxdependent_release -Recurse | Out-String | Write-Verbose -Verbose + + Write-Verbose -Verbose "drop_windows_build_windows_fxdependentWinDesktop_release" + Get-ChildItem -Path $(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_windows_build_windows_fxdependentWinDesktop_release -Recurse | Out-String | Write-Verbose -Verbose + + Write-Verbose -Verbose "drop_linux_sign_linux_fxd" + Get-ChildItem -Path $(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_linux_sign_linux_fxd -Recurse | Out-String | Write-Verbose -Verbose + + Write-Verbose -Verbose "drop_linux_sign_linux_fxd_x64_alpine" + Get-ChildItem -Path $(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_linux_sign_linux_fxd_x64_alpine -Recurse | Out-String | Write-Verbose -Verbose + displayName: 'Capture download artifacts' + env: + ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + + - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self + parameters: + repoRoot: $(PowerShellRoot) + + - task: NuGetToolInstaller@1 + displayName: 'Install NuGet.exe' + + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + useGlobalJson: true + packageType: 'sdk' + workingDirectory: '$(PowerShellRoot)' + + - pwsh: | + Set-Location -Path '$(PowerShellRoot)' + Import-Module "$(PowerShellRoot)/build.psm1" -Force + + $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 -ReleaseTag $(ReleaseTagVar) + + $sharedModules | Foreach-Object { + $refFile = Get-ChildItem -Path "$(PowerShellRoot)\src\$_\obj\Release\net10.0\refint\$_.dll" + Write-Verbose -Verbose "RefAssembly: $refFile" + Copy-Item -Path $refFile -Destination "$refAssemblyFolder\$_.dll" -Verbose + $refDoc = "$(PowerShellRoot)\src\$_\bin\Release\net10.0\$_.xml" + if (-not (Test-Path $refDoc)) { + Write-Warning "$refDoc not found" + Get-ChildItem -Path "$(PowerShellRoot)\src\$_\bin\Release\net10.0\" | Out-String | Write-Verbose -Verbose + } + else { + Copy-Item -Path $refDoc -Destination "$refAssemblyFolder\$_.xml" -Verbose + } + } + + Start-PSBuild -Clean -Runtime win7-x64 -Configuration Release -ReleaseTag $(ReleaseTagVar) + + $winOnlyModules | Foreach-Object { + $refFile = Get-ChildItem -Path "$(PowerShellRoot)\src\$_\obj\Release\net10.0\refint\*.dll" + Write-Verbose -Verbose 'RefAssembly: $refFile' + Copy-Item -Path $refFile -Destination "$refAssemblyFolder\$_.dll" -Verbose + $refDoc = "$(PowerShellRoot)\src\$_\bin\Release\net10.0\$_.xml" + if (-not (Test-Path $refDoc)) { + Write-Warning "$refDoc not found" + Get-ChildItem -Path "$(PowerShellRoot)\src\$_\bin\Release\net10.0" | Out-String | Write-Verbose -Verbose + } + 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_KEY: $(RUNTIME_SOURCEFEED_KEY) + + - task: onebranch.pipeline.signing@1 + displayName: Sign ref assemblies + inputs: + command: 'sign' + signing_profile: external_distribution + files_to_sign: '**\*.dll' + search_root: '$(System.ArtifactsDirectory)\RefAssembly' + + - pwsh: | + $files = @( + "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" + ) + + Import-Module -Name '$(PowerShellRoot)\build.psm1' + Import-Module -Name '$(PowerShellRoot)\tools\packaging' + Find-DotNet + + Write-Verbose -Verbose "Version == $(Version)" + + $winFxdPath = "$(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_windows_build_windows_fxdependent_release\Signed-fxdependent" + Write-Verbose -Verbose "winFxdPath == $winFxdPath" + + $linuxFxdPath = "$(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_linux_sign_linux_fxd\Signed-fxdependent" + Write-Verbose -Verbose "linuxFxdPath == $linuxFxdPath" + + $nupkgOutputPath = Join-Path -Path '$(Pipeline.Workspace)' -ChildPath 'nupkg' + New-Item -Path $nupkgOutputPath -ItemType Directory -Force + + $files | Foreach-Object { + $FileBaseName = [System.IO.Path]::GetFileNameWithoutExtension($_) + $FilePackagePath = Join-Path -Path $nupkgOutputPath -ChildPath $FileBaseName + Write-Verbose -Verbose "FileName to package: $_" + Write-Verbose -Verbose "FilePackage path: $FilePackagePath" + New-ILNugetPackageSource -File $_ -PackagePath $FilePackagePath -PackageVersion '$(Version)' -WinFxdBinPath $winFxdPath -LinuxFxdBinPath $linuxFxdPath -RefAssemblyPath $(RefAssemblyPath) + New-ILNugetPackageFromSource -FileName $_ -PackageVersion '$(Version)' -PackagePath $FilePackagePath + } + displayName: 'Create NuGet Package for single file' + + - task: onebranch.pipeline.signing@1 + displayName: Sign nupkg files + inputs: + command: 'sign' + cp_code: 'CP-401405' + files_to_sign: '**\*.nupkg' + search_root: '$(Pipeline.Workspace)\nupkg' + + ### Create global tools + + - pwsh: | + $winFxdPath = "$(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_windows_build_windows_fxdependent_release\Signed-fxdependent" + $winDesktopFxdPath = "$(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_windows_build_windows_fxdependentWinDesktop_release\Signed-fxdependent-win-desktop" + $linuxFxdPath = "$(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_linux_sign_linux_fxd\Signed-fxdependent" + $alpineFxdPath = "$(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_linux_sign_linux_fxd_x64_alpine\Signed-fxdependent-noopt-linux-musl-x64" + + Import-Module -Name '$(PowerShellRoot)\build.psm1' + Import-Module -Name '$(PowerShellRoot)\tools\packaging' + + Start-PrepForGlobalToolNupkg -LinuxBinPath $linuxFxdPath -WindowsBinPath $winFxdPath -WindowsDesktopBinPath $winDesktopFxdPath -AlpineBinPath $alpineFxdPath + displayName: 'Prepare for global tool packages' + + - pwsh: | + Import-Module -Name '$(PowerShellRoot)\build.psm1' + Import-Module -Name '$(PowerShellRoot)\tools\packaging' + Find-DotNet + + $gblToolOutputPath = Join-Path -Path '$(Pipeline.Workspace)' -ChildPath 'globaltools' + New-Item -Path $gblToolOutputPath -ItemType Directory -Force + + $winFxdPath = "$(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_windows_build_windows_fxdependent_release\Signed-fxdependent" + $winDesktopFxdPath = "$(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_windows_build_windows_fxdependentWinDesktop_release\Signed-fxdependent-win-desktop" + $linuxFxdPath = "$(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_linux_sign_linux_fxd\Signed-fxdependent" + $alpineFxdPath = "$(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_linux_sign_linux_fxd_x64_alpine\Signed-fxdependent-noopt-linux-musl-x64" + + # Build global tools which do not have the shims exe generated in build. + $packageTypes = @('Unified', 'PowerShell.Linux.Alpine', 'PowerShell.Linux.x64', 'PowerShell.Linux.arm32', 'PowerShell.Linux.arm64') + + $packageTypes | Foreach-Object { + $PackageType = $_ + Write-Verbose -Verbose "PackageType: $PackageType" + + New-GlobalToolNupkgSource -PackageType $PackageType -PackageVersion '$(Version)' -LinuxBinPath $linuxFxdPath -WindowsBinPath $winFxdPath -WindowsDesktopBinPath $winDesktopFxdPath -AlpineBinPath $alpineFxdPath -SkipCGManifest + + Write-Verbose -Verbose "GlobalToolNuspecSourcePath = $global:GlobalToolNuSpecSourcePath" + Write-Verbose -Verbose "GlobalToolPkgName = $global:GlobalToolPkgName" + + Write-Verbose -Verbose "Starting global tool package creation for $PackageType" + New-GlobalToolNupkgFromSource -PackageNuSpecPath "$global:GlobalToolNuSpecSourcePath" -PackageName "$global:GlobalToolPkgName" -DestinationPath $gblToolOutputPath + Write-Verbose -Verbose "Global tool package created for $PackageType" + $global:GlobalToolNuSpecSourcePath = $null + $global:GlobalToolPkgName = $null + } + displayName: 'Create global tools' + + - pwsh: | + $gblToolOutputPath = Join-Path -Path '$(Pipeline.Workspace)' -ChildPath 'globaltools' + Get-ChildItem -Path $gblToolOutputPath + displayName: Capture global tools + + - task: onebranch.pipeline.signing@1 + displayName: Sign nupkg files + inputs: + command: 'sign' + cp_code: 'CP-401405' + files_to_sign: '**\*.nupkg' + search_root: '$(Pipeline.Workspace)\globaltools' + + - pwsh: | + if (-not (Test-Path '$(ob_outputDirectory)')) { + New-Item -ItemType Directory -Path '$(ob_outputDirectory)' -Force + } + + Write-Verbose -Verbose "Copying nupkgs to output directory" + $nupkgOutputPath = Join-Path -Path '$(Pipeline.Workspace)' -ChildPath 'nupkg' + Get-ChildItem -Path $nupkgOutputPath -Filter *.nupkg -Recurse | Copy-Item -Destination '$(ob_outputDirectory)' -Force -Verbose + + # Copy Windows.x86 global tool from build to output directory + $winX64GlobalTool = "$(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_windows_build_windows_fxdependent_release\globaltool\powershell*.nupkg" + Write-Verbose -Verbose "Finding Windows.x64 global tool at $winX64GlobalTool" + $globalToolPath = Get-Item $winX64GlobalTool + Copy-Item -Path $globalToolPath -Destination '$(ob_outputDirectory)' -Force -Verbose + + Write-Verbose -Verbose "Copying global tools to output directory" + $gblToolOutputPath = Join-Path -Path '$(Pipeline.Workspace)' -ChildPath 'globaltools' + Get-ChildItem -Path $gblToolOutputPath -Filter *.nupkg -Recurse | Copy-Item -Destination '$(ob_outputDirectory)' -Force -Verbose + displayName: Copy artifacts to output directory + + - pwsh: | + $nupkgOutputPath = '$(ob_outputDirectory)' + Get-ChildItem -Path $nupkgOutputPath | Out-String | Write-Verbose -Verbose + displayName: List artifacts diff --git a/.pipelines/templates/obp-file-signing.yml b/.pipelines/templates/obp-file-signing.yml new file mode 100644 index 00000000000..b6683d3caaf --- /dev/null +++ b/.pipelines/templates/obp-file-signing.yml @@ -0,0 +1,160 @@ +parameters: + binPath: '$(ob_outputDirectory)' + globalTool: 'false' + +steps: +- pwsh: | + $fullSymbolsFolder = '${{ parameters.binPath }}' + Write-Verbose -Verbose "fullSymbolsFolder == $fullSymbolsFolder" + Get-ChildItem -Recurse $fullSymbolsFolder | Select-Object -ExpandProperty FullName | Write-Verbose -Verbose + $filesToSignDirectory = "$(Pipeline.Workspace)/toBeSigned" + if ((Test-Path -Path $filesToSignDirectory)) { + Remove-Item -Path $filesToSignDirectory -Recurse -Force + } + $null = New-Item -ItemType Directory -Path $filesToSignDirectory -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" + } + } + + $itemsToExclude = @( + # This package is retrieved from https://www.github.com/powershell/MarkdownRender + "$($fullSymbolsFolder)/Microsoft.PowerShell.MarkdownRender.dll" + ) + + if('$(ArtifactPlatform)' -eq 'linux' -or '$(ArtifactPlatform)' -eq 'macos') { + $itemsToExclude += "$($fullSymbolsFolder)/pwsh" + } + + Write-Verbose -verbose "recursively copying $($itemsToCopyWithRecurse | out-string) to $filesToSignDirectory" + Copy-Item -Path $itemsToCopyWithRecurse -Destination $filesToSignDirectory -Recurse -verbose -exclude $itemsToExclude + Write-Verbose -verbose "recursive copy done." + + 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" + + if (-not (Test-Path -Path $pattern)) { + Write-Verbose -verbose "No files found for pattern $pattern" + continue + } + + Copy-Item -Path $pattern -Destination $destinationFolder -Recurse -verbose + } + + Write-Verbose -verbose "copying done." + Write-Verbose -verbose "Files to be signed at: $filesToSignDirectory" + + Get-ChildItem -Recurse -File $filesToSignDirectory | Select-Object -Property FullName + displayName: 'Prepare files to be signed' + +- task: onebranch.pipeline.signing@1 + displayName: Sign 1st party files + inputs: + command: 'sign' + signing_profile: external_distribution + files_to_sign: '**\*.psd1;**\*.psm1;**\*.ps1xml;**\*.ps1;**\*.dll;**\*.exe;**\pwsh' + search_root: $(Pipeline.Workspace)/toBeSigned + +- pwsh : | + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture environment + +- pwsh: | + Import-Module $(PowerShellRoot)/build.psm1 -Force + Import-Module $(PowerShellRoot)/tools/packaging -Force + + $BuildPath = (Get-Item '${{ parameters.binPath }}').FullName + Write-Verbose -Verbose -Message "BuildPath: $BuildPath" + + ## copy all files to be signed to build folder + Update-PSSignedBuildFolder -BuildPath $BuildPath -SignedFilesPath '$(Pipeline.Workspace)/toBeSigned' + + $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 = "$(Pipeline.Workspace)/thirdPartyToBeSigned" + if (Test-Path $filesToSignDirectory) { + Remove-Item -Path $filesToSignDirectory -Recurse -Force + } + $null = New-Item -ItemType Directory -Path $filesToSignDirectory -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 + +- task: onebranch.pipeline.signing@1 + displayName: Sign 3rd Party files + inputs: + command: 'sign' + signing_profile: $(msft_3rd_party_cert_id) + files_to_sign: '**\*.dll;**\*.exe' + search_root: $(Pipeline.Workspace)/thirdPartyToBeSigned + +- pwsh: | + Get-ChildItem '$(Pipeline.Workspace)/thirdPartyToBeSigned/*' + displayName: Capture ThirdParty Signed files + +- pwsh: | + Import-Module '$(PowerShellRoot)/build.psm1' -Force + Import-Module '$(PowerShellRoot)/tools/packaging' -Force + $isGlobalTool = '${{ parameters.globalTool }}' -eq 'true' + + if (-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 + Write-Verbose -Verbose -Message "Files copied to $pathForUpload" + } + else { + $pathForUpload = '${{ parameters.binPath }}' + } + + Write-Verbose "Copying third party signed files to the build folder" + $thirdPartySignedFilesPath = (Get-Item '$(Pipeline.Workspace)/thirdPartyToBeSigned').FullName + Update-PSSignedBuildFolder -BuildPath $pathForUpload -SignedFilesPath $thirdPartySignedFilesPath + + displayName: 'Copy signed files for upload' + +- template: /.pipelines/templates/step/finalize.yml@self diff --git a/.pipelines/templates/package-create-msix.yml b/.pipelines/templates/package-create-msix.yml new file mode 100644 index 00000000000..0ab2408e6a7 --- /dev/null +++ b/.pipelines/templates/package-create-msix.yml @@ -0,0 +1,110 @@ +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 + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_windows_package_package_win_arm64 + itemPattern: | + **/*.msix + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download windows arm64 packages + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_windows_package_package_win_x64 + itemPattern: | + **/*.msix + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download windows x64 packages + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_windows_package_package_win_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 + } + + $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: 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" + + if (-not (Test-Path $(ob_outputDirectory))) { + New-Item -ItemType Directory -Path $(ob_outputDirectory) -Force + } + + Copy-Item -Path $signedBundle.FullName -Destination "$(ob_outputDirectory)" -Verbose + + Write-Verbose -Verbose "Uploaded Bundle:" + Get-ChildItem -Path $(ob_outputDirectory) | Write-Verbose -Verbose + displayName: Upload msixbundle to Artifacts diff --git a/.pipelines/templates/release-MakeBlobPublic.yml b/.pipelines/templates/release-MakeBlobPublic.yml new file mode 100644 index 00000000000..c8f12938d25 --- /dev/null +++ b/.pipelines/templates/release-MakeBlobPublic.yml @@ -0,0 +1,179 @@ +parameters: + - name: SkipPSInfraInstallers + displayName: Skip Copying Archives and Installers to PSInfrastructure Public Location + type: boolean + default: false + +jobs: +- template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Approve Copy release packages to PSInfra storage + jobName: CopyReleaseBlobApproval + instructions: | + Approval for Copy release packages to PSInfra storage + +- job: PSInfraReleaseBlobPublic + displayName: Copy release to PSInfra storage + dependsOn: CopyReleaseBlobApproval + condition: and(succeeded(), ne('${{ parameters.SkipPSInfraInstallers }}', true)) + pool: + name: PowerShell1ES + type: windows + isCustom: true + demands: + - ImageOverride -equals PSMMS2019-Secure + + + variables: + - group: 'PSInfraStorage' + - group: 'Azure Blob variable group' + - 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 + - name: ob_sdl_codeql_compiled_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 + + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: yes + UseJson: no + + - pwsh: | + Get-ChildItem Env: + displayName: 'Capture Environment Variables' + + - task: AzurePowerShell@5 + displayName: Copy blobs to PSInfra storage + inputs: + azureSubscription: az-blob-cicd-infra + scriptType: inlineScript + azurePowerShellVersion: LatestVersion + pwsh: true + inline: | + $sourceStorageAccountName = '$(StorageAccount)' + $destinationStorageAccountName = '$(PSInfraStorageAccount)' + $destinationContainerName = '$web' + $destinationPrefix = 'install/$(ReleaseTagVar)' + + $sourceContext = New-AzStorageContext -StorageAccountName $sourceStorageAccountName + Write-Verbose -Verbose "Source context: $($sourceContext.BlobEndPoint)" + + $destinationContext = New-AzStorageContext -StorageAccountName $destinationStorageAccountName + Write-Verbose -Verbose "Destination context: $($destinationContext.BlobEndPoint)" + + foreach ($sourceContainerName in '$(AzureVersion)', '$(AzureVersion)-gc') { + $blobs = Get-AzStorageBlob -Context $sourceContext -Container $sourceContainerName + + Write-Verbose -Verbose "Blobs found in $sourceContainerName" + $blobs.Name | Write-Verbose -Verbose + + Write-Verbose -Verbose "Copying blobs from $sourceContainerName to $destinationContainerName/$destinationPrefix" + + foreach ($blob in $blobs) { + $sourceBlobName = $blob.Name + Write-Verbose -Verbose "sourceBlobName = $sourceBlobName" + + $destinationBlobName = "$destinationPrefix/$sourceBlobName" + Write-Verbose -Verbose "destinationBlobName = $destinationBlobName" + $existingBlob = Get-AzStorageBlob -Blob $destinationBlobName -Container $destinationContainerName -Context $destinationContext -ErrorAction Ignore + if ($existingBlob) { + Write-Verbose -Verbose "Blob $destinationBlobName already exists in '$destinationStorageAccountName/$destinationContainerName', removing before copy." + $existingBlob | Remove-AzStorageBlob -ErrorAction Stop -Verbose + } + + Copy-AzStorageBlob -SourceContext $sourceContext -DestinationContext $destinationContext -SrcContainer $sourceContainerName -SrcBlob $sourceBlobName -DestContainer $destinationContainerName -DestBlob $destinationBlobName -Force -Verbose -Confirm:$false + } + } + + +- template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Approve 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 + type: windows + isCustom: true + demands: + - ImageOverride -equals PSMMS2019-Secure + + variables: + - group: 'PSInfraStorage' + - group: 'Azure Blob variable group' + - 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 + 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 + + - pwsh: | + Get-ChildItem Env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: 'Capture Environment Variables' + + - task: AzurePowerShell@5 + displayName: Copy blobs to PSInfra storage + inputs: + azureSubscription: az-blob-cicd-infra + scriptType: inlineScript + azurePowerShellVersion: LatestVersion + pwsh: true + inline: | + $sourceStorageAccountName = '$(StorageAccount)' + $sourceContainerName = '$(AzureVersion)-nuget' + $prefix = 'globaltool' + + $destinationStorageAccountName = '$(PSInfraStorageAccount)' + $destinationContainerName = '$web' + $destinationPrefix = 'tool/$(Version)' + + $sourceContext = New-AzStorageContext -StorageAccountName $sourceStorageAccountName + Write-Verbose -Verbose "Source context: $($sourceContext.BlobEndPoint)" + + $destinationContext = New-AzStorageContext -StorageAccountName $destinationStorageAccountName + Write-Verbose -Verbose "Destination context: $($destinationContext.BlobEndPoint)" + + $blobs = Get-AzStorageBlob -Context $sourceContext -Container $sourceContainerName -Prefix $prefix + + Write-Verbose -Verbose "Blobs found in $sourceContainerName" + $blobs.Name | Write-Verbose -Verbose + + Write-Verbose -Verbose "Copying blobs from $sourceContainerName to $destinationContainerName/$destinationPrefix" + + foreach ($blob in $blobs) { + $sourceBlobName = $blob.Name + Write-Verbose -Verbose "sourceBlobName = $sourceBlobName" + + $destinationBlobName = $sourceBlobName -replace "$prefix", $destinationPrefix + Write-Verbose -Verbose "destinationBlobName = $destinationBlobName" + + Copy-AzStorageBlob -SourceContext $sourceContext -DestinationContext $destinationContext -SrcContainer $sourceContainerName -SrcBlob $sourceBlobName -DestContainer $destinationContainerName -DestBlob $destinationBlobName -Force -Verbose -Confirm:$false + } diff --git a/.pipelines/templates/release-SetReleaseTagandContainerName.yml b/.pipelines/templates/release-SetReleaseTagandContainerName.yml new file mode 100644 index 00000000000..d40551353d2 --- /dev/null +++ b/.pipelines/templates/release-SetReleaseTagandContainerName.yml @@ -0,0 +1,36 @@ +parameters: +- name: restorePhase + default: false + +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;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 = '$(OutputReleaseTag.ReleaseTag)'.ToLowerInvariant() -replace '\.', '-' + $vstsCommandString = "vso[task.setvariable variable=AzureVersion;isOutput=true]$azureVersion" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + + $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..f0c516dd28f --- /dev/null +++ b/.pipelines/templates/release-SetTagAndChangelog.yml @@ -0,0 +1,49 @@ +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 diff --git a/.pipelines/templates/release-checkout-pwsh-repo.yml b/.pipelines/templates/release-checkout-pwsh-repo.yml new file mode 100644 index 00000000000..9a7486887a6 --- /dev/null +++ b/.pipelines/templates/release-checkout-pwsh-repo.yml @@ -0,0 +1,13 @@ +steps: + - pwsh: | + Write-Verbose -Verbose "Deploy Box Product Pathway Does Not Support the `"checkout`" task" + if ($ENV:BUILD_REASON -eq 'PullRequest') { + throw 'We dont support PRs' + } + + Write-Verbose -Verbose $ENV:BUILD_SOURCEBRANCH + $branchName = $ENV:BUILD_SOURCEBRANCH -replace '^refs/heads/' + Write-Verbose -Verbose "Branch Name: $branchName" + git clone --depth 1 --branch $branchName https://$(mscodehubCodeReadPat)@mscodehub.visualstudio.com/PowerShellCore/_git/PowerShell '$(Pipeline.Workspace)/PowerShell' + cd $(Pipeline.Workspace)/PowerShell + displayName: Checkout Powershell Repository diff --git a/.pipelines/templates/release-download-packages.yml b/.pipelines/templates/release-download-packages.yml new file mode 100644 index 00000000000..27a3098d1e1 --- /dev/null +++ b/.pipelines/templates/release-download-packages.yml @@ -0,0 +1,122 @@ +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..a8a874619a4 --- /dev/null +++ b/.pipelines/templates/release-githubNuget.yml @@ -0,0 +1,203 @@ +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: ./variable/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" + $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 + $startLine = $headingStartLines[0] + $endLine = $headingStartLines[1] - 1 + + $clContent = $changelog | Select-Object -Skip ($startLine-1) -First ($endLine - $startLine) | 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 '-') { + $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: ./variable/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-install-pwsh.yml b/.pipelines/templates/release-install-pwsh.yml new file mode 100644 index 00000000000..9d7080a7e78 --- /dev/null +++ b/.pipelines/templates/release-install-pwsh.yml @@ -0,0 +1,34 @@ +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..cf7982cd5e1 --- /dev/null +++ b/.pipelines/templates/release-prep-for-ev2.yml @@ -0,0 +1,237 @@ +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 + 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 + 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 + + - download: PSPackagesOfficial + artifact: 'drop_linux_package_deb' + displayName: 'Download artifact containing .deb_amd64.deb file from PSPackagesOfficial triggering pipeline' + env: + ob_restore_phase: true + + - download: PSPackagesOfficial + artifact: 'drop_linux_package_rpm' + displayName: 'Download artifact containing .rh.x64_86.rpm file from PSPackagesOfficial triggering pipeline' + env: + ob_restore_phase: true + + - download: PSPackagesOfficial + artifact: 'drop_linux_package_mariner_x64' + displayName: 'Download artifact containing .cm.x86_64.rpm file from PSPackagesOfficial triggering pipeline' + env: + ob_restore_phase: true + + - download: PSPackagesOfficial + artifact: 'drop_linux_package_mariner_arm64' + displayName: 'Download artifact containing .cm.aarch64.rpm file from PSPackagesOfficial triggering pipeline' + env: + ob_restore_phase: true + + - pwsh: | + Write-Verbose -Verbose "Copy ESRP signed .deb and .rpm packages" + $downloadedPipelineFolder = Join-Path '$(Pipeline.Workspace)' -ChildPath 'PSPackagesOfficial' + $srcFilesFolder = Join-Path -Path '$(Pipeline.Workspace)' -ChildPath 'SourceFiles' + New-Item -Path $srcFilesFolder -ItemType Directory + $packagesFolder = Join-Path -Path $srcFilesFolder -ChildPath 'packages' + New-Item -Path $packagesFolder -ItemType Directory + + $packageFiles = Get-ChildItem -Path $downloadedPipelineFolder -Recurse -Directory -Filter "drop_*" | Get-ChildItem -File -Include *.deb, *.rpm + 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.Latest + $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-pmc.yml b/.pipelines/templates/release-publish-pmc.yml new file mode 100644 index 00000000000..d5454845211 --- /dev/null +++ b/.pipelines/templates/release-publish-pmc.yml @@ -0,0 +1,37 @@ +stages: +- stage: 'Prod_Release' + displayName: 'Deploy packages to PMC with EV2' + dependsOn: + - PrepForEV2 + variables: + - name: ob_release_environment + value: "Production" + - name: repoRoot + value: $(Build.SourcesDirectory) + jobs: + - job: Prod_ReleaseJob + displayName: Publish to PMC + pool: + type: release + + steps: + - task: DownloadPipelineArtifact@2 + inputs: + targetPath: '$(Pipeline.Workspace)' + artifact: drop_PrepForEV2_CopyEv2FilesToArtifact + displayName: 'Download drop_PrepForEV2_CopyEv2FilesToArtifact artifact that has all files needed' + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + targetPath: '$(Pipeline.Workspace)' + displayName: 'Download to get EV2 Files' + + - task: vsrm-ev2.vss-services-ev2.adm-release-task.ExpressV2Internal@1 + displayName: 'Ev2: Push to PMC' + inputs: + UseServerMonitorTask: true + EndpointProviderType: ApprovalService + ApprovalServiceEnvironment: Production + ServiceRootPath: '$(Pipeline.Workspace)/drop_PrepForEV2_CopyEV2FilesToArtifact/EV2Specs/ServiceGroupRoot' + RolloutSpecPath: '$(Pipeline.Workspace)/drop_PrepForEV2_CopyEV2FilesToArtifact/EV2Specs/ServiceGroupRoot/RolloutSpec.json' diff --git a/.pipelines/templates/release-symbols.yml b/.pipelines/templates/release-symbols.yml new file mode 100644 index 00000000000..9bfa7b870d4 --- /dev/null +++ b/.pipelines/templates/release-symbols.yml @@ -0,0 +1,91 @@ +parameters: + - name: skipPublish + default: false + type: boolean + +jobs: +- job: PublishSymbols + displayName: Publish Symbols + condition: succeeded() + pool: + type: windows + variables: + - name: runCodesignValidationInjection + value: false + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE + value: 1 + - 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 + clean: true + env: + ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase + + - template: release-SetReleaseTagandContainerName.yml + + - pwsh: | + Get-ChildItem Env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: 'Capture Environment Variables' + + - download: CoOrdinatedBuildPipeline + artifact: drop_windows_build_windows_x64_release + patterns: 'symbols.zip' + displayName: Download winx64 + + - download: CoOrdinatedBuildPipeline + artifact: drop_windows_build_windows_x86_release + patterns: 'symbols.zip' + displayName: Download winx86 + + - download: CoOrdinatedBuildPipeline + artifact: drop_windows_build_windows_arm64_release + patterns: 'symbols.zip' + displayName: Download winx64 + + - pwsh: | + Write-Verbose -Verbose "Enumerating $(Pipeline.Workspace)\CoOrdinatedBuildPipeline" + $downloadedArtifacts = Get-ChildItem -Path "$(Pipeline.Workspace)\CoOrdinatedBuildPipeline" -Recurse -Filter 'symbols.zip' + $downloadedArtifacts + $expandedRoot = New-Item -Path "$(Pipeline.Workspace)/expanded" -ItemType Directory -Verbose + $symbolsRoot = New-Item -Path "$(Pipeline.Workspace)/symbols" -ItemType Directory -Verbose + + $downloadedArtifacts | ForEach-Object { + $folderName = (Get-Item (Split-Path $_.FullName)).Name + Write-Verbose -Verbose "Expanding $($_.FullName) to $expandedRoot/$folderName/$($_.BaseName)" + $destFolder = New-Item -Path "$expandedRoot/$folderName/$($_.BaseName)/" -ItemType Directory -Verbose + Expand-Archive -Path $_.FullName -DestinationPath $destFolder -Force + + $symbolsToPublish = New-Item -Path "$symbolsRoot/$folderName/$($_.BaseName)" -ItemType Directory -Verbose + + Get-ChildItem -Path $destFolder -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/.pipelines/templates/release-upload-buildinfo.yml b/.pipelines/templates/release-upload-buildinfo.yml new file mode 100644 index 00000000000..c8693228847 --- /dev/null +++ b/.pipelines/templates/release-upload-buildinfo.yml @@ -0,0 +1,141 @@ +parameters: + - name: skipPublish + default: false + type: boolean + +jobs: +- job: BuildInfoPublish + displayName: Publish BuildInfo + condition: succeeded() + pool: + name: PowerShell1ES + type: windows + isCustom: true + demands: + - ImageOverride -equals PSMMS2019-Secure + variables: + - name: runCodesignValidationInjection + value: false + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE + value: 1 + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - group: 'Azure Blob variable group' + - 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 + clean: true + env: + ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase + + - template: release-SetReleaseTagandContainerName.yml + + - pwsh: | + Get-ChildItem Env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: 'Capture Environment Variables' + + - download: PSPackagesOfficial + artifact: BuildInfoJson + displayName: Download build info artifact + + - pwsh: | + $toolsDirectory = '$(Build.SourcesDirectory)/tools' + Import-Module "$toolsDirectory/ci.psm1" + $jsonFile = Get-Item "$ENV:PIPELINE_WORKSPACE/PSPackagesOfficial/BuildInfoJson/*.json" + $fileName = Split-Path $jsonFile -Leaf + + $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 + + 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: Create json files + + - task: AzurePowerShell@5 + displayName: Upload buildjson to blob + inputs: + azureSubscription: az-blob-cicd-infra + scriptType: inlineScript + azurePowerShellVersion: LatestVersion + pwsh: true + inline: | + $containerName = '$web' + $storageAccount = '$(PSInfraStorageAccount)' + $prefix = "buildinfo" + + $storageContext = New-AzStorageContext -StorageAccountName $storageAccount -UseConnectedAccount + + if ($env:CopyMainBuildInfo -eq 'YES') { + $jsonFile = "$env:BuildInfoJsonFile" + $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" + $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" + $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')) diff --git a/.pipelines/templates/release-validate-fxdpackages.yml b/.pipelines/templates/release-validate-fxdpackages.yml new file mode 100644 index 00000000000..30a2ab13905 --- /dev/null +++ b/.pipelines/templates/release-validate-fxdpackages.yml @@ -0,0 +1,123 @@ +parameters: + - 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 }} + displayName: ${{ parameters.displayName }} + variables: + - group: DotNetPrivateBuildAccess + - name: artifactName + value: ${{ parameters.artifactName }} + - 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 + - name: ob_sdl_credscan_enabled + value: ${{ parameters.enableCredScan }} + + pool: + type: ${{ parameters.jobtype }} + ${{ if eq(parameters.arm64, 'yes') }}: + hostArchitecture: arm64 + + steps: + - checkout: self + clean: true + + - template: release-SetReleaseTagandContainerName.yml@self + + - download: PSPackagesOfficial + artifact: "${{ parameters.artifactName }}" + displayName: Download fxd artifact + + - pwsh: | + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture environment + + - pwsh: | + $artifactName = '$(artifactName)' + Get-ChildItem "$(Pipeline.Workspace)/PSPackagesOfficial/$artifactName" -Recurse + displayName: 'Capture Downloaded Artifacts' + + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + useGlobalJson: true + packageType: 'sdk' + workingDirectory: $(Build.SourcesDirectory)/PowerShell" + + - pwsh: | + $artifactName = '$(artifactName)' + $rootPath = "$(Pipeline.Workspace)/PSPackagesOfficial/$artifactName" + + $destPath = New-Item "$rootPath/fxd" -ItemType Directory + $packageNameFilter = '${{ parameters.packageNamePattern }}' + + if ($packageNameFilter.EndsWith('tar.gz')) { + $package = @(Get-ChildItem -Path "$rootPath/*.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 "$rootPath/*.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: | + $repoRoot = "$(Build.SourcesDirectory)/PowerShell" + $artifactName = '$(artifactName)' + $rootPath = "$(Pipeline.Workspace)/PSPackagesOfficial/$artifactName" + + $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 + Import-Module "$repoRoot/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 "$rootPath/fxd" $pwshExeName + + if ($IsLinux) { + chmod u+x $pwshPath + } + + $pwshDllPath = Join-Path "$rootPath/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/.pipelines/templates/release-validate-globaltools.yml b/.pipelines/templates/release-validate-globaltools.yml new file mode 100644 index 00000000000..3dd1fd15f56 --- /dev/null +++ b/.pipelines/templates/release-validate-globaltools.yml @@ -0,0 +1,126 @@ +parameters: + jobName: "" + displayName: "" + jobtype: "windows" + globalToolExeName: 'pwsh.exe' + globalToolPackageName: 'PowerShell.Windows.x64' + + +jobs: +- job: ${{ parameters.jobName }} + displayName: ${{ parameters.displayName }} + pool: + type: ${{ parameters.jobtype }} + variables: + - group: DotNetPrivateBuildAccess + - 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: + - checkout: self + clean: true + + - template: release-SetReleaseTagandContainerName.yml@self + + - download: PSPackagesOfficial + artifact: drop_nupkg_build_nupkg + displayName: Download nupkgs + + - pwsh: | + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture environment + + - pwsh: | + Get-ChildItem "$(Pipeline.Workspace)/PSPackagesOfficial/drop_nupkg_build_nupkg" -Recurse + displayName: 'Capture Downloaded Artifacts' + + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + useGlobalJson: true + packageType: 'sdk' + workingDirectory: $(REPOROOT) + + - pwsh: | + $repoRoot = "$(Build.SourcesDirectory)/PowerShell" + + $toolPath = New-Item -ItemType Directory "$(System.DefaultWorkingDirectory)/toolPath" | Select-Object -ExpandProperty FullName + + Write-Verbose -Verbose "dotnet tool list -g" + dotnet tool list -g + + $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 '$(OutputVersion.Version)' $packageName + + 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: | + $repoRoot = "$(Build.SourcesDirectory)/PowerShell" + + $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 ( '$(OutputVersion.Version)' -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/.pipelines/templates/release-validate-packagenames.yml b/.pipelines/templates/release-validate-packagenames.yml new file mode 100644 index 00000000000..c717b50f289 --- /dev/null +++ b/.pipelines/templates/release-validate-packagenames.yml @@ -0,0 +1,184 @@ +jobs: +- job: validatePackageNames + displayName: Validate Package Names + pool: + type: windows + variables: + - 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 + - group: 'Azure Blob variable group' + + steps: + - checkout: self + clean: true + + - 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 '$(OutputReleaseTag.releaseTag)', (Get-Date).Ticks + Write-Host $name + Write-Host "##vso[build.updatebuildnumber]$name" + displayName: Set Release Name + + - task: AzurePowerShell@5 + displayName: Upload packages to blob + inputs: + azureSubscription: az-blob-cicd-infra + scriptType: inlineScript + azurePowerShellVersion: LatestVersion + pwsh: true + inline: | + $storageAccount = Get-AzStorageAccount -ResourceGroupName '$(StorageResourceGroup)' -Name '$(StorageAccount)' + $ctx = $storageAccount.Context + $container = '$(OutputVersion.AzureVersion)' + + $destinationPath = '$(System.ArtifactsDirectory)' + $blobList = Get-AzStorageBlob -Container $container -Context $ctx + foreach ($blob in $blobList) { + $blobName = $blob.Name + $destinationFile = Join-Path -Path $destinationPath -ChildPath $blobName + Get-AzStorageBlobContent -Container $container -Blob $blobName -Destination $destinationFile -Context $ctx -Force + Write-Output "Downloaded $blobName to $destinationFile" + } + + - 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 + +# Move to 1ES SBOM validation tool +# - job: validateBOM +# displayName: Validate Package Names +# pool: +# type: windows +# variables: +# - 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 +# - group: 'Azure Blob variable group' + +# 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/.pipelines/templates/release-validate-sdk.yml b/.pipelines/templates/release-validate-sdk.yml new file mode 100644 index 00000000000..6eb800f6326 --- /dev/null +++ b/.pipelines/templates/release-validate-sdk.yml @@ -0,0 +1,100 @@ +parameters: + jobName: "" + displayName: "" + poolName: "windows" + imageName: 'none' + +jobs: +- job: ${{ parameters.jobName }} + displayName: ${{ parameters.displayName }} + pool: + type: linux + isCustom: true + ${{ if eq( parameters.poolName, 'Azure Pipelines') }}: + name: ${{ parameters.poolName }} + vmImage: ${{ parameters.imageName }} + ${{ else }}: + name: ${{ parameters.poolName }} + demands: + - ImageOverride -equals ${{ parameters.imageName }} + + variables: + - group: mscodehub-feed-read-general + - group: mscodehub-feed-read-akv + - group: DotNetPrivateBuildAccess + + steps: + - checkout: self + clean: true + lfs: false + + - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self + parameters: + repoRoot: "$(Build.SourcesDirectory)" + + - template: release-SetReleaseTagandContainerName.yml@self + + - download: PSPackagesOfficial + artifact: drop_nupkg_build_nupkg + displayName: Download nupkgs + + - pwsh: | + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture environment + + - pwsh: | + Get-ChildItem "$(Pipeline.Workspace)/PSPackagesOfficial/drop_nupkg_build_nupkg" -Recurse + displayName: 'Capture Downloaded Artifacts' + + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + useGlobalJson: true + packageType: 'sdk' + workingDirectory: $(REPOROOT) + + - pwsh: | + $repoRoot = "$(Build.SourcesDirectory)" + + $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 + + $localLocation = "$(Pipeline.Workspace)/PSPackagesOfficial/drop_nupkg_build_nupkg" + $xmlElement = @" + + + + "@ + + $releaseVersion = '$(OutputVersion.Version)' + + Write-Verbose -Message "Release Version: $releaseVersion" -Verbose + + Set-Location -Path $repoRoot/test/hosting + + Get-ChildItem + + ## register the packages download directory in the nuget file + $nugetPath = './NuGet.Config' + if(!(test-path $nugetPath)) { + $nugetPath = "$repoRoot/nuget.config" + } + Write-Verbose -Verbose "nugetPath: $nugetPath" + $nugetConfigContent = Get-Content $nugetPath -Raw + $updateNugetContent = $nugetConfigContent.Replace("", $xmlElement) + + $updateNugetContent | Out-File $nugetPath -Encoding ascii + + Get-Content $nugetPath + + 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/.pipelines/templates/shouldSign.yml b/.pipelines/templates/shouldSign.yml new file mode 100644 index 00000000000..4bac9e1a3ae --- /dev/null +++ b/.pipelines/templates/shouldSign.yml @@ -0,0 +1,25 @@ +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' + env: + ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue diff --git a/.pipelines/templates/step/finalize.yml b/.pipelines/templates/step/finalize.yml new file mode 100644 index 00000000000..78e0341c829 --- /dev/null +++ b/.pipelines/templates/step/finalize.yml @@ -0,0 +1,6 @@ +# This was used before migrating to OneBranch to deal with one of the SDL taks from failing with a warning instead of an error. +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/.pipelines/templates/testartifacts.yml b/.pipelines/templates/testartifacts.yml new file mode 100644 index 00000000000..240ceae80f7 --- /dev/null +++ b/.pipelines/templates/testartifacts.yml @@ -0,0 +1,144 @@ +jobs: +- job: build_testartifacts_win + variables: + - name: runCodesignValidationInjection + value: false + - name: NugetSecurityAnalysisWarningLevel + value: none + - group: DotNetPrivateBuildAccess + - 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 + - name: ob_sdl_codeSignValidation_excludes + value: '-|**\*.ps1;-|**\*.psm1;-|**\*.ps1xml;-|**\*.psd1;-|**\*.exe;-|**\*.dll;-|**\*.cdxml' + + displayName: Build windows test artifacts + condition: succeeded() + pool: + type: windows + steps: + - checkout: self + clean: true + env: + ob_restore_phase: true + + - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self + parameters: + repoRoot: $(Build.SourcesDirectory)/PowerShell + ob_restore_phase: true + + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + useGlobalJson: true + packageType: 'sdk' + workingDirectory: $(Build.SourcesDirectory)/PowerShell" + env: + ob_restore_phase: true + + - pwsh: | + New-Item -Path '$(ob_outputDirectory)' -ItemType Directory -Force + Import-Module $(Build.SourcesDirectory)/PowerShell/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" + + Copy-Item -Path $(System.ArtifactsDirectory)/$packageName -Destination $(ob_outputDirectory) -Force -Verbose + } + BuildTestPackage -runtime win7-x64 + BuildTestPackage -runtime win7-x86 + BuildTestPackage -runtime win-arm64 + displayName: Build test package and upload + retryCountOnTaskFailure: 1 + env: + ob_restore_phase: true + + - pwsh: | + Write-Host "This doesn't do anything but make the build phase run." + displayName: Dummy build task + + +- job: build_testartifacts_nonwin + variables: + - name: runCodesignValidationInjection + value: false + - name: NugetSecurityAnalysisWarningLevel + value: none + - group: DotNetPrivateBuildAccess + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + displayName: Build non-windows test artifacts + condition: succeeded() + pool: + type: linux + steps: + - checkout: self + clean: true + env: + ob_restore_phase: true + + - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self + parameters: + repoRoot: $(Build.SourcesDirectory)/PowerShell + ob_restore_phase: true + + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + useGlobalJson: true + packageType: 'sdk' + workingDirectory: $(Build.SourcesDirectory)/PowerShell" + env: + ob_restore_phase: true + + - pwsh: | + New-Item -Path '$(ob_outputDirectory)' -ItemType Directory -Force + Import-Module $(Build.SourcesDirectory)/PowerShell/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 + Copy-Item -Path $(System.ArtifactsDirectory)/$packageName -Destination $(ob_outputDirectory) -Force -Verbose + } + 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 + env: + ob_restore_phase: true + + - pwsh: | + Write-Host "This doesn't do anything but make the build phase run." + displayName: Dummy build task diff --git a/.pipelines/templates/uploadToAzure.yml b/.pipelines/templates/uploadToAzure.yml new file mode 100644 index 00000000000..e698a7041da --- /dev/null +++ b/.pipelines/templates/uploadToAzure.yml @@ -0,0 +1,434 @@ +jobs: +- job: upload_packages + displayName: Upload packages + condition: succeeded() + pool: + type: windows + variables: + - name: ob_sdl_sbom_enabled + value: true + - name: runCodesignValidationInjection + value: false + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE + value: 1 + - 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 + - name: ob_sdl_codeql_compiled_enabled + value: false + - group: 'Azure Blob variable group' + + steps: + - checkout: self + clean: true + 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 + + - template: /.pipelines/templates/release-SetReleaseTagandContainerName.yml@self + + - template: /.pipelines/templates/cloneToOfficialPath.yml@self + + - pwsh: | + Get-ChildItem Env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: 'Capture Environment Variables' + + - pwsh: | + New-Item -Path '$(Build.ArtifactStagingDirectory)/downloads' -ItemType Directory -Force + displayName: Create downloads directory + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_linux_package_deb + itemPattern: '**/*.deb' + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download deb package + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_linux_package_fxdependent + itemPattern: '**/*.tar.gz' + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download linux fxd package + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_linux_package_mariner_arm64 + itemPattern: '**/*.rpm' + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download linux mariner arm64 package + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_linux_package_mariner_x64 + itemPattern: '**/*.rpm' + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download linux mariner x64 package + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_linux_package_minSize + itemPattern: '**/*.tar.gz' + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download linux minSize package + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_linux_package_rpm + itemPattern: '**/*.rpm' + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download linux rpm package + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_linux_package_tar + itemPattern: '**/*.tar.gz' + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download linux tar package + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_linux_package_tar_alpine + itemPattern: '**/*.tar.gz' + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download linux alpine tar package + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_linux_package_tar_alpine_fxd + itemPattern: '**/*.tar.gz' + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download linux alpine fxd tar package + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_linux_package_tar_arm + itemPattern: '**/*.tar.gz' + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download linux arm32 tar package + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_linux_package_tar_arm64 + itemPattern: '**/*.tar.gz' + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download linux arm64 tar package + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_nupkg_build_nupkg + itemPattern: '**/*.nupkg' + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download nupkgs + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_windows_package_package_win_arm64 + itemPattern: | + **/*.msi + **/*.msix + **/*.zip + **/*.exe + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download windows arm64 packages + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_windows_package_package_win_fxdependent + itemPattern: '**/*.zip' + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download windows fxdependent packages + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_windows_package_package_win_fxdependentWinDesktop + itemPattern: '**/*.zip' + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download windows fxdependentWinDesktop packages + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_windows_package_package_win_minsize + itemPattern: '**/*.zip' + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download windows minsize packages + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_windows_package_package_win_x64 + itemPattern: | + **/*.msi + **/*.msix + **/*.zip + **/*.exe + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download windows x64 packages + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_windows_package_package_win_x86 + itemPattern: | + **/*.msi + **/*.msix + **/*.zip + **/*.exe + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download windows x86 packages + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: macos-pkgs + itemPattern: | + **/*.tar.gz + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download macos tar packages + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_mac_package_sign_package_macos_arm64 + itemPattern: | + **/*.pkg + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download macos arm packages + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_mac_package_sign_package_macos_x64 + itemPattern: | + **/*.pkg + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download macos x64 packages + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_msixbundle_CreateMSIXBundle + itemPattern: | + **/*.msixbundle + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download MSIXBundle + + - pwsh: | + Get-ChildItem '$(Build.ArtifactStagingDirectory)/downloads' | Select-Object -ExpandProperty FullName + displayName: 'Capture downloads' + + - pwsh: | + Write-Verbose -Verbose "Copying Github Release files in $(Build.ArtifactStagingDirectory)/downloads to use in Release Pipeline" + + Write-Verbose -Verbose "Creating output directory for GitHub Release files: $(ob_outputDirectory)/GitHubPackages" + New-Item -Path $(ob_outputDirectory)/GitHubPackages -ItemType Directory -Force + Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)/downloads/*" -Recurse | + Where-Object { $_.Extension -notin '.msix', '.nupkg' -and $_.Name -notmatch '-gc'} | + 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 + + - task: AzurePowerShell@5 + displayName: Upload packages to blob + inputs: + azureSubscription: az-blob-cicd-infra + scriptType: inlineScript + azurePowerShellVersion: LatestVersion + pwsh: true + inline: | + $downloadsDirectory = '$(Build.ArtifactStagingDirectory)/downloads' + $uploadedDirectory = '$(Build.ArtifactStagingDirectory)/uploaded' + $storageAccountName = "pscoretestdata" + $containerName = $env:AZUREVERSION + + Write-Verbose -Verbose "Uploading packages to blob storage account: $storageAccountName container: $containerName" + + $context = New-AzStorageContext -StorageAccountName $storageAccountName -UseConnectedAccount + + # Create the blob container if it doesn't exist + $containerExists = Get-AzStorageContainer -Name $containerName -Context $context -ErrorAction SilentlyContinue + if (-not $containerExists) { + $null = New-AzStorageContainer -Name $containerName -Context $context + Write-Host "Blob container $containerName created successfully." + } + + $gcPackages = Get-ChildItem -Path $downloadsDirectory -Filter "powershell*gc.*" + Write-Verbose -Verbose "gc files to upload." + $gcPackages | Write-Verbose -Verbose + $gcContainerName = "$containerName-gc" + # Create the blob container if it doesn't exist + $containerExists = Get-AzStorageContainer -Name $gcContainerName -Context $context -ErrorAction SilentlyContinue + if (-not $containerExists) { + $null = New-AzStorageContainer -Name $gcContainerName -Context $context + Write-Host "Blob container $gcContainerName created successfully." + } + + $gcPackages | ForEach-Object { + $blobName = "${_.Name}" + Write-Verbose -Verbose "Uploading $($_.FullName) to $gcContainerName/$blobName" + $null = Set-AzStorageBlobContent -File $_.FullName -Container $gcContainerName -Blob $blobName -Context $context + # Move to folder to we wont upload again + Move-Item -Path $_.FullName -Destination $uploadedDirectory -Force -Verbose + } + + $nupkgFiles = Get-ChildItem -Path $downloadsDirectory -Filter "*.nupkg" | Where-Object { $_.Name -notlike "powershell*.nupkg" } + + # create a SHA512 checksum file for each nupkg files + + $checksums = $nupkgFiles | + 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 "$downloadsDirectory\SHA512SUMS" -Force + $fileContent = Get-Content -Path "$downloadsDirectory\SHA512SUMS" -Raw | Out-String + Write-Verbose -Verbose -Message $fileContent + + Write-Verbose -Verbose "nupkg files to upload." + $nupkgFiles += (Get-Item "$downloadsDirectory\SHA512SUMS") + $nupkgFiles | Write-Verbose -Verbose + $nugetContainerName = "$containerName-nuget" + # Create the blob container if it doesn't exist + $containerExists = Get-AzStorageContainer -Name $nugetContainerName -Context $context -ErrorAction SilentlyContinue + if (-not $containerExists) { + $null = New-AzStorageContainer -Name $nugetContainerName -Context $context + Write-Host "Blob container $nugetContainerName created successfully." + } + + $nupkgFiles | ForEach-Object { + $blobName = $_.Name + Write-Verbose -Verbose "Uploading $($_.FullName) to $nugetContainerName/$blobName" + $null = Set-AzStorageBlobContent -File $_.FullName -Container $nugetContainerName -Blob $blobName -Context $context + # Move to folder to we wont upload again + Move-Item -Path $_.FullName -Destination $uploadedDirectory -Force -Verbose + } + + $globaltoolFiles = Get-ChildItem -Path $downloadsDirectory -Filter "powershell*.nupkg" + # create a SHA512 checksum file for each nupkg files + + $checksums = $globaltoolFiles | + 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" + } + + New-Item -Path "$downloadsDirectory\globaltool" -ItemType Directory -Force + $checksums | Out-File -FilePath "$downloadsDirectory\globaltool\SHA512SUMS" -Force + $fileContent = Get-Content -Path "$downloadsDirectory\globaltool\SHA512SUMS" -Raw | Out-String + Write-Verbose -Verbose -Message $fileContent + + Write-Verbose -Verbose "globaltool files to upload." + $globaltoolFiles += Get-Item ("$downloadsDirectory\globaltool\SHA512SUMS") + $globaltoolFiles | Write-Verbose -Verbose + $globaltoolContainerName = "$containerName-nuget" + $globaltoolFiles | ForEach-Object { + $blobName = "globaltool/" + $_.Name + $globaltoolContainerName = "$containerName-nuget" + Write-Verbose -Verbose "Uploading $($_.FullName) to $globaltoolContainerName/$blobName" + $null = Set-AzStorageBlobContent -File $_.FullName -Container $globaltoolContainerName -Blob $blobName -Context $context + # Move to folder to we wont upload again + Move-Item -Path $_.FullName -Destination $uploadedDirectory -Force + } + + # To use -Include parameter, we need to use \* to get all files + $privateFiles = Get-ChildItem -Path $downloadsDirectory\* -Include @("*.msix", "*.exe") + Write-Verbose -Verbose "private files to upload." + $privateFiles | Write-Verbose -Verbose + $privateContainerName = "$containerName-private" + # Create the blob container if it doesn't exist + $containerExists = Get-AzStorageContainer -Name $privateContainerName -Context $context -ErrorAction SilentlyContinue + if (-not $containerExists) { + $null = New-AzStorageContainer -Name $privateContainerName -Context $context + Write-Host "Blob container $privateContainerName created successfully." + } + + $privateFiles | ForEach-Object { + $blobName = $_.Name + Write-Verbose -Verbose "Uploading $($_.FullName) to $privateContainerName/$blobName" + $null = Set-AzStorageBlobContent -File $_.FullName -Container $privateContainerName -Blob $blobName -Context $context + # Move to folder to we wont upload again + Move-Item -Path $_.FullName -Destination $uploadedDirectory -Force -Verbose + } + + # 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") + Write-Verbose -Verbose "files to upload." + $files | Write-Verbose -Verbose + + $files | ForEach-Object { + $blobName = $_.Name + Write-Verbose -Verbose "Uploading $($_.FullName) to $containerName/$blobName" + $null = Set-AzStorageBlobContent -File $_.FullName -Container $containerName -Blob $blobName -Context $context + 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/variable/release-shared.yml b/.pipelines/templates/variable/release-shared.yml new file mode 100644 index 00000000000..f944639a908 --- /dev/null +++ b/.pipelines/templates/variable/release-shared.yml @@ -0,0 +1,42 @@ +parameters: + - name: REPOROOT + type: string + default: $(Build.SourcesDirectory)\PowerShell + - 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 + value: 1 + - group: 'mscodehub-code-read-akv' + - group: 'Azure Blob variable group' + - group: 'GitHubTokens' + - 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: ${{ parameters.REPOROOT }}\.config\tsaoptions.json + - name: ob_sdl_credscan_suppressionsFile + 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 new file mode 100644 index 00000000000..929aa54b8a7 --- /dev/null +++ b/.pipelines/templates/windows-hosted-build.yml @@ -0,0 +1,324 @@ +parameters: + Architecture: 'x64' + BuildConfiguration: 'release' + JobName: 'build_windows' + +jobs: +- job: build_windows_${{ parameters.Architecture }}_${{ parameters.BuildConfiguration }} + displayName: Build_Windows_${{ parameters.Architecture }}_${{ parameters.BuildConfiguration }} + condition: succeeded() + pool: + type: windows + variables: + - name: runCodesignValidationInjection + value: false + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE + value: 1 + - group: DotNetPrivateBuildAccess + - group: certificate_logical_to_actual + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: ob_sdl_codeSignValidation_enabled + value: false + - 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: Architecture + value: ${{ parameters.Architecture }} + - name: BuildConfiguration + value: ${{ parameters.BuildConfiguration }} + - name: ob_sdl_sbom_packageName + value: 'Microsoft.Powershell.Windows.${{ parameters.Architecture }}' + # We add this manually, so we need it disabled the OneBranch auto-injected one. + - name: ob_sdl_codeql_compiled_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 + + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + + - template: /.pipelines/templates/cloneToOfficialPath.yml@self + + - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self + parameters: + repoRoot: $(PowerShellRoot) + + - task: CodeQL3000Init@0 # Add CodeQL Init task right before your 'Build' step. + condition: eq(variables['CODEQL_ENABLED'], 'true') + 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 + + - task: UseDotNet@2 + inputs: + useGlobalJson: true + workingDirectory: $(PowerShellRoot) + env: + ob_restore_phase: true + + - 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 + } + + $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 + + $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 "Verifying pdbs exist in build folder" + $pdbs = Get-ChildItem -Path $buildWithSymbolsPath -Recurse -Filter *.pdb + if ($pdbs.Count -eq 0) { + Write-Error -Message "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.zip" -Update + } + + 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. + + - pwsh: | + $runtime = switch ($env:Architecture) + { + "x64" { "win7-x64" } + "x86" { "win7-x86" } + "arm64" { "win-arm64" } + "fxdependent" { "fxdependent" } + "fxdependentWinDesktop" { "fxdependent-win-desktop" } + } + + Import-Module -Name $(PowerShellRoot)/build.psm1 -Force + + ## Build global tool + Write-Verbose -Message "Building PowerShell global tool for Windows.x64" -Verbose + $globalToolCsProjDir = Join-Path $(PowerShellRoot) 'src' 'GlobalTools' 'PowerShell.Windows.x64' + Push-Location -Path $globalToolCsProjDir -Verbose + + $globalToolArtifactPath = Join-Path $(Build.SourcesDirectory) 'GlobalTool' + $vstsCommandString = "vso[task.setvariable variable=GlobalToolArtifactPath]${globalToolArtifactPath}" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + + if ($env:RELEASETAGVAR) { + $ReleaseTagToUse = $env:RELEASETAGVAR -Replace '^v' + } + + Write-Verbose -Verbose "Building PowerShell global tool for Windows.x64 with cmdline: dotnet publish --no-self-contained --artifacts-path $globalToolArtifactPath /property:PackageVersion=$(Version) --configuration 'Release' /property:ReleaseTag=$ReleaseTagToUse" + dotnet publish --no-self-contained --artifacts-path $globalToolArtifactPath /property:PackageVersion=$(Version) --configuration 'Release' /property:ReleaseTag=$ReleaseTagToUse + $globalToolBuildModulePath = Join-Path $globalToolArtifactPath 'publish' 'PowerShell.Windows.x64' 'release' + Pop-Location + # do this to ensure everything gets signed. + Restore-PSModuleToBuild -PublishPath $globalToolBuildModulePath + + $buildWithSymbolsPath = Get-Item -Path "$(Pipeline.Workspace)/Symbols_$(Architecture)" + $refFolderPath = Join-Path $buildWithSymbolsPath 'ref' + Write-Verbose -Verbose "refFolderPath: $refFolderPath" + + # Copy reference assemblies + Copy-Item -Path $refFolderPath -Destination $globalToolBuildModulePath -Recurse -Force + + Write-Verbose -Verbose "clean unnecessary files in obj directory" + $objDir = Join-Path $globalToolArtifactPath 'obj' 'PowerShell.Windows.x64' 'release' + + $filesToKeep = @("apphost.exe", "PowerShell.Windows.x64.pdb", "PowerShell.Windows.x64.dll", "project.assets.json") + + # only four files are needed in obj folder for global tool packaging + Get-ChildItem -Path $objDir -File -Recurse | + Where-Object { -not $_.PSIsContainer } | + Where-Object { $_.name -notin $filesToKeep } | + Remove-Item -Verbose + + + displayName: 'Build Winx64 Global tool' + condition: and(succeeded(), eq(variables['Architecture'], 'fxdependent')) + 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. + condition: eq(variables['CODEQL_ENABLED'], 'true') + env: + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + + - pwsh: | + $platform = 'windows' + $vstsCommandString = "vso[task.setvariable variable=ArtifactPlatform]$platform" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + displayName: Set artifact platform + + - template: /.pipelines/templates/obp-file-signing.yml@self + parameters: + binPath: '$(Pipeline.Workspace)/Symbols_$(Architecture)' + + ## first we sign all the files in the bin folder + - ${{ if eq(variables['Architecture'], 'fxdependent') }}: + - template: /.pipelines/templates/obp-file-signing.yml@self + parameters: + binPath: '$(GlobalToolArtifactPath)/publish/PowerShell.Windows.x64/release' + globalTool: 'true' + + - pwsh: | + Get-ChildItem '$(GlobalToolArtifactPath)/obj/PowerShell.Windows.x64/release' + displayName: Capture obj files + condition: and(succeeded(), eq(variables['Architecture'], 'fxdependent')) + + ## Now we sign couple of file from the obj folder which are needed for the global tool packaging + - task: onebranch.pipeline.signing@1 + displayName: Sign obj files + inputs: + command: 'sign' + signing_profile: external_distribution + files_to_sign: '**\*.dll;**\*.exe' + search_root: '$(GlobalToolArtifactPath)/obj/PowerShell.Windows.x64/release' + condition: and(succeeded(), eq(variables['Architecture'], 'fxdependent')) + + - pwsh: | + <# The way the packaging works is a bit tricky as when it is built, we cannot add the modules that come from gallery. + We have to use dotnet pack to build the nupkg and then expand it as a zip. + After expanding we restore the signed files for the modules from the gallery. + We also delete pdbs, content and contentFiles folder which are not necessary. + After that, we repack using Compress-Archive and rename it back to a nupkg. + #> + + $packagingStrings = Import-PowerShellDataFile "$(PowerShellRoot)\tools\packaging\packaging.strings.psd1" + + $outputPath = Join-Path '$(ob_outputDirectory)' 'globaltool' + $null = New-Item -ItemType Directory -Path $outputPath -Force + $globalToolCsProjDir = Join-Path $(PowerShellRoot) 'src' 'GlobalTools' 'PowerShell.Windows.x64' + Push-Location -Path $globalToolCsProjDir -Verbose + + if ($env:RELASETAGVAR) { + $ReleaseTagToUse = $env:RELASETAGVAR -Replace '^v' + } + + Write-Verbose -Verbose "Packing PowerShell global tool for Windows.x64 with cmdline: dotnet pack --output $outputPath --no-build --artifacts-path '$(GlobalToolArtifactPath)' /property:PackageVersion=$(Version) /property:PackageIcon=Powershell_64.png /property:Version=$(Version) /property:ReleaseTag=$ReleaseTagToUse" + + dotnet pack --output $outputPath --no-build --artifacts-path '$(GlobalToolArtifactPath)' /property:PackageVersion=$(Version) /property:PackageIcon=Powershell_64.png /property:Version=$(Version) /property:ReleaseTag=$ReleaseTagToUse + + Write-Verbose -Verbose "Deleting content and contentFiles folders from the nupkg" + + $nupkgs = Get-ChildItem -Path $outputPath -Filter powershell*.nupkg + + $nupkgName = $nupkgs.Name + $newName = $nupkgName -replace '(\.nupkg)$', '.zip' + Rename-Item -Path $nupkgs.FullName -NewName $newName + + $zipPath = Get-ChildItem -Path $outputPath -Filter powershell*.zip + + # Expand zip and remove content and contentFiles folders + Expand-Archive -Path $zipPath -DestinationPath "$outputPath\temp" -Force + + $modulesToCopy = @( + 'PowerShellGet' + 'PackageManagement' + 'Microsoft.PowerShell.PSResourceGet' + 'Microsoft.PowerShell.Archive' + 'PSReadLine' + 'ThreadJob' + ) + + $sourceModulePath = Join-Path '$(GlobalToolArtifactPath)' 'publish' 'PowerShell.Windows.x64' 'release' 'Modules' + $destModulesPath = Join-Path "$outputPath" 'temp' 'tools' 'net10.0' 'any' 'modules' + + $modulesToCopy | ForEach-Object { + $modulePath = Join-Path $sourceModulePath $_ + Copy-Item -Path $modulePath -Destination $destModulesPath -Recurse -Force + } + + # Copy ref assemblies + Copy-Item '$(Pipeline.Workspace)/Symbols_$(Architecture)/ref' "$outputPath\temp\tools\net10.0\any\ref" -Recurse -Force + + $contentPath = Join-Path "$outputPath\temp" 'content' + $contentFilesPath = Join-Path "$outputPath\temp" 'contentFiles' + + Remove-Item -Path $contentPath,$contentFilesPath -Recurse -Force + + # remove PDBs to reduce the size of the nupkg + Remove-Item -Path "$outputPath\temp\tools\net10.0\any\*.pdb" -Recurse -Force + + # create powershell.config.json + $config = [ordered]@{} + $config.Add("Microsoft.PowerShell:ExecutionPolicy", "RemoteSigned") + $config.Add("WindowsPowerShellCompatibilityModuleDenyList", @("PSScheduledJob", "BestPractices", "UpdateServices")) + + $configPublishPath = Join-Path "$outputPath" 'temp' 'tools' 'net10.0' 'any' "powershell.config.json" + Set-Content -Path $configPublishPath -Value ($config | ConvertTo-Json) -Force -ErrorAction Stop + + Compress-Archive -Path "$outputPath\temp\*" -DestinationPath "$outputPath\$nupkgName" -Force + + Remove-Item -Path "$outputPath\temp" -Recurse -Force + Remove-Item -Path $zipPath -Force + + if (-not (Test-Path "$outputPath\powershell.windows.x64.*.nupkg")) { + throw "Global tool package not found at $outputPath" + } + displayName: 'Pack Windows.x64 global tool' + condition: and(succeeded(), eq(variables['Architecture'], 'fxdependent')) + + - task: onebranch.pipeline.signing@1 + displayName: Sign nupkg files + inputs: + command: 'sign' + cp_code: 'CP-401405' + files_to_sign: '**\*.nupkg' + search_root: '$(ob_outputDirectory)\globaltool' + condition: and(succeeded(), eq(variables['Architecture'], 'fxdependent')) + + - template: /.pipelines/templates/step/finalize.yml@self diff --git a/.pipelines/templates/windows-package-build.yml b/.pipelines/templates/windows-package-build.yml new file mode 100644 index 00000000000..53b57df45dd --- /dev/null +++ b/.pipelines/templates/windows-package-build.yml @@ -0,0 +1,305 @@ +parameters: + runtime: x64 + +jobs: +- job: package_win_${{ parameters.runtime }} + displayName: Package Windows ${{ parameters.runtime }} + condition: succeeded() + pool: + type: windows + + variables: + - name: runCodesignValidationInjection + value: false + - 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 # 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 + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: yes + UseJson: no + + - template: shouldSign.yml + + - template: cloneToOfficialPath.yml + parameters: + nativePathRoot: '$(Agent.TempDirectory)' + + - 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" + Get-ChildItem "$(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_windows_build_windows_${{ parameters.runtime }}_release" -Recurse + displayName: 'Capture Downloaded Artifacts' + # Diagnostics is not critical it passes every time it runs + continueOnError: true + env: + ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + + - task: UseDotNet@2 + inputs: + useGlobalJson: true + workingDirectory: $(REPOROOT) + env: + ob_restore_phase: true + + - pwsh: | + $msixUrl = '$(makeappUrl)' + Invoke-RestMethod -Uri $msixUrl -OutFile '$(Pipeline.Workspace)\makeappx.zip' + Expand-Archive '$(Pipeline.Workspace)\makeappx.zip' -destination '\' -Force + displayName: Install packaging tools + env: + ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + + - pwsh: | + $runtime = '$(Runtime)' + Write-Verbose -Verbose "runtime = '$(Runtime)'" + + $signedFolder = switch ($runtime) { + 'x64' { 'Signed-win7-x64' } + 'x86' { 'Signed-win7-x86' } + 'arm64' { 'Signed-win-arm64' } + 'fxdependent' { 'Signed-fxdependent' } + 'fxdependentWinDesktop' { 'Signed-fxdependent-win-desktop' } + 'minsize' { 'Signed-win7-x64' } + } + + Write-Verbose -Message "Init..." -Verbose + + $repoRoot = "$env:REPOROOT" + Import-Module "$repoRoot\build.psm1" + Import-Module "$repoRoot\tools\packaging" + + Start-PSBootstrap -Scenario Package + + $signedFilesPath, $psoptionsFilePath = if ($env:RUNTIME -eq 'minsize') { + "$(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_windows_build_windows_x64_${runtime}\$signedFolder" + "$(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_windows_build_windows_x64_${runtime}\psoptions\psoptions.json" + } + else { + "$(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_windows_build_windows_${runtime}_release\$signedFolder" + "$(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_windows_build_windows_${runtime}_release\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 + $LTS = $metadata.LTSRelease.Package + + if ($LTS) { + Write-Verbose -Message "LTS Release: $LTS" + } + + Start-PSBootstrap -Scenario Package + + $WindowsRuntime = switch ($runtime) { + 'x64' { 'win7-x64' } + 'x86' { 'win7-x86' } + 'arm64' { 'win-arm64' } + 'fxdependent' { 'win7-x64' } + 'fxdependentWinDesktop' { 'win7-x64' } + 'minsize' { 'win7-x64' } + } + + $packageTypes = switch ($runtime) { + 'x64' { @('msi', 'zip', 'msix') } + 'x86' { @('msi', 'zip', 'msix') } + 'arm64' { @('msi', '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 + } + + Set-Location $repoRoot + + 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 + } + + $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)' + + - 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') } + '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 '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" + 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 "msixPkgPath: $msixPkgPath" + Copy-Item -Path $msixPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose + } + displayName: Copy 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 + diff --git a/.poshchan/settings.json b/.poshchan/settings.json deleted file mode 100644 index 21ad0f08b48..00000000000 --- a/.poshchan/settings.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "version": "0.1", - "azdevops": { - "build_targets": { - "static": "PowerShell-CI-static-analysis", - "windows": "PowerShell-CI-Windows", - "macos": "PowerShell-CI-macOS", - "linux": "PowerShell-CI-Linux", - "ssh": "PowerShell-CI-SSH", - "all": [ - "PowerShell-CI-static-analysis", - "PowerShell-CI-Windows", - "PowerShell-CI-macOS", - "PowerShell-CI-Linux", - "PowerShell-CI-SSH" - ] - }, - "authorized_users": [ - "adityapatwardhan", - "anmenaga", - "bergmeister", - "daxian-dbw", - "iSazonov", - "JamesWTruher", - "KirkMunro", - "PaulHigin", - "rjmholt", - "SteveL-MSFT", - "TravisEz13", - "TylerLeonhardt", - "vexx32" - ] - }, - "failures": { - "authorized_users": [ - "adityapatwardhan", - "anmenaga", - "bergmeister", - "daxian-dbw", - "IISResetMe", - "iSazonov", - "JamesWTruher", - "KirkMunro", - "kwkam", - "PaulHigin", - "powercode", - "rjmholt", - "rkeithhill", - "SteveL-MSFT", - "TravisEz13", - "TylerLeonhardt", - "vexx32" - ] - }, - "reminders": { - "authorized_users": "*" - } -} 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 42a757b509d..dbc54bbc393 100644 --- a/.spelling +++ b/.spelling @@ -8,13 +8,18 @@ 0xfeeddeadbeef 100ms 1redone +1.final 2.x 2ae5d07 32-bit +4.final 64-bit +AAATechGuy about_ about_debuggers about_jobs +about_Telemetry +about_PSDesiredStateConfiguration acl adamdriscoll add-localgroupmember @@ -27,6 +32,8 @@ adityapatwardhan ADOPTERS.md aetos382 aiello +Aishat452 +al-cheb alepauly alexandair alexjordan6 @@ -45,6 +52,7 @@ alpha.9 alternatestream alvarodelvalle amd64 +ananya26-vishnoi andschwa anmenaga api @@ -53,23 +61,29 @@ APIScan appimage applocker appveyor +appx +ArchitectureSensitiveAttribute args argumentlist arm32 arm64 asp.net +ast.cs assemblyloadcontext AssemblyInfo assessibility +AtariDreams authenticode authenticodesignature azdevops AzFileCopy +AzureFileCopy azurerm.netcore.preview azurerm.profile.netcore.preview azurerm.resources.netcore.preview backgrounded backgrounding +backport beatcracker bergmeister beta.1 @@ -81,40 +95,57 @@ beta.6 beta.7 beta.8 beta.9 +beta.406 +beta.507 beta2 bgelens Bhaal22 +BinaryFormatter bjh7242 +bnot bool bpayette brcrista breakpoint brianbunke britishben +brotli brucepay bugfix build.json build.psm1 bulid +buildInfoJson callmejoebob +CarloToso catchable cdxml celsius CentOS +CimDscParser +codeql-action +CGManifest +cgmanifest.json +cgmanifest changelog changelog.md changelogs changeset changesets channel9 +charltonstanley charset checkbox checksum chibi childitem +ChuckieChen945 ChrisLGardner +chrullrich cimsession cimsupport +ci.psm1 +cgmanifest classlib clear-itemproperty cloudydino @@ -132,10 +163,14 @@ CodeFormatter codeowner codepage commanddiscovery +CommandInvocationIntrinsics commandsearch CommandSearcher comobject +Compiler.cs composability +computerinfo +ComRuntimeHelpers.cs config connect-pssession consolehost @@ -163,6 +198,8 @@ CorePsAssemblyLoadContext.cs coveralls.exe coveralls.io. coveralls.net +CreateFile +CreateFileW credssp cron crontab @@ -176,9 +213,11 @@ ctrl CurrentCulture CustomShellCommands.cs DamirAinullin +DarylGraves darquewarrior darwinjs DateTime +DateTime.UnixEpoch daxian-dbw dayofweek dchristian3188 @@ -214,23 +253,28 @@ displaydataquery Distribution_Request.md distro distros +dkaszews dll +DllImport dlls dlwyatt dockerbasedbuild dockerfile dockerfiles -docs.microsoft.com +learn.microsoft.com doctordns don'ts dongbo dotcover dotnet dotnetcore +dotnetmetadata.json DotnetRutimeMetadata.json +DotnetRuntimeMetadata.json dottedscopes downlevel dropdown +dwtaber e.g. ebook ebooks @@ -254,6 +298,7 @@ ergo3114 errorrecord etl eugenesmlv +EventLogLogProvider excludeversion exe executables @@ -265,25 +310,32 @@ export-clixml export-csv export-formatdata export-modulemember +fabricbot.json failurecode failurecount +farmerau fbehrens felixfbecker ffeldhaus ffi +fflaten +File.OpenHandle filecatalog filename filesystem filesystemprovider +files.wxs filterhashtable find-dscresource find-packageprovider find-rolecapability +findMissingNotices.ps1 firefox folderName foreach formatfileloading formatviewbinding +FormatWideCommand Francisco-Gamino frontload fullclr @@ -292,6 +344,7 @@ functionprovider FunctionTable fxdependent gabrielsroka +GAC_Arm64 gamified gc.regions.xml Generic.SortedList @@ -332,32 +385,40 @@ get-typedata get-uiculture get-winevent get-wsmaninstance +Get-WSManSupport GetExceptionForHR getparentprocess gettype Geweldig +GigaScratch gitcommitid github githug gitter glachancecmaisonneuve +global.json globbing GoogleTest +gregsdennis GUIs gzip hackathons +HashData HashSet hashtable hashtables +hayhay27 helloworld.ps1 helpproviderwithcache helpproviderwithfullcache helpsystem hemant hemantmahawar +Higinbotham himura2la hololens homebrew +hostifaces hostname hotfix httpbin.org @@ -365,6 +426,7 @@ httpbin's https hubuk hvitved +i3arnon i.e. ico idera @@ -382,7 +444,10 @@ includeide includeusername informationrecord initializers +InitialSessionState.cs +InlineAsTypeCheck install-packageprovider +IntelliSense interactivetesting interop interoperation @@ -391,6 +456,7 @@ invoke-cimmethod Invoke-DSCResource invoke-restmethod invoke-wsmanaction +InvokeRestMethodCommand.Common iot isazonov iscore @@ -410,19 +476,24 @@ joandrsn joeltankam joeyaiello jokajak +JohnLBevan +josea joshuacooper journalctl jpsnover json jsonconfigfileaccessor +JsonSchema.Net judgement jumplist jwmoss kanjibates kasper3 katacoda +Kellen-Stuart kevinmarquette kevinoid +KevRitchie keyfileparameter keyhandler khansen00 @@ -443,7 +514,9 @@ launch.json ldspits lee303 Leonhardt +Libera.Chat libicu +LibraryImport libpsl libpsl-native libunwind8 @@ -459,18 +532,25 @@ lukexjeremy lupino3 lynda.com lzybkr +m1k0net M1kep mababio macos macports maertendmsft mahawar +mailmap Markdig.Signed +markdown.yml +manifest.spdx.json markekraus marktiedemann Marusyk MarvTheRobot +mattifestation +matt9ucci mcbobke +mcr.microsoft.com md meir017 memberresolution @@ -479,14 +559,19 @@ messageanalyzer metadata metadata.json miaromero +michaeltlombardi microsoft Microsoft.ApplicationInsights Microsoft.CodeAnalysis.CSharp +Microsoft.CodeAnalysis.NetAnalyzers microsoft.com +Microsoft.Management microsoft.management.infrastructure.cimcmdlets microsoft.management.infrastructure.native +Microsoft.Management.Infrastructure.Runtime.Win microsoft.net.test.sdk microsoft.powershell.archive +Microsoft.PowerShell.Commands microsoft.powershell.commands.diagnostics microsoft.powershell.commands.management microsoft.powershell.commands.utility @@ -501,14 +586,19 @@ microsoft.powershell.markdownrender microsoft.powershell.psreadline microsoft.powershell.security microsoft.powershell.utility +Microsoft.Security.Extensions +Microsoft.WSMan microsoft.wsman.management microsoft.wsman.runtime mikeTWC1984 mirichmo mjanko5 mkdir +mkht mklement0 +ModuleCmdletBase.cs MohiTheFish +Molkree move-itemproperty ms-psrp msbuild @@ -524,9 +614,12 @@ mwrock myget namedpipe nameof +NameObscurerTelemetryInitializer namespace nano nanoserver +NativeCommandProcessor.cs +NativeCultureResolver nativeexecution net5.0 netcoreapp5.0 @@ -554,7 +647,10 @@ new-winevent new-wsmaninstance new-wsmansessionoption NextTurn +ngharo +Newtonsoft.Json NJsonSchema +nohwnd NoMoreFood non-22 non-cim @@ -586,6 +682,7 @@ openssh openssl opensuse oss +OutputType p1 packagemanagement PackageVersion @@ -593,8 +690,11 @@ parameshbabu parameterbinderbase parameterbindercontroller parameterbinding +ParenExpression ParseError.ToString +Path.Join pathresolution +PathResolvedToMultiple patochun patwardhan paulhigin @@ -603,7 +703,11 @@ payette perf perfview perfview.exe +peter-evans petseral +PingPathCommand.cs +pinvoke +pinvokes plaintext pluggable pluralsight @@ -624,12 +728,15 @@ powershellgallery powershellget powershellmagazine.com powershellninja +powershellpr0mpt powershellproperties ppadmavilasom pre-build pre-compiled +pre-defined pre-generated pre-installed +pre-parse pre-release pre-releases pre-requisites @@ -637,6 +744,7 @@ prepend preprocessor preview.1 preview.2 +preview.2.22153.17 preview.3 preview.4 preview.5 @@ -645,18 +753,26 @@ preview.5.20278.13 preview.5.20269.29 preview.5.20268.9 preview.5.20272.6 +preview.5.22307.18 preview.6 preview.6.20318.15 +preview.6.21355.2 preview.7 preview.7.20356.2 preview.7.20358.6 preview.7.20364.3 preview.7.20366.2 preview.7.20366.15 +preview.7.22377.5 preview.4.20258.7 preview.4.20229.10 +preview.4.22252.9 +preview.8 preview1-24530-04 +preview1.22217.1 preview7 +ProcessorArchitecture +ProductCode productversion program.cs prototyyppi @@ -675,8 +791,10 @@ PSGalleryModules psm1 psobject psobjects +psoptions.json psproxyjobs psreadline +psresourceget psrp.windows psscriptanalyzer pssessionconfiguration @@ -690,13 +808,19 @@ pvs-studio pwd pwrshplughin.dll pwsh +pwsh.deps.json qmfrederik raghav710 +Random.Shared RandomNoun7 +RandomNumberGenerator.Fill raspbian rc rc.1 +rc.1.21455.2 +rc.1.21458.32 rc.2 +rc.2.22477.20 rc.3 rc2-24027 rc3-24011 @@ -706,11 +830,13 @@ readme.md readonly ReadyToRun rebase +rebase.yml rebasing receive-pssession recurse reddit redhat +redirections redistributable redistributables register-argumentcompleter @@ -721,6 +847,8 @@ register-packagesource register-psrepository registryprovider relationlink +releaseTools.psm1 +RemoteSessionNamedPipe remotesigned remoting remove-ciminstance @@ -758,13 +886,17 @@ rkitover robo210 ronn rpalo +rpolley +runas runspace runspaceinit runspaces runtime runtimes +Ryan-Hutchison-USAF SA1026CodeMustNotContainSpaceAfterNewKeywordInImplicitlyTypedArrayAllocation Saancreed +SafeRegistryHandle sample-dotnet1 sample-dotnet2 sarithsutha @@ -778,6 +910,7 @@ scriptblock securestring seemethere select-xml +SemanticChecks semver serverless sessionid @@ -800,6 +933,7 @@ set-wsmaninstance set-wsmanquickconfig sethvs setversionvariables +sha256 ShaydeNofziger shellexecute shouldbeerrorid @@ -808,6 +942,7 @@ Shriram0908 silijon simonwahlin singleline +sles15 smes snapcraft snapin @@ -825,6 +960,7 @@ start-codecoveragerun start-pspester stdin stevel-msft +StevenLiekens stevend811 stknohg strawgate @@ -834,6 +970,7 @@ string.split stringbuilder stuntguy3000 StyleCop +subfolder submodule submodules sudo @@ -851,6 +988,7 @@ System.IO.Packaging System.InvalidOperationException system.manage system.management.automation +System.Management.Automation.utils systemd SytzeAndr tabcompletion @@ -861,6 +999,7 @@ TargetFramework test-modulemanifest test-pssessionconfigurationfile test-scriptfileinfo +tar.gz test.ps1 test.txt. Tests.ps1 @@ -868,6 +1007,7 @@ test1.txt test2.txt testcase testdrive +TestPathCommand.cs tests.zip tgz theflyingcorpse @@ -894,7 +1034,10 @@ toolset tracesource travisez13 travisty +trossr32 truher +TSAUpload +turbedi TValue tylerleonhardt typecataloggen @@ -903,9 +1046,11 @@ typegen typematch t_ ubuntu +ubuntu22.04 uint un-versioned unicode +UnixSocket unregister-event unregister-packagesource unregister-psrepository @@ -913,12 +1058,15 @@ unregister-pssessionconfiguration unregistering untracked unvalidated +UpdateDotnetRuntime.ps1 update-formatdata update-modulemanifest update-scriptfileinfo update-typedata uri +urizen-source urls +UseMU userdata uservoice utf-8 @@ -933,6 +1081,7 @@ v0.4.0 v0.5.0 v0.6.0 v141 +v2 v3 v4 v5 @@ -955,11 +1104,23 @@ v6.2.4 v7.0.0 v7.0.3 v7.0.4 +v7.0.9 v7.1.0 +v7.1.6 +v7.1.7 +v7.2.2 +v7.2.6 +v7.3.0 +v7.4.0 +v7.0.12 +v7.0.13 validatenotnullorempty +ValidateSet +varunsh-coder versioned versioning vexx32 +Virtualization visualstudio vmsilvamolina vorobev @@ -971,6 +1132,8 @@ walkthrough webcmdlets weblistener webrequest +webrequestpscmdlet.common.cs +webresponseobject.common weltner wesholton84 wget @@ -981,11 +1144,16 @@ wildcards win32 win32-openssh win7 +win8 windos +windows.json windowspsmodulepath windowsversion winrm wix +wmentha +WNetGetConnection +WNetAddConnection2 worrenb wpr wprui.exe @@ -1008,8 +1176,10 @@ yecril71pl yml youtube Youssef1313 +Yulv-git zackjknight ComInterop +ryneandal runtime#33060 vexx32 perf @@ -1038,6 +1208,7 @@ unvalidated Geweldig mjanko5 v7.0.0 +v7.0.10 renehernandez ece-jacob-scott st0le @@ -1055,6 +1226,126 @@ authenticode env MarianoAlipi Microsoft.PowerShell.Native +davidBar-On +parameterized +misconfigured +hez2010 +ZhiZe-ZG +SecureStringHelper.FromPlainTextString +ProcessBaseCommand.AllProcesses +Parser.cs +MultipleServiceCommandBase.AllServices +JustinGrote +Newtonsoft.Json +minSize +WGs +wg-definitions +thejasonhelmick +winps +componentization +CimCmdlets +Microsoft.PowerShell.Host +PSDiagnostics +nightlies +wg +Visio +triaged +lifecycle +v2.0.5 +mutex +gukoff +dinhngtu +globbed +octos4murai +PSCommand +System.Management.Automation.ICommandRuntime +AppDomain.CreateDomain +AppDomain.Unload +ProcessModule.FileName +Environment.ProcessPath +PSUtils.GetMainModule +schuelermine +SupportsShouldProcess +Start-PSBootstrap +DotnetMetadataRuntime.json +deps.json +Jaykul +eltociear +consolehost.proto +IDisposable +ConvertToJsonCommand +CommandPathSearch +UseCoalesceExpression +UseSystemHashCode +UseCoalesceExpressionForNullable +substring +RemoveAll +MakeFieldReadonly +Microsoft.Management.UI.Internal +StringComparison +osx-arm64 +crossgen2 +MartinGC94 +BrannenGH +SergeyZalyadeev +KiwiThePoodle +Thomas-Yu +cgmanifest.json +mcr.microsoft.com +global.json +tar.gz +psoptions.json +manifest.spdx.json +buildinfo +SKUs +vmImage +InternalCommands.cs +CommonCommandParameters.cs +preview.6.22352.1 +v2.2.6 +ResultsComparer +pre-defined +System.Runtime.CompilerServices.Unsafe +TabExpansion +PSv2 +System.Data.SqlClient +Microsoft.CSharp +v7.2.10 +7.2.x +v7.3.3 +http +webcmdlet +argumentexception.throwifnullorempty +bitconverter.tostring +convert.tohexstring +requires.notnullorempty +argumentoutofrangeexception.throwifnegativeorzero +callerargumentexpression +requires.notnull +argumentnullexception +throwifnull +process.cs +setrequestcontent +streamhelper.cs +invokerestmethodcommand.common.cs +gethttpmethod +httpmethod +removenulls +notnull +argumentnullexception.throwifnull +disable_telemetry +langversion +microsoft.extensions.objectpool +microsoft.codeanalysis.analyzers +benchmarkdotnet +winforms +MicrosoftDocs +about_Scripts +debugging-from-commandline +about_Object_Creation +about_Functions_Advanced +Microsoft.PowerShell.SDK +NuGet.org. - CHANGELOG.md aavdberg asrosent @@ -1094,6 +1385,7 @@ weltkante kilasuit tnieto88 Orca88 +OrderBy centreboard romero126 Greg-Smulko @@ -1123,7 +1415,9 @@ Francisco-Gamino adamdriscoll analytics deserialized +string.Join string.Split +StringSplitOptions.TrimEntries Dictionary.TryAdd Environment.NewLine ParseError.ToString @@ -1146,6 +1440,35 @@ SetVersionVariables yml DateTime DeploymentScripts +GetValues +GetNames +SessionStateStrings +Enum.HasFlags +ConsoleInfoErrorStrings.resx +ContentHelper.Common.cs +FusionAssemblyIdentity +GlobalAssemblyCache +StringManipulationHelper +testexe.exe +echocmdline +MemoryExtensions.IndexOfAny +PSv2CompletionCompleter +RemoteRunspacePoolInternal.cs +PSVersionInfo +WildcardPattern +UTF8Encoding +PowerShell.Core.Instrumentation.man +Encoding.Default +WinTrust +System.Runtime.CompilerServices.Unsafe +azCopy +APISets +ApiScan +System.Data.SqlClient +minimatch +2.final +SessionStateInternal +Microsoft.PowerShell.SDK Markdig.Signed - docs/debugging/README.md corehost @@ -1197,7 +1520,9 @@ ini package.json jcotton42 RPMs - - CHANGELOG/preview.md +PSDesiredStateConfiguration +dotnet5 + - CHANGELOG/7.2.md Gimly jborean93 mkswd @@ -1291,9 +1616,141 @@ powershell.config.json romero126 boolean rtm.20526.5 - +dbaileyut +un-localized +awakecoding +bcwood +ThrowTerminatingError +DoesNotReturn +GetValueOrDefault +PSLanguageMode +adamsitnik +msixbundle +PowerShell-Native#70 +AppxManifest.xml +preview.9 +preview.10 +ArmaanMcleod +entrypoint +lselden +SethFalco +CodeQL +slowy07 +rc.2.21505.57 +ThirdPartyNotices +ThirdPartyNotices.txt +cgmanifest.json +buildinfo +tar.gz +psoptions.json +manifest.spdx.json +vPack +kondratyev-nv +v7.2.0 +v7.2.3 +cgmanifest.json +pwsh.exe +6.0.100-rtm.21527.11 +6.0.100-rc.2.21505.57 +ThirdPartyNotices.txt +rtm.21527.11 +SKUs +vmImage +Ubuntu22.04 - CHANGELOG/7.0.md codesign release-BuildJson yml +dotnet5 +buildinfo +SKUs +CGManifest +vmImage +ci.psm1 +centos-7 +PSDesiredStateConfiguration +NoLanguage +createdump +vPack +PkgES + - test/perf/benchmarks/README.md +benchmarked +BenchmarkDotNet + - docs/community/working-group-definitions.md +gaelcolas +jdhitsolutions +jhoneill +kilasuit +michaeltlombardi +SeeminglyScience +TobiasPSP + - CHANGELOG/7.3.md +ayousuf23 +AzCopy.exe +hammy3502 +PowerShellExecutionHelper.cs +ClientRemotePowerShell +DOTNET_ROOT +SkipExperimentalFeatureGeneration +AzCopy +Start-PSBootStrap +precheck +SKUs +powershell.config.json +Microsoft.PowerShell.GlobalTool.Shim.csproj +InvokeCommand +UseDotNet +vmImage +NoLanguage +GetValueOrDefault +kondratyev-nv +penimc_cor3.dll +PkgES +v7.2.0 +preview.9 +pwsh.exe +XunitXml.TestLogger +rtm.21527.11 +ThirdPartyNotices.txt +buildinfo +tar.gz +rc.2.21505.57 +psoptions.json +manifest.spdx.json +AzureFileCopy +vPack +dotnet5 +buildinfo +SKUs +CGManifest +vmImage +ci.psm1 +jcotton42 centos-7 +Security.types.ps1xml +optout + - ADOPTERS.md +MicrosoftPowerBIMgmt + - tools/clearlyDefined/readme.md +ClearlyDefined + - CHANGELOG/preview.md +stevenebutler +spaette +syntax-tm +URIs +typeDataXmlLoader.cs +GetResponseObject +ContentHelper +BasicHtmlWebResponseObject +WebRequestSession.cs +dkattan +preview.3.23178.7 +PoolNames +techguy16 +sdwheeler +MicrosoftDocs +about_Scripts +about_Object_Creation +about_Functions_Advanced +Microsoft.PowerShell.SDK +NuGet.org. diff --git a/.vsts-ci/install-ps.yml b/.vsts-ci/install-ps.yml index 72f42551162..7190e228578 100644 --- a/.vsts-ci/install-ps.yml +++ b/.vsts-ci/install-ps.yml @@ -9,13 +9,8 @@ trigger: - feature* paths: include: - - /tools/install-powershell.sh - - /tools/installpsh-amazonlinux.sh - - /tools/installpsh-debian.sh - - /tools/installpsh-osx.sh - - /tools/installpsh-redhat.sh - - /tools/installpsh-suse.sh - - /tools/install-powershell.ps1 + - /tools/install-powershell.* + - /tools/installpsh-*.sh - /.vsts-ci/install-ps.yml pr: branches: @@ -26,11 +21,7 @@ pr: paths: include: - /tools/install-powershell.sh - - /tools/installpsh-amazonlinux.sh - - /tools/installpsh-debian.sh - - /tools/installpsh-osx.sh - - /tools/installpsh-redhat.sh - - /tools/installpsh-suse.sh + - /tools/installpsh-*.sh - /tools/install-powershell.ps1 - /.vsts-ci/install-ps.yml @@ -48,7 +39,19 @@ phases: jobName: InstallPowerShellUbuntu pool: ubuntu-latest verification: | - if ([Version]"$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor).$($PSVersionTable.PSVersion.Patch)" -lt [version]"6.2.0") + if ([Version]"$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor).$($PSVersionTable.PSVersion.Patch)" -lt [version]"7.3.0") + { + throw "powershell was not upgraded: $($PSVersionTable.PSVersion)" + } + +- template: templates/install-ps-phase.yml + parameters: + scriptName: sudo ./tools/install-powershell.sh + jobName: InstallPowerShellMariner2 + pool: ubuntu-latest + container: mcr.microsoft.com/powershell/test-deps:mariner-2.0 + verification: | + if ([Version]"$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor).$($PSVersionTable.PSVersion.Patch)" -lt [version]"7.3.0") { throw "powershell was not upgraded: $($PSVersionTable.PSVersion)" } @@ -60,7 +63,7 @@ phases: pool: ubuntu-latest container: pshorg/powershellcommunity-test-deps:amazonlinux-2.0 verification: | - if ([Version]"$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor).$($PSVersionTable.PSVersion.Patch)" -lt [version]"6.2.0") + if ([Version]"$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor).$($PSVersionTable.PSVersion.Patch)" -lt [version]"7.3.0") { throw "powershell was not upgraded: $($PSVersionTable.PSVersion)" } @@ -72,7 +75,7 @@ phases: pool: ubuntu-latest container: pshorg/powershellcommunity-test-deps:amazonlinux-2.0 verification: | - if ([Version]"$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor).$($PSVersionTable.PSVersion.Patch)" -lt [version]"6.2.0") + if ([Version]"$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor).$($PSVersionTable.PSVersion.Patch)" -lt [version]"7.3.0") { throw "powershell was not upgraded: $($PSVersionTable.PSVersion)" } @@ -105,7 +108,7 @@ phases: jobName: InstallPowerShellMacOS pool: macOS-latest verification: | - if ([Version]"$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor).$($PSVersionTable.PSVersion.Patch)" -lt [version]"6.2.0") + if ([Version]"$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor).$($PSVersionTable.PSVersion.Patch)" -lt [version]"7.3.0") { # The script does not upgrade on mac os https://github.com/PowerShell/PowerShell/issues/9322 Write-Warning "powershell was not upgraded: $($PSVersionTable.PSVersion)" @@ -124,7 +127,7 @@ phases: pool: ubuntu-latest verification: | Write-Verbose $PSVersionTable.PSVersion -verbose - if ([Version]"$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor).$($PSVersionTable.PSVersion.Patch)" -lt [version]"7.0.0") + if ([Version]"$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor).$($PSVersionTable.PSVersion.Patch)" -lt [version]"7.3.0") { throw "powershell was not upgraded: $($PSVersionTable.PSVersion)" } diff --git a/.vsts-ci/linux-daily.yml b/.vsts-ci/linux-daily.yml index 6ab1832dfd9..c1dd96fd0b4 100644 --- a/.vsts-ci/linux-daily.yml +++ b/.vsts-ci/linux-daily.yml @@ -18,17 +18,9 @@ pr: branches: include: - master - - release* - - feature* paths: include: - - '*' - exclude: - - tools/releaseBuild/* - - tools/releaseBuild/azureDevOps/templates/* - - /.vsts-ci/misc-analysis.yml - - /.github/ISSUE_TEMPLATE/* - - /.dependabot/config.yml + - .vsts-ci/linux-daily.yml variables: DOTNET_CLI_TELEMETRY_OPTOUT: 1 @@ -47,7 +39,7 @@ stages: jobs: - template: templates/ci-build.yml parameters: - pool: ubuntu-16.04 + pool: ubuntu-20.04 jobName: linux_build displayName: linux Build @@ -55,13 +47,14 @@ stages: displayName: Test for Linux jobs: - job: linux_test + timeoutInMinutes: 90 pool: - vmImage: ubuntu-16.04 + vmImage: ubuntu-20.04 displayName: Linux Test steps: - pwsh: | - Get-ChildItem -Path env: + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose displayName: Capture Environment condition: succeededOrFailed() @@ -149,7 +142,7 @@ stages: - job: CodeCovTestPackage displayName: CodeCoverage and Test Packages pool: - vmImage: ubuntu-16.04 + vmImage: ubuntu-20.04 steps: - pwsh: | Import-Module .\tools\ci.psm1 diff --git a/.vsts-ci/linux-internal.yml b/.vsts-ci/linux-internal.yml new file mode 100644 index 00000000000..6286a03fb52 --- /dev/null +++ b/.vsts-ci/linux-internal.yml @@ -0,0 +1,116 @@ +# 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.json + - 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 + # 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 + 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 be7de6d8fc5..b386b9c7eb3 100644 --- a/.vsts-ci/linux.yml +++ b/.vsts-ci/linux.yml @@ -1,3 +1,12 @@ +parameters: + - name: ContainerPattern + displayName: | + Pattern to match JobName of the container. + Update this to force a container. + `.` will match everything + type: string + default: . + name: PR-$(System.PullRequest.PullRequestNumber)-$(Date:yyyyMMdd)$(Rev:.rr) trigger: # Batch merge builds together while a merge build is running @@ -11,9 +20,12 @@ trigger: include: - '*' exclude: - - /.vsts-ci/misc-analysis.yml - - /.github/ISSUE_TEMPLATE/* - - /.dependabot/config.yml + - .vsts-ci/misc-analysis.yml + - .github/ISSUE_TEMPLATE/* + - .github/workflows/* + - .dependabot/config.yml + - .pipelines/* + - test/perf/* pr: branches: include: @@ -22,16 +34,16 @@ pr: - feature* paths: include: - - '*' - exclude: - - test/common/markdown/* - - tools/releaseBuild/* - - tools/releaseBuild/azureDevOps/templates/* - - .vsts-ci/misc-analysis.yml - - .github/ISSUE_TEMPLATE/* - - .dependabot/config.yml - - .vsts-ci/windows.yml - - .vsts-ci/windows/* + - .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 @@ -39,66 +51,30 @@ variables: # 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 + nugetMultiFeedWarnLevel: none resources: -- repo: self - clean: true + repositories: + - repository: Docker + type: github + endpoint: PowerShell + name: PowerShell/PowerShell-Docker + ref: master stages: -- stage: BuildLinux +- stage: BuildLinuxStage displayName: Build for Linux jobs: - template: templates/ci-build.yml parameters: - pool: ubuntu-16.04 + pool: ubuntu-latest jobName: linux_build displayName: linux Build -- stage: TestLinux - displayName: Test for Linux +- stage: PackageLinux + displayName: Package Linux + dependsOn: ["BuildLinuxStage"] jobs: - - template: templates/nix-test.yml + - template: linux/templates/packaging.yml parameters: - name: Linux - pool: ubuntu-16.04 - purpose: UnelevatedPesterTests - tagSet: CI - - - template: templates/nix-test.yml - parameters: - name: Linux - pool: ubuntu-16.04 - purpose: ElevatedPesterTests - tagSet: CI - - - template: templates/nix-test.yml - parameters: - name: Linux - pool: ubuntu-16.04 - purpose: UnelevatedPesterTests - tagSet: Others - - - template: templates/nix-test.yml - parameters: - name: Linux - pool: ubuntu-16.04 - purpose: ElevatedPesterTests - tagSet: Others - - - template: templates/verify-xunit.yml - parameters: - pool: ubuntu-16.04 - -- stage: CodeCovTestPackage - displayName: CodeCoverage and Test Packages - dependsOn: [] # by specifying an empty array, this stage doesn't depend on the stage before it - jobs: - - job: CodeCovTestPackage - displayName: CodeCoverage and Test Packages - pool: - vmImage: ubuntu-16.04 - steps: - - pwsh: | - Import-Module .\tools\ci.psm1 - New-CodeCoverageAndTestPackage - displayName: CodeCoverage and Test Package + pool: ubuntu-latest diff --git a/.vsts-ci/linux/templates/packaging.yml b/.vsts-ci/linux/templates/packaging.yml new file mode 100644 index 00000000000..8f77b8e24a0 --- /dev/null +++ b/.vsts-ci/linux/templates/packaging.yml @@ -0,0 +1,99 @@ +parameters: + pool: 'ubuntu-20.04' + parentJobs: [] + name: 'Linux' + +jobs: +- job: ${{ parameters.name }}_packaging + dependsOn: + ${{ parameters.parentJobs }} + pool: + vmImage: ${{ parameters.pool }} + + 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 + 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: | + Import-Module .\build.psm1 + Start-PSBootstrap -Scenario Package + displayName: Bootstrap + + - pwsh: | + Import-Module ./build.psm1 + displayName: 'Capture Artifacts Directory' + continueOnError: true + + - task: ExtractFiles@1 + displayName: 'Extract Build ZIP' + inputs: + archiveFilePatterns: '$(System.ArtifactsDirectory)/build/build.zip' + destinationFolder: '$(System.ArtifactsDirectory)/bins' + + - bash: | + find "$(System.ArtifactsDirectory)/bins" -type d -exec chmod +rwx {} \; + find "$(System.ArtifactsDirectory)/bins" -type f -exec chmod +rw {} \; + displayName: 'Fix permissions' + continueOnError: true + + - pwsh: | + Get-ChildItem "$(System.ArtifactsDirectory)\bins\*" -Recurse -ErrorAction SilentlyContinue + displayName: 'Capture Extracted Build ZIP' + continueOnError: true + + - pwsh: | + Import-Module .\tools\ci.psm1 + Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' + $options = (Get-PSOptions) + $rootPath = '$(System.ArtifactsDirectory)\bins' + $originalRootPath = Split-Path -path $options.Output + $path = Join-Path -path $rootPath -ChildPath (split-path -leaf -path $originalRootPath) + $pwshPath = Join-Path -path $path -ChildPath 'pwsh' + chmod a+x $pwshPath + $options.Output = $pwshPath + Set-PSOptions $options + Invoke-CIFinish + displayName: Packaging Tests + condition: succeeded() + + - pwsh: | + Get-ChildItem "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}\*.deb" -Recurse | ForEach-Object { + $packagePath = $_.FullName + Write-Host "Uploading $packagePath" + Write-Host "##vso[artifact.upload containerfolder=deb;artifactname=deb]$packagePath" + } + Get-ChildItem "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}\*.rpm" -Recurse | ForEach-Object { + $packagePath = $_.FullName + Write-Host "Uploading $packagePath" + Write-Host "##vso[artifact.upload containerfolder=rpm;artifactname=rpm]$packagePath" + } + Get-ChildItem "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}\*.tar.gz" -Recurse | ForEach-Object { + $packagePath = $_.FullName + Write-Host "Uploading $packagePath" + Write-Host "##vso[artifact.upload containerfolder=rpm;artifactname=rpm]$packagePath" + } + displayName: Upload packages + retryCountOnTaskFailure: 2 diff --git a/.vsts-ci/mac.yml b/.vsts-ci/mac.yml index 5f487275bf2..05d6d71ea71 100644 --- a/.vsts-ci/mac.yml +++ b/.vsts-ci/mac.yml @@ -11,10 +11,13 @@ trigger: include: - '*' exclude: - - /tools/releaseBuild/**/* - - /.vsts-ci/misc-analysis.yml - - /.github/ISSUE_TEMPLATE/* - - /.dependabot/config.yml + - tools/releaseBuild/**/* + - .vsts-ci/misc-analysis.yml + - .github/ISSUE_TEMPLATE/* + - .github/workflows/* + - .dependabot/config.yml + - .pipelines/* + - test/perf/* pr: branches: include: @@ -25,14 +28,22 @@ pr: include: - '*' exclude: - - test/common/markdown/* - - .vsts-ci/misc-analysis.yml - - .github/ISSUE_TEMPLATE/* - .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/packaging/* - tools/releaseBuild/* - tools/releaseBuild/azureDevOps/templates/* - - /.vsts-ci/windows.yml - - /.vsts-ci/windows/* + - README.md + - .spelling + - .pipelines/* variables: DOTNET_CLI_TELEMETRY_OPTOUT: 1 @@ -42,6 +53,7 @@ variables: # Turn off Homebrew analytics HOMEBREW_NO_ANALYTICS: 1 __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none resources: - repo: self @@ -84,20 +96,20 @@ stages: parameters: pool: macOS-latest -- stage: CodeCovTestPackage - displayName: CodeCoverage and Test Packages - dependsOn: [] # by specifying an empty array, this stage doesn't depend on the stage before it +- stage: PackageMac + dependsOn: ['BuildMac'] + displayName: Package macOS (bootstrap only) jobs: - - job: CodeCovTestPackage - displayName: CodeCoverage and Test Packages - pool: - vmImage: macOS-latest - steps: - - pwsh: | - # Remove old .NET SDKs - if (Test-Path -Path $HOME/.dotnet) { - Remove-Item $HOME/.dotnet -Recurse -Force - } - Import-Module .\tools\ci.psm1 - New-CodeCoverageAndTestPackage - displayName: CodeCoverage and Test Package + - job: macos_packaging + pool: + vmImage: macOS-latest + + displayName: macOS packaging (bootstrap only) + steps: + - checkout: self + clean: true + - pwsh: | + import-module ./build.psm1 + 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 8c81c604270..00000000000 --- a/.vsts-ci/misc-analysis.yml +++ /dev/null @@ -1,109 +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* - -pr: - branches: - include: - - master - - release* - - feature* - -resources: - repositories: - - repository: ComplianceRepo - type: github - endpoint: PowerShell - name: PowerShell/compliance - ref: master - -variables: - - name: repoFolder - value: PowerShell - -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 - -- job: Linux_CI - displayName: Markdown and Common Tests - - pool: - vmImage: ubuntu-16.04 - - variables: - - name: repoPath - value: $(Agent.BuildDirectory)/$(repoFolder) - - steps: - - checkout: self - clean: true - path: $(repoFolder) - - - checkout: ComplianceRepo - - - powershell: | - Get-ChildItem -Path env: - displayName: Capture Environment - condition: succeededOrFailed() - - - powershell: | - Install-module Pester -Scope CurrentUser -Force -MaximumVersion 4.99 - displayName: Install Pester - condition: succeededOrFailed() - - - bash: | - curl -o- --progress-bar -L https://yarnpkg.com/install.sh | bash - displayName: Bootstrap Yarn - condition: succeededOrFailed() - - - bash: | - sudo yarn global add markdown-spellcheck@0.11.0 - displayName: Install mdspell - condition: succeededOrFailed() - - - bash: | - mdspell '**/*.md' '!**/Pester/**/*.md' --ignore-numbers --ignore-acronyms --report --en-us; - displayName: Test Spelling in Markdown - condition: succeededOrFailed() - workingDirectory: '$(repoPath)' - - - ${{ if not(contains(variables['SYSTEM.COLLECTIONURI'],'mscodehub')) }}: - - pwsh: | - Import-module ./build.psm1 - $path = Join-Path -Path $pwd -ChildPath './commonTestResults.xml' - $results = invoke-pester -Script ./test/common -OutputFile $path -OutputFormat NUnitXml -PassThru - Write-Host "##vso[results.publish type=NUnit;mergeResults=true;runTitle=Common Tests;publishRunAttachments=true;resultFiles=$path;]" - if($results.TotalCount -eq 0 -or $results.FailedCount -gt 0) - { - throw "Markdown tests failed" - } - displayName: Run Common Tests - condition: succeededOrFailed() - workingDirectory: '$(repoPath)' - - - template: dailyBuildCompliance.yml@ComplianceRepo - parameters: - sourceScanPath: '$(repoPath)' diff --git a/.vsts-ci/misc-analysis/generateMarkdownMatrix.yml b/.vsts-ci/misc-analysis/generateMarkdownMatrix.yml new file mode 100644 index 00000000000..56a43accd55 --- /dev/null +++ b/.vsts-ci/misc-analysis/generateMarkdownMatrix.yml @@ -0,0 +1,46 @@ +parameters: + - name: jobName + - name: taskName + +jobs: +- job: ${{ parameters.jobName }} + displayName: Generate Markdown Matrix + + pool: + vmImage: ubuntu-20.04 + + variables: + - name: repoPath + value: $(Agent.BuildDirectory)/$(repoFolder) + + steps: + - checkout: self + clean: true + path: $(repoFolder) + + - powershell: | + $matrix = @{} + $matrix += @{ + 'root' = @{ + markdown_folder = "$(repoPath)" + markdown_recurse = $false + } + } + Get-ChildItem -path '$(repoPath)' -Directory | Foreach-Object { + $folder = $_ + $matrix += @{ + $_.Name = @{ + markdown_folder = $_.fullName + markdown_recurse = $true + } + } + } + + $matrixJson = $matrix | ConvertTo-Json -Compress + $variableName = "matrix" + $command = "vso[task.setvariable variable=$variableName;isoutput=true]$($matrixJson)" + Write-Verbose "sending command: '$command'" + Write-Host "##$command" + displayName: Create Matrix + condition: succeededOrFailed() + name: ${{ parameters.taskName }} diff --git a/.vsts-ci/psresourceget-acr.yml b/.vsts-ci/psresourceget-acr.yml new file mode 100644 index 00000000000..1a24983b5b5 --- /dev/null +++ b/.vsts-ci/psresourceget-acr.yml @@ -0,0 +1,157 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +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 + - test/perf/* + - .pipelines/* +pr: + branches: + include: + - master + - release* + - feature* + paths: + include: + - '*' + 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 + __SuppressAnsiEscapeSequences: 1 + NugetSecurityAnalysisWarningLevel: none + nugetMultiFeedWarnLevel: none + +resources: +- repo: self + clean: true + +stages: +- stage: BuildWin + displayName: Build for Windows + jobs: + - template: templates/ci-build.yml + +- stage: TestWin + displayName: Test PSResourceGetACR + jobs: + - job: win_test_ACR + displayName: PSResourceGet ACR Tests + pool: + vmImage: 'windows-latest' + + steps: + - pwsh: | + Get-ChildItem -Path env: + 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: | + # 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: | + Install-Module -Name 'Microsoft.PowerShell.SecretManagement' -force -SkipPublisherCheck -AllowClobber + Install-Module -Name 'Microsoft.PowerShell.SecretStore' -force -SkipPublisherCheck -AllowClobber + $vaultPassword = ConvertTo-SecureString $("a!!"+ (Get-Random -Maximum ([int]::MaxValue))) -AsPlainText -Force + Set-SecretStoreConfiguration -Authentication None -Interaction None -Confirm:$false -Password $vaultPassword + Register-SecretVault -Name SecretStore -ModuleName Microsoft.PowerShell.SecretStore -DefaultVault + displayName: 'Install Secret store' + + - task: AzurePowerShell@5 + inputs: + azureSubscription: PSResourceGetACR + azurePowerShellVersion: LatestVersion + ScriptType: InlineScript + pwsh: true + inline: | + Write-Verbose -Verbose "Getting Azure Container Registry" + Get-AzContainerRegistry -ResourceGroupName 'PSResourceGet' -Name 'psresourcegettest' | Select-Object -Property * + Write-Verbose -Verbose "Setting up secret for Azure Container Registry" + $azt = Get-AzAccessToken + $tenantId = $azt.TenantID + Set-Secret -Name $tenantId -Secret $azt.Token -Verbose + $vstsCommandString = "vso[task.setvariable variable=TenantId]$tenantId" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + displayName: 'Setup Azure Container Registry secret' + + - pwsh: | + Import-Module .\build.psm1 -force + Import-Module .\tools\ci.psm1 + Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' + $options = (Get-PSOptions) + $path = split-path -path $options.Output + $rootPath = split-Path -path $path + Expand-Archive -Path '$(System.ArtifactsDirectory)\build\build.zip' -DestinationPath $rootPath -Force + + $pwshExe = Get-ChildItem -Path $rootPath -Recurse -Filter pwsh.exe | Select-Object -First 1 + + $outputFilePath = "$(Build.SourcesDirectory)\test\powershell\Modules\Microsoft.PowerShell.PSResourceGet\ACRTests.xml" + $cmdline = "`$env:ACRTESTS = 'true'; Invoke-Pester -Path '$(Build.SourcesDirectory)\test\powershell\Modules\Microsoft.PowerShell.PSResourceGet\Microsoft.PowerShell.PSResourceGet.Tests.ps1' -TestName 'PSResourceGet - ACR tests' -OutputFile $outputFilePath -OutputFormat NUnitXml" + Write-Verbose -Verbose "Running $cmdline" + + & $pwshExe -Command $cmdline + + 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 016f3bfddca..2eda2a18276 100644 --- a/.vsts-ci/sshremoting-tests.yml +++ b/.vsts-ci/sshremoting-tests.yml @@ -23,23 +23,35 @@ pr: - '/test/SSHRemoting/*' variables: - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - POWERSHELL_TELEMETRY_OPTOUT: 1 + - name: DOTNET_CLI_TELEMETRY_OPTOUT + 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 - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 - __SuppressAnsiEscapeSequences: 1 + - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE + value: 1 + - name: __SuppressAnsiEscapeSequences + value: 1 + - name: NugetSecurityAnalysisWarningLevel + value: none +# Prevents auto-injection of nuget-security-analysis@0 + - name: skipNugetSecurityAnalysis + value: true + resources: - repo: self clean: true jobs: - job: SSHRemotingTests + pool: + vmImage: ubuntu-20.04 container: mcr.microsoft.com/powershell/test-deps:ubuntu-18.04 displayName: SSH Remoting Tests steps: - pwsh: | - Get-ChildItem -Path env: + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose displayName: Capture Environment condition: succeededOrFailed() diff --git a/.vsts-ci/templates/ci-build.yml b/.vsts-ci/templates/ci-build.yml index 9dc43bc8ebf..5ec458c3c5a 100644 --- a/.vsts-ci/templates/ci-build.yml +++ b/.vsts-ci/templates/ci-build.yml @@ -1,47 +1,84 @@ parameters: - pool: 'vs2017-win2016' - jobName: 'win_build' - displayName: Windows Build + - name: pool + default: 'windows-latest' + - name: imageName + default: 'PSWindows11-ARM64' + - name: jobName + default: 'win_build' + - name: displayName + default: Windows Build + - name: PoolType + default: AzDoHosted + type: string + values: + - AzDoHosted + - 1esHosted jobs: - job: ${{ parameters.jobName }} pool: - vmImage: ${{ parameters.pool }} + ${{ if eq( parameters.PoolType, 'AzDoHosted') }}: + vmImage: ${{ parameters.pool }} + ${{ else }}: + name: ${{ parameters.pool }} + demands: + - ImageOverride -equals ${{ parameters.imageName }} displayName: ${{ parameters.displayName }} steps: - powershell: | - Get-ChildItem -Path env: + [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 + + - checkout: self + fetchDepth: 1000 + + - pwsh: | + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose displayName: Capture Environment condition: succeededOrFailed() - - powershell: Write-Host "##vso[build.updatebuildnumber]$env:BUILD_SOURCEBRANCHNAME-$env:BUILD_SOURCEVERSION-$((get-date).ToString("yyyyMMddhhmmss"))" + - pwsh: Write-Host "##vso[build.updatebuildnumber]$env:BUILD_SOURCEBRANCHNAME-$env:BUILD_SOURCEVERSION-$((get-date).ToString("yyyyMMddhhmmss"))" displayName: Set Build Name for Non-PR condition: ne(variables['Build.Reason'], 'PullRequest') - - template: /tools/releaseBuild/azureDevOps/templates/insert-nuget-config-azfeed.yml + - ${{ if ne(variables['UseAzDevOpsFeed'], '') }}: + - template: /tools/releaseBuild/azureDevOps/templates/insert-nuget-config-azfeed.yml - - pwsh: | - if (Test-Path -Path $HOME/.dotnet) { - Remove-Item $HOME/.dotnet -Recurse -Force - } - displayName: Remove Old .NET SDKs - condition: succeededOrFailed() + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + useGlobalJson: true + packageType: 'sdk' - pwsh: | Import-Module .\tools\ci.psm1 Invoke-CIInstall -SkipUser + Write-Verbose -Verbose "Start Sync-PSTags" + Sync-PSTags -AddRemoteIfMissing + Write-Verbose -Verbose "End Sync-PSTags" displayName: Bootstrap condition: succeeded() - - powershell: | + - pwsh: | Import-Module .\tools\ci.psm1 Invoke-CIBuild displayName: Build condition: succeeded() - - powershell: | + - pwsh: | Import-Module .\tools\ci.psm1 Restore-PSOptions Invoke-CIxUnit -SkipFailing diff --git a/.vsts-ci/templates/credscan.yml b/.vsts-ci/templates/credscan.yml index f6ed5b8fd23..60094ff3d77 100644 --- a/.vsts-ci/templates/credscan.yml +++ b/.vsts-ci/templates/credscan.yml @@ -1,12 +1,12 @@ parameters: - pool: 'Hosted VS2017' + pool: 'windows-latest' jobName: 'credscan' displayName: Secret Scan jobs: - job: ${{ parameters.jobName }} pool: - name: ${{ parameters.pool }} + vmImage: ${{ parameters.pool }} displayName: ${{ parameters.displayName }} diff --git a/.vsts-ci/templates/install-ps-phase.yml b/.vsts-ci/templates/install-ps-phase.yml index f521cda0444..4e650273264 100644 --- a/.vsts-ci/templates/install-ps-phase.yml +++ b/.vsts-ci/templates/install-ps-phase.yml @@ -22,7 +22,7 @@ jobs: steps: - pwsh: | - Get-ChildItem -Path env: + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose displayName: Capture Environment condition: succeededOrFailed() diff --git a/.vsts-ci/templates/nanoserver.yml b/.vsts-ci/templates/nanoserver.yml index c989d01c2f8..ae9f639b3b2 100644 --- a/.vsts-ci/templates/nanoserver.yml +++ b/.vsts-ci/templates/nanoserver.yml @@ -1,5 +1,5 @@ parameters: - vmImage: 'windows-2019' + vmImage: 'windows-latest' jobName: 'Nanoserver_Tests' continueOnError: false diff --git a/.vsts-ci/templates/nix-test.yml b/.vsts-ci/templates/nix-test.yml index 6a1b6e6f9de..214ae14b2c6 100644 --- a/.vsts-ci/templates/nix-test.yml +++ b/.vsts-ci/templates/nix-test.yml @@ -1,78 +1,25 @@ parameters: pool: 'macOS-latest' - parentJobs: [] purpose: '' tagSet: 'CI' name: 'mac' jobs: - job: ${{ parameters.name }}_test_${{ parameters.purpose }}_${{ parameters.tagSet }} - dependsOn: - ${{ parameters.parentJobs }} + pool: vmImage: ${{ parameters.pool }} displayName: ${{ parameters.name }} Test - ${{ parameters.purpose }} - ${{ parameters.tagSet }} steps: - - pwsh: | - Get-ChildItem -Path env: - 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: | - if (Test-Path -Path $HOME/.dotnet) { - Remove-Item $HOME/.dotnet -Recurse -Force - } - displayName: Remove Old .NET SDKs - condition: succeededOrFailed() - - - pwsh: | - Import-Module .\tools\ci.psm1 - Invoke-CIInstall -SkipUser - displayName: Bootstrap - - - task: ExtractFiles@1 - displayName: 'Extract Build ZIP' - inputs: - archiveFilePatterns: '$(System.ArtifactsDirectory)/build/build.zip' - destinationFolder: '$(System.ArtifactsDirectory)/bins' - - - bash: | - find "$(System.ArtifactsDirectory)/bins" -type d -exec chmod +rwx {} \; - find "$(System.ArtifactsDirectory)/bins" -type f -exec chmod +rw {} \; - displayName: 'Fix permissions' - continueOnError: true - - - pwsh: | - Get-ChildItem "$(System.ArtifactsDirectory)\bins\*" -Recurse -ErrorAction SilentlyContinue - displayName: 'Capture Extracted Build ZIP' - continueOnError: true - - - pwsh: | - Import-Module .\tools\ci.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - $options = (Get-PSOptions) - $rootPath = '$(System.ArtifactsDirectory)\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 '${{ parameters.purpose }}' -TagSet '${{ parameters.tagSet }}' - displayName: Test - condition: succeeded() + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + useGlobalJson: true + packageType: 'sdk' + + - template: ./test/nix-test-steps.yml + parameters: + purpose: ${{ parameters.purpose }} + tagSet: ${{ parameters.tagSet }} diff --git a/.vsts-ci/templates/test/nix-container-test.yml b/.vsts-ci/templates/test/nix-container-test.yml new file mode 100644 index 00000000000..37c60a4c53b --- /dev/null +++ b/.vsts-ci/templates/test/nix-container-test.yml @@ -0,0 +1,36 @@ +parameters: + pool: 'macOS-latest' + purpose: '' + tagSet: 'CI' + name: 'mac' + +jobs: +- job: ${{ parameters.name }}_test_${{ parameters.purpose }}_${{ parameters.tagSet }} + + dependsOn: + - getContainerJob + + variables: + __INCONTAINER: 1 + getContainerJob: $[ dependencies.getContainerJob.outputs['getContainerTask.containerName'] ] + containerBuildName: $[ dependencies.getContainerJob.outputs['getContainerTask.containerBuildName'] ] + + container: $[ variables.getContainerJob ] + + pool: + vmImage: ${{ parameters.pool }} + + 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 }} + tagSet: ${{ parameters.tagSet }} + buildName: $(containerBuildName) diff --git a/.vsts-ci/templates/test/nix-test-steps.yml b/.vsts-ci/templates/test/nix-test-steps.yml new file mode 100644 index 00000000000..f15d59ea73a --- /dev/null +++ b/.vsts-ci/templates/test/nix-test-steps.yml @@ -0,0 +1,60 @@ +parameters: + purpose: '' + tagSet: 'CI' + buildName: 'Ubuntu' + +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/**/* + downloadPath: '$(System.ArtifactsDirectory)' + + - pwsh: | + Get-ChildItem "$(System.ArtifactsDirectory)\*" -Recurse + displayName: 'Capture Artifacts Directory' + continueOnError: true + + - pwsh: | + Import-Module .\tools\ci.psm1 + Invoke-CIInstall -SkipUser + displayName: Bootstrap + + - task: ExtractFiles@1 + displayName: 'Extract Build ZIP' + inputs: + archiveFilePatterns: '$(System.ArtifactsDirectory)/build/build.zip' + destinationFolder: '$(System.ArtifactsDirectory)/bins' + + - bash: | + find "$(System.ArtifactsDirectory)/bins" -type d -exec chmod +rwx {} \; + find "$(System.ArtifactsDirectory)/bins" -type f -exec chmod +rw {} \; + displayName: 'Fix permissions' + continueOnError: true + + - pwsh: | + Get-ChildItem "$(System.ArtifactsDirectory)\bins\*" -Recurse -ErrorAction SilentlyContinue + displayName: 'Capture Extracted Build ZIP' + continueOnError: true + + - pwsh: | + Import-Module .\tools\ci.psm1 + Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' + $options = (Get-PSOptions) + $rootPath = '$(System.ArtifactsDirectory)\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 '${{ parameters.purpose }}' -TagSet '${{ parameters.tagSet }}' -TitlePrefix '${{ parameters.buildName }}' + displayName: Test + condition: succeeded() diff --git a/.vsts-ci/templates/verify-xunit.yml b/.vsts-ci/templates/verify-xunit.yml index 9e09584d5c3..b43cb9339f9 100644 --- a/.vsts-ci/templates/verify-xunit.yml +++ b/.vsts-ci/templates/verify-xunit.yml @@ -1,6 +1,6 @@ parameters: parentJobs: [] - pool: 'vs2017-win2016' + pool: 'windows-latest' jobName: 'xunit_verify' jobs: @@ -19,12 +19,12 @@ jobs: xunit/**/* downloadPath: '$(System.ArtifactsDirectory)' - - powershell: | + - pwsh: | dir "$(System.ArtifactsDirectory)\*" -Recurse displayName: 'Capture artifacts directory' continueOnError: true - - powershell: | + - pwsh: | Import-Module .\tools\ci.psm1 $xUnitTestResultsFile = "$(System.ArtifactsDirectory)\xunit\xUnitTestResults.xml" diff --git a/.vsts-ci/templates/windows-test.yml b/.vsts-ci/templates/windows-test.yml index b021e45f000..22758e3953c 100644 --- a/.vsts-ci/templates/windows-test.yml +++ b/.vsts-ci/templates/windows-test.yml @@ -1,5 +1,6 @@ parameters: - pool: 'Hosted VS2017' + pool: 'windows-2019' + imageName: 'PSWindows11-ARM64' parentJobs: [] purpose: '' tagSet: 'CI' @@ -9,13 +10,34 @@ jobs: dependsOn: ${{ parameters.parentJobs }} pool: - name: ${{ parameters.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: + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose displayName: Capture Environment condition: succeededOrFailed() @@ -32,6 +54,13 @@ jobs: displayName: 'Capture Artifacts Directory' continueOnError: true + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + useGlobalJson: true + packageType: 'sdk' + workingDirectory: $(Build.SourcesDirectory)" + # must be run frow Windows PowerShell - powershell: | # Remove "Program Files\dotnet" from the env variable PATH, so old SDKs won't affect us. @@ -51,6 +80,7 @@ jobs: displayName: Bootstrap - pwsh: | + Import-Module .\build.psm1 -force Import-Module .\tools\ci.psm1 Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' $options = (Get-PSOptions) diff --git a/.vsts-ci/windows-arm64.yml b/.vsts-ci/windows-arm64.yml new file mode 100644 index 00000000000..be4cfcbaf4c --- /dev/null +++ b/.vsts-ci/windows-arm64.yml @@ -0,0 +1,95 @@ +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 + - test/perf/* +pr: + branches: + include: + - master + - release* + - feature* + paths: + include: + - '*' + exclude: + - .dependabot/config.yml + - .github/ISSUE_TEMPLATE/* + - .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 + +variables: + - name: GIT_CONFIG_PARAMETERS + value: "'core.autocrlf=false'" + - name: DOTNET_CLI_TELEMETRY_OPTOUT + 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 + value: 1 + - name: __SuppressAnsiEscapeSequences + value: 1 + - group: PoolNames + +resources: +- repo: self + clean: true + +stages: +- stage: BuildWin + displayName: Build for Windows + jobs: + - template: templates/ci-build.yml + parameters: + pool: $(armPool) + PoolType: 1esHosted + +- stage: TestWin + displayName: Test for Windows + jobs: + - template: templates/windows-test.yml + parameters: + purpose: UnelevatedPesterTests + tagSet: CI + pool: $(armPool) + + - template: templates/windows-test.yml + parameters: + purpose: ElevatedPesterTests + tagSet: CI + pool: $(armPool) + + - template: templates/windows-test.yml + parameters: + purpose: UnelevatedPesterTests + tagSet: Others + pool: $(armPool) + + - template: templates/windows-test.yml + parameters: + purpose: ElevatedPesterTests + tagSet: Others + pool: $(armPool) + + - template: templates/verify-xunit.yml diff --git a/.vsts-ci/windows-daily.yml b/.vsts-ci/windows-daily.yml index f15d0926d1d..59dd3ba2f36 100644 --- a/.vsts-ci/windows-daily.yml +++ b/.vsts-ci/windows-daily.yml @@ -48,18 +48,16 @@ stages: - stage: TestWin displayName: Test for Windows - variables: - - group: CLR-CAP jobs: - job: win_test pool: - vmImage: vs2017-win2016 + vmImage: windows-2019 displayName: Windows Test timeoutInMinutes: 90 steps: - pwsh: | - Get-ChildItem -Path env: + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose displayName: 'Capture Environment' condition: succeededOrFailed() @@ -77,26 +75,6 @@ stages: displayName: 'Capture Artifacts Directory' continueOnError: true - - pwsh: | - $capRootDir = Join-Path ([System.IO.Path]::GetTempPath()) "CAP" - $capUtilDir = Join-Path $capRootDir "Utils" - - if (Test-Path $capRootDir) { Remove-Item $capRootDir -Recurse -Force } - New-Item $capUtilDir -ItemType Directory > $null - - $capZipFile = Join-Path $capRootDir "cap.zip" - Invoke-WebRequest -Uri https://pscoretestdata.blob.core.windows.net/dotnet-cap/windows.zip -OutFile $capZipFile - Unblock-File -Path $capZipFile - Expand-Archive -Path $capZipFile -DestinationPath $capUtilDir -Force - - Write-Host "=== Capture CAP Util Directory ===" - Get-ChildItem $capUtilDir -Recurse - - Write-Host "##vso[task.setvariable variable=CapRootDir]$capRootDir" - Write-Host "##vso[task.setvariable variable=CapUtilDir]$capUtilDir" - displayName: 'Download CAP package' - condition: succeededOrFailed() - # must be run frow Windows PowerShell - powershell: | # Remove "Program Files\dotnet" from the env variable PATH, so old SDKs won't affect us. @@ -115,6 +93,13 @@ stages: displayName: Bootstrap condition: succeededOrFailed() + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + useGlobalJson: true + packageType: 'sdk' + workingDirectory: $(Build.SourcesDirectory)" + - pwsh: | Import-Module .\build.psm1 Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' @@ -125,13 +110,7 @@ stages: condition: succeeded() - pwsh: | - Import-Module $(CapUtilDir)\CAPService.psm1 - $dataDir = Start-TraceCollection -RootDir $(CapRootDir) - Write-Host "##vso[task.setvariable variable=CapDataDir]$dataDir" - displayName: 'Start CLR Trace Collection' - condition: succeeded() - - - pwsh: | + Import-Module .\build.psm1 Import-Module .\tools\ci.psm1 Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' Invoke-CITest -Purpose UnelevatedPesterTests -TagSet CI @@ -139,6 +118,7 @@ stages: condition: succeeded() - pwsh: | + Import-Module .\build.psm1 Import-Module .\tools\ci.psm1 Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' Invoke-CITest -Purpose ElevatedPesterTests -TagSet CI @@ -146,6 +126,7 @@ stages: condition: succeededOrFailed() - pwsh: | + Import-Module .\build.psm1 Import-Module .\tools\ci.psm1 Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' Invoke-CITest -Purpose UnelevatedPesterTests -TagSet Others @@ -153,6 +134,7 @@ stages: condition: succeededOrFailed() - pwsh: | + Import-Module .\build.psm1 Import-Module .\tools\ci.psm1 Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' Invoke-CITest -Purpose ElevatedPesterTests -TagSet Others @@ -165,14 +147,3 @@ stages: Test-XUnitTestResults -TestResultsFile $xUnitTestResultsFile displayName: Verify xUnit Test Results condition: succeededOrFailed() - - - pwsh: | - $capDataDir = '$(CapDataDir)' - $capModuleFile = '$(CapUtilDir)\CAPService.psm1' - - if ((Test-Path $capModuleFile) -and (Test-Path $capDataDir)) { - Import-Module $capModuleFile - Stop-TraceCollection -DataDir $capDataDir -RepoRoot $pwd -IngressToken '$(CapIngressToken)' - } - displayName: 'Upload CLR Trace' - condition: always() diff --git a/.vsts-ci/windows.yml b/.vsts-ci/windows.yml index bdaa015832e..c0f08f54a41 100644 --- a/.vsts-ci/windows.yml +++ b/.vsts-ci/windows.yml @@ -11,9 +11,12 @@ trigger: include: - '*' exclude: - - /.vsts-ci/misc-analysis.yml - - /.github/ISSUE_TEMPLATE/* - - /.dependabot/config.yml + - .vsts-ci/misc-analysis.yml + - .github/ISSUE_TEMPLATE/* + - .github/workflows/* + - .dependabot/config.yml + - test/perf/* + - .pipelines/* pr: branches: include: @@ -22,14 +25,18 @@ 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: - - .vsts-ci/misc-analysis.yml - - .github/ISSUE_TEMPLATE/* - - .dependabot/config.yml - - tools/releaseBuild/* - - tools/releaseBuild/azureDevOps/templates/* - test/common/markdown/* + - test/perf/* variables: GIT_CONFIG_PARAMETERS: "'core.autocrlf=false'" @@ -38,6 +45,8 @@ variables: # 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 + NugetSecurityAnalysisWarningLevel: none + nugetMultiFeedWarnLevel: none resources: - repo: self diff --git a/.vsts-ci/windows/templates/windows-packaging.yml b/.vsts-ci/windows/templates/windows-packaging.yml index 19d2be32618..d23b745c30f 100644 --- a/.vsts-ci/windows/templates/windows-packaging.yml +++ b/.vsts-ci/windows/templates/windows-packaging.yml @@ -1,35 +1,111 @@ parameters: - pool: 'Hosted VS2017' - jobName: 'win_packaging' - architecture: 'x64' - channel: 'preview' - parentJobs: [] + - name: pool + default: 'windows-latest' + - name: jobName + default: 'win_packaging' + - name: runtimePrefix + default: 'win7' + - name: architecture + default: 'x64' + - name: channel + default: 'preview' jobs: - job: ${{ parameters.jobName }}_${{ parameters.channel }}_${{ parameters.architecture }} - dependsOn: - ${{ parameters.parentJobs }} + + variables: + - name: repoFolder + value: PowerShell + - name: repoPath + value: $(Agent.BuildDirectory)\$(repoFolder) + - name: complianceRepoFolder + value: compliance + - name: complianceRepoPath + value: $(Agent.BuildDirectory)\$(complianceRepoFolder) + pool: - name: ${{ parameters.pool }} + vmImage: ${{ parameters.pool }} displayName: Windows Packaging - ${{ parameters.architecture }} - ${{ parameters.channel }} steps: + - checkout: self + clean: true + path: $(repoFolder) + + - checkout: ComplianceRepo + clean: true + path: $(complianceRepoFolder) + - powershell: | - Get-ChildItem -Path env: + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose displayName: Capture environment condition: succeededOrFailed() - - template: /tools/releaseBuild/azureDevOps/templates/insert-nuget-config-azfeed.yml + - pwsh: | + $PSVersionTable + displayName: Capture PowerShell Version Table + condition: succeededOrFailed() + + - pwsh: | + Import-Module .\tools\ci.psm1 + Switch-PSNugetConfig -Source Public + displayName: Switch to public feeds + 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 displayName: Bootstrap condition: succeeded() + workingDirectory: $(repoPath) + + - pwsh: | + Import-Module .\tools\ci.psm1 + New-CodeCoverageAndTestPackage + Invoke-CIFinish -Runtime ${{ parameters.runtimePrefix }}-${{ parameters.architecture }} -channel ${{ parameters.channel }} -Stage Build + displayName: Build + workingDirectory: $(repoPath) + + - template: Sbom.yml@ComplianceRepo + parameters: + BuildDropPath: '$(System.ArtifactsDirectory)/mainBuild' + Build_Repository_Uri: $(build.repository.uri) + displayName: SBOM + sourceScanPath: '$(repoPath)\tools' + signSBOM: false + + # This is needed as SBOM task removed the installed .NET and installs .NET 3.1 + - pwsh: | + Import-Module .\tools\ci.psm1 + Invoke-CIInstall -SkipUser + displayName: Bootstrap + condition: succeeded() + workingDirectory: $(repoPath) + + - pwsh: | + $manifestFolder = Join-Path -Path '$(System.ArtifactsDirectory)/mainBuild' -ChildPath '_manifest' + + if (-not (Test-Path $manifestFolder)) { + throw "_manifest folder does not exist under $(System.ArtifactsDirectory)/mainBuild" + } + + $null = New-Item -Path "$manifestFolder/spdx_2.2/bsi.json" -Verbose -Force + $null = New-Item -Path "$manifestFolder/spdx_2.2/manifest.cat" -Verbose -Force + + displayName: Create fake SBOM manifest signed files - pwsh: | Import-Module .\tools\ci.psm1 New-CodeCoverageAndTestPackage - Invoke-CIFinish -Runtime win7-${{ parameters.architecture }} -channel ${{ parameters.channel }} - displayName: Build and Test Package + Invoke-CIFinish -Runtime ${{ parameters.runtimePrefix }}-${{ parameters.architecture }} -channel ${{ parameters.channel }} -Stage Package + displayName: Package and Test + workingDirectory: $(repoPath) diff --git a/.vsts-ci/windows/windows-packaging.yml b/.vsts-ci/windows/windows-packaging.yml index f471196d963..05f69400719 100644 --- a/.vsts-ci/windows/windows-packaging.yml +++ b/.vsts-ci/windows/windows-packaging.yml @@ -27,32 +27,18 @@ pr: - release* - feature* paths: - # file extension filters are not supported when this was written. - # This really should be /src/**/*.csproj include: - - .vsts-ci/windows/* + - .vsts-ci/windows/*.yml - assets/wix/* - build.psm1 - global.json - nuget.config - PowerShell.Common.props - - src/Microsoft.Management.Infrastructure.CimCmdlets/Microsoft.Management.Infrastructure.CimCmdlets.csproj - - src/Microsoft.Management.UI.Internal/Microsoft.PowerShell.GraphicalHost.csproj - - src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj - - src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj - - src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj - - src/Microsoft.PowerShell.ConsoleHost/Microsoft.PowerShell.ConsoleHost.csproj - - src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj - - src/Microsoft.PowerShell.GlobalTool.Shim/Microsoft.PowerShell.GlobalTool.Shim.csproj - - src/Microsoft.PowerShell.MarkdownRender/Microsoft.PowerShell.MarkdownRender.csproj - - src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj - - src/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.csproj - - src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj - - src/Microsoft.WSMan.Runtime/Microsoft.WSMan.Runtime.csproj - - src/Modules/PSGalleryModules.csproj - - src/powershell-win-core/powershell-win-core.csproj + - src/*.csproj + - test/packaging/windows/* - tools/ci.psm1 - tools/packaging/* + - tools/wix/* variables: - name: GIT_CONFIG_PARAMETERS @@ -67,10 +53,19 @@ variables: - name: __SuppressAnsiEscapeSequences value: 1 - group: fakeNugetKey + - name: SBOMGenerator_Formats + value: spdx:2.2 + - name: nugetMultiFeedWarnLevel + value: none resources: -- repo: self - clean: true + repositories: + - repository: ComplianceRepo + type: github + endpoint: PowerShell + name: PowerShell/compliance + ref: master + stages: - stage: PackagingWin displayName: Packaging for Windows @@ -86,3 +81,8 @@ stages: parameters: channel: preview architecture: x86 + - template: templates/windows-packaging.yml + parameters: + channel: preview + architecture: arm64 + runtimePrefix: win diff --git a/ADOPTERS.md b/ADOPTERS.md index 110f56d16e6..186b99dd093 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -10,28 +10,30 @@ Example entry: ``` --> -This is a list of adopters of using PowerShell in production or in their products (in alphabetical order): +This is a list of adopters using PowerShell in production or in their products (in alphabetical order): -* [Azure Cloud Shell](https://shell.azure.com/) provides a batteries-included browser-based PowerShell environment used by Azure administrators to manage their environment. +* [Azure Cloud Shell](https://shell.azure.com/) provides a battery-included browser-based PowerShell environment used by Azure administrators to manage their environment. It includes up-to-date PowerShell modules for `Azure`, `AzureAD`, `Exchange`, `Teams`, and many more. - More information about Azure Cloud Shell is available at [Azure Cloud Shell Overview.](https://docs.microsoft.com/azure/cloud-shell/overview) -* [Azure Functions - PowerShell](https://github.com/Azure/azure-functions-powershell-worker) is a serverless compute service to execute PowerShell scripts in the cloud without worrying about managing resources. - In addition, Azure Functions provides client tools such as [`Az.Functions`](https://www.powershellgallery.com/packages/Az.Functions), a cross-platform PowerShell module to manage function apps and service plans in the cloud. - For more information about Functions, please visit [functions overview](https://docs.microsoft.com/azure/azure-functions/functions-overview). -* [PowerShell Universal](https://ironmansoftware.com/powershell-universal) is a cross-platform web framework for PowerShell. - It provides the ability to create robust, interactive websites, REST APIs, and Electron-based desktop apps with PowerShell script. + More information about Azure Cloud Shell is available at [Azure Cloud Shell Overview.](https://learn.microsoft.com/azure/cloud-shell/overview) +* [Azure Functions - PowerShell](https://github.com/Azure/azure-functions-powershell-worker) is a serverless compute service to execute PowerShell scripts on the cloud without worrying about managing resources. + In addition, Azure Functions provides client tools such as [`Az.Functions`](https://www.powershellgallery.com/packages/Az.Functions), a cross-platform PowerShell module for managing function apps and service plans in the cloud. + For more information about Functions, please visit [functions overview](https://learn.microsoft.com/azure/azure-functions/functions-overview). +* [PowerShell Universal](https://ironmansoftware.com/powershell-universal) is a cross-platform web framework for PowerShell. + It provides the ability to create robust, interactive sites, REST APIs, and Electron-based desktop apps with PowerShell script. More information about PowerShell Universal Dashboard is available at the [PowerShell Universal Dashboard Docs](https://docs.universaldashboard.io). -* [System Frontier](https://systemfrontier.com/solutions/powershell/) provides dynamically generated web GUIs and REST APIs for PowerShell and other scripting languages. - Enable non-admins like help desk and tier 1 support teams to execute secure web based tools on any platform `without admin rights`. - Configure flexible RBAC permissions from an intuitive interface, without a complex learning curve. +* [System Frontier](https://systemfrontier.com/solutions/powershell/) provides dynamically generated web GUIs and REST APIs for PowerShell and other scripting languages. + Enable non-admins like help desk and tier 1 support teams to execute secure web based tools on any platform `without admin rights`. + Configure flexible RBAC permissions from an intuitive interface, without a complex learning curve. Script output along with all actions are audited. Manage up to 5,000 nodes for free with the [Community Edition](https://systemfrontier.com/solutions/community-edition/). * [Amazon AWS](https://aws.com) supports PowerShell in a wide variety of its products including [AWS tools for PowerShell](https://github.com/aws/aws-tools-for-powershell), [AWS Lambda Support For PowerShell](https://github.com/aws/aws-lambda-dotnet/tree/master/PowerShell) and [AWS PowerShell Tools for `CodeBuild`](https://docs.aws.amazon.com/powershell/latest/reference/items/CodeBuild_cmdlets.html) as well as supporting PowerShell Core in both Windows and Linux EC2 Images. -* [Azure Resource Manager Deployment Scripts](https://docs.microsoft.com/azure/azure-resource-manager/templates/deployment-script-template) Complete the "last mile" of your Azure Resource Manager (ARM) template deployments with a Deployment Script, which enables you to run an arbitrary PowerShell script in the context of a deployment. - Designed to let you complete tasks that should be part of a deployment, but are not possible in an ARM template today — for example, creating a Key Vault certificate or querying an external API for a new CIDR block. -* [Azure Pipelines Hosted Agents](https://docs.microsoft.com/azure/devops/pipelines/agents/hosted?view=azure-devops) Windows, Ubuntu, and MacOS Agents used by Azure Pipelines customers have PowerShell pre-installed so that customers can make use of it for all their CI/CD needs. -* [GitHub Actions Virtual Environments for Hosted Runners](https://help.github.com/actions/reference/virtual-environments-for-github-hosted-runners) Windows, Ubuntu, and macOS virtual environments used by customers of GitHub Actions include PowerShell out of the box. +* [Azure Resource Manager Deployment Scripts](https://learn.microsoft.com/azure/azure-resource-manager/templates/deployment-script-template) Complete the "last mile" of your Azure Resource Manager (ARM) template deployments with a Deployment Script, which enables you to run an arbitrary PowerShell script in the context of a deployment. + It is designed to let you complete tasks that should be part of a deployment, but are not possible in an ARM template today — for example, creating a Key Vault certificate or querying an external API for a new CIDR block. +* [Azure Pipelines Hosted Agents](https://learn.microsoft.com/azure/devops/pipelines/agents/hosted?view=azure-devops) Windows, Ubuntu, and macOS Agents used by Azure Pipelines customers have PowerShell pre-installed so that customers can make use of it for all their CI/CD needs. +* [GitHub Actions Virtual Environments for Hosted Runners](https://help.github.com/actions/reference/virtual-environments-for-github-hosted-runners) Windows, Ubuntu, and macOS virtual environments are used by customers of GitHub Actions include PowerShell out of the box. * [GitHub Actions Python builds](https://github.com/actions/python-versions) GitHub Actions uses PowerShell to automate building Python from source for its runners. * [Microsoft HoloLens](https://www.microsoft.com/hololens) makes extensive use of PowerShell 7+ throughout the development cycle to automate tasks such as firmware assembly and automated testing. -* [Windows 10 IoT Core](https://docs.microsoft.com/windows/iot-core/windows-iot-core) is a small form factor Windows edition for IoT devices and now you can easily include the [PowerShell package](https://github.com/ms-iot/iot-adk-addonkit/blob/master/Tools/IoTCoreImaging/Docs/Import-PSCoreRelease.md#Import-PSCoreRelease) in your imaging process. +* [Power BI](https://powerbi.microsoft.com/) provides PowerShell users a set of cmdlets in [MicrosoftPowerBIMgmt](https://learn.microsoft.com/powershell/power-bi) module to manage and automate the Power BI service. + This is in addition to Power BI leveraging PowerShell, internally for various engineering systems and infrastructure for its service. +* [Windows 10 IoT Core](https://learn.microsoft.com/windows/iot-core/windows-iot-core) is a small form factor Windows edition for IoT devices and now you can easily include the [PowerShell package](https://github.com/ms-iot/iot-adk-addonkit/blob/master/Tools/IoTCoreImaging/Docs/Import-PSCoreRelease.md#Import-PSCoreRelease) in your imaging process. diff --git a/Analyzers.props b/Analyzers.props index 14b63bff789..6f906496c73 100644 --- a/Analyzers.props +++ b/Analyzers.props @@ -1,5 +1,6 @@ - + + diff --git a/CHANGELOG/6.0.md b/CHANGELOG/6.0.md index b5993b5be30..52db53afabf 100644 --- a/CHANGELOG/6.0.md +++ b/CHANGELOG/6.0.md @@ -104,7 +104,7 @@ work is required for Microsoft to continue to sign and release packages from the project as official Microsoft packages. - Remove `PerformWSManPluginReportCompletion`, which was not used, from `pwrshplugin.dll` (#5498) (Thanks @bergmeister!) -- Remove exclusion for hang and add context exception for remaining instances (#5595) +- Remove exclusion for unresponsive condition and add context exception for remaining instances (#5595) - Replace `strlen` with `strnlen` in native code (#5510) ## [6.0.0-rc] - 2017-11-16 @@ -766,9 +766,8 @@ For more information on this, we invite you to read [this blog post explaining P ### Move to .NET Core 2.0 (.NET Standard 2.0 support) PowerShell Core has moved to using .NET Core 2.0 so that we can leverage all the benefits of .NET Standard 2.0. (#3556) -To learn more about .NET Standard 2.0, there's some great starter content [on Youtube](https://www.youtube.com/playlist?list=PLRAdsfhKI4OWx321A_pr-7HhRNk7wOLLY), -on [the .NET blog](https://devblogs.microsoft.com/dotnet/introducing-net-standard/), -and [on GitHub](https://github.com/dotnet/standard/blob/master/docs/faq.md). +To learn more about .NET Standard 2.0, there's some great starter content [on Youtube](https://www.youtube.com/playlist?list=PLRAdsfhKI4OWx321A_pr-7HhRNk7wOLLY) +and on [the .NET blog](https://devblogs.microsoft.com/dotnet/introducing-net-standard/). We'll also have more content soon in our [repository documentation](https://github.com/PowerShell/PowerShell/tree/master/docs) (which will eventually make its way to [official documentation](https://github.com/powershell/powershell-docs)). In a nutshell, .NET Standard 2.0 allows us to have universal, portable modules between Windows PowerShell (which uses the full .NET Framework) and PowerShell Core (which uses .NET Core). Many modules and cmdlets that didn't work in the past may now work on .NET Core, so import your favorite modules and tell us what does and doesn't work in our GitHub Issues! @@ -781,7 +780,7 @@ Many modules and cmdlets that didn't work in the past may now work on .NET Core, If you want to opt-out of this telemetry, simply delete `$PSHome\DELETE_ME_TO_DISABLE_CONSOLEHOST_TELEMETRY`. Even before the first run of Powershell, deleting this file will bypass all telemetry. -In the future, we plan on also enabling a configuration value for whatever is approved as part of [RFC0015](https://github.com/PowerShell/PowerShell-RFC/blob/master/X-Rejected/RFC0015-PowerShell-StartupConfig.md). +In the future, we plan on also enabling a configuration value for whatever is approved as part of [RFC0015](https://github.com/PowerShell/PowerShell-RFC/blob/master/Archive/Rejected/RFC0015-PowerShell-StartupConfig.md). We also plan on exposing this telemetry data (as well as whatever insights we leverage from the telemetry) in [our community dashboard](https://devblogs.microsoft.com/powershell/powershell-open-source-community-dashboard/). If you have any questions or comments about our telemetry, please file an issue. diff --git a/CHANGELOG/6.1.md b/CHANGELOG/6.1.md index f8e12f47001..59cf2842d78 100644 --- a/CHANGELOG/6.1.md +++ b/CHANGELOG/6.1.md @@ -428,7 +428,7 @@ - Fix crash when terminal is reset (#6777) - Fix a module-loading regression that caused an infinite loop (#6843) - Further improve `PSMethod` to `Delegate` conversion (#6851) -- Blacklist `System.Windows.Forms` from loading to prevent a crash (#6822) +- Block list `System.Windows.Forms` from loading to prevent a crash (#6822) - Fix `Format-Table` where rows were being trimmed unnecessarily if there's only one row of headers (#6772) - Fix `SetDate` function in `libpsl-native` to avoid corrupting memory during `P/Invoke` (#6881) - Fix tab completions for hash table (#6839) (Thanks @iSazonov!) diff --git a/CHANGELOG/6.2.md b/CHANGELOG/6.2.md index 06ad8f41482..bf54f978eba 100644 --- a/CHANGELOG/6.2.md +++ b/CHANGELOG/6.2.md @@ -844,7 +844,7 @@ ### Documentation and Help Content -- Replace ambiguous `hang` term (#7902, #7931) (Thanks @iSazonov!) +- Replace ambiguous term (#7902, #7931) (Thanks @iSazonov!) - Updating incorrect example of `PowerShell.Create()` (#7926) (Thanks @1RedOne!) - Update `governance.md` (#7927) (Thanks @tommymaynard!) - Add `cURL` to the Bash users list in `README.md` (#7948) (Thanks @vmsilvamolina!) diff --git a/CHANGELOG/7.0.md b/CHANGELOG/7.0.md index af21d0bd957..e054b34cfc9 100644 --- a/CHANGELOG/7.0.md +++ b/CHANGELOG/7.0.md @@ -1,5 +1,241 @@ # 7.0 Changelog +## [7.0.13] - 2022-10-20 + +### Engine Updates and Fixes + +- Stop sending telemetry about `ApplicationType` (#18265) + +### Build and Packaging Improvements + +
+ + + +

Bump .NET SDK to 3.1.424 (#18272)

+ +
+ +
    +
  • Update Wix file for new assemblies (Internal 22873)
  • +
  • Update the cgmanifest.json for v7.0.13 (#18318)
  • +
  • Update Newtonsoft.Json version for 7.0.13 release (#18259)
  • +
  • Fix build.psm1 to not specify both version and quality for dotnet-install (#18267)
  • +
  • Update list of PowerShell team members in release tools(#18266)
  • +
  • Move cgmanifest generation to daily (#18268)
  • +
  • Disable static analysis CI on 7.0 (#18269)
  • +
+ +
+ +[7.0.13]: https://github.com/PowerShell/PowerShell/compare/v7.0.12...v7.0.13 + + +## [7.0.12] - 2022-08-11 + +### General Cmdlet Updates and Fixes + +- Fix `Export-PSSession` to not throw error when a rooted path is specified for `-OutputModule` (#17671) + +### Tests + +- Enable more tests to be run in a container. (#17294) +- Switch to using GitHub action to verify markdown links for PRs (#17281) +- Add `win-x86` test package to the build (#15517) + +### Build and Packaging Improvements + +
+ + +

Bump .NET 3.1 SDK to 3.1.28

+
+ +
    +
  • Update wix file
  • +
  • Add a finalize template which causes jobs with issues to fail (#17314)
  • +
  • Make sure we execute tests on LTS package for older LTS releases (#17326)
  • +
  • Update AzureFileCopy task and fix the syntax for specifying pool (#17013)
  • +
+ +
+ +[7.0.12]: https://github.com/PowerShell/PowerShell/compare/v7.0.11...v7.0.12 + +## [7.0.11] - 2022-05-13 + +### Build and Packaging Improvements + +
+ + + +

Update .NET SDK to 3.1.419

+ +
+ +
    +
  • Add explicit job name for approval tasks in Snap stage (#16579)
  • +
  • Update to use mcr.microsoft.com (#17272)
  • +
  • Update global.json and wix
  • +
  • Put Secure supply chain analysis at correct place (#17273)
  • +
  • Partial back-port of: Update a few tests to make them more stable in CI (#16944) (Internal 20648)
  • +
  • Replace . in notices container name (#17292)
  • +
  • Add an approval for releasing build-info json (#16351)
  • +
  • Release build info json when it is preview (#16335)
  • +
  • Add a major-minor build info JSON file (#16301)
  • +
  • Update release instructions with link to new build (#17256)
  • +
  • Add condition to generate release file in local dev build only (#17255)
  • +
  • Removed old not-used-anymore docker-based tests for PS release packages (#16224)
  • +
  • Publish global tool package for stable releases (#15961)
  • +
  • Update to use windows-latest as the build agent image (#16831)
  • +
  • Don't upload dep or tar.gz for RPM build because there are none. (#17224)
  • +
  • Update to vPack task version 12 (#17225)
  • +
  • Make RPM license recognized (#17223)
  • +
  • Ensure psoptions.json and manifest.spdx.json files always exist in packages (#17226)
  • +
+ +
+ +[7.0.11]: https://github.com/PowerShell/PowerShell/compare/v7.0.10...v7.0.11 + +## [7.0.10] - 2022-04-26 + +### Engine Updates and Fixes + +- Fix for partial PowerShell module search paths, that can be resolved to CWD locations +- Do not include node names when sending telemetry. (#16981) to v7.0.10 (Internal 20186,Internal 20261) + +### Tests + +- Re-enable `PowerShellGet` tests targeting PowerShell gallery (#17062) +- Skip failing scriptblock tests (#17093) + +### Build and Packaging Improvements + +
+ + + +

Update .NET SDK to 3.1.418

+ +
+ +
    +
  • Fixed package names verification to support multi-digit versions (Internal 20363)
  • +
  • Fix build failure in `generate checksum file for packages` step - v7.0.10 (Internal 20275)
  • +
  • Updated files.wxs for 7.0.10 (Internal 20208)
  • +
  • Updated to .NET 3.1.24 / SDK 3.1.418 (Internal 20133)
  • +
  • Disable broken macOS CI job, which is unused (Internal 20189)
  • +
  • Update Ubuntu images to use Ubuntu 20.04 (#15906)
  • +
  • Update dotnet-install script download link (Internal 19949)
  • +
  • Create checksum file for global tools (Internal 19934)
  • +
  • Make sure global tool packages are published in stable build (Internal 19623)
  • +
+ +
+ +[7.0.10]: https://github.com/PowerShell/PowerShell/compare/v7.0.9...v7.0.10 + +## [7.0.9] - 2022-03-16 + +### Build and Packaging Improvements + +
+ + + +

Update .NET SDK to 3.1.417

+ +
+ +
    +
  • Fix the NuGet SDK package creation (Internal 19569)
  • +
  • Fix NuGet package compliance issues (#13045)
  • +
  • Fix issues in release build (#16332)
  • +
  • Enable ARM64 packaging for macOS (#15768)
  • +
  • Update feed and analyzer dependency (#16327)
  • +
  • Only upload stable buildinfo for stable releases (#16251)
  • +
  • Opt-in to build security monitoring (#16911)
  • +
  • Update experimental feature json files (#16838) (Thanks @!)
  • +
  • Ensure alpine and arm SKUs have the PowerShell configuration file with experimental features enabled (#16823)
  • +
  • Remove WiX install (#16834)
  • +
  • Add Linux package dependencies for packaging (#16807)
  • +
  • Switch to our custom images for build and release (#16801)
  • +
  • Remove all references to cmake for the builds in this repo (#16578)
  • +
  • Register NuGet source when generating CGManifest (#16570)
  • +
  • Update Images used for release (#16580)
  • +
  • Add Software Bill of Materials to the main packages (#16202, #16641, #16711)
  • +
  • Add GitHub Workflow to keep notices up to date (#16284)
  • +
  • Update the vmImage and PowerShell root directory for macOS builds (#16611)
  • +
  • Update macOS build image and root folder for build (#16609)
  • +
  • Add checkout to build json stage to get ci.psm1 (#16399)
  • +
  • Move mapping file into product repo and add Debian 11 (#16316)
  • +
+ +
+ +[7.0.9]: https://github.com/PowerShell/PowerShell/compare/v7.0.8...v7.0.9 + +## [7.0.8] - 2021-10-14 + +### Engine Updates and Fixes + +- Handle error from unauthorized access when removing `AppLocker` test files (#15881) +- Handle error when the telemetry mutex cannot be created (#15574) (Thanks @gukoff!) +- Configure `ApplicationInsights` to not send cloud role name (Internal 17099) +- Disallow `Add-Type` in NoLanguage mode on a locked down machine (Internal 17521) + +### Tools + +- Add `.stylecop` to `filetypexml` and format it (#16025) + +### Build and Packaging Improvements + +
+ + +

Bump .NET SDK to 3.1.414

+
+ +
    +
  • Update the nuget.config file used for building NuGet packages (Internal 17547)
  • +
  • Sign the .NET createdump executable (#16229)
  • +
  • Upgrade set-value package for markdown test (#16196)
  • +
  • Move vPack build to 1ES Pool (#16169)
  • +
  • Update to .NET SDK 3.1.414 (Internal 17532)
  • +
  • Fix the macOS build by updating the pool image name (#16010)
  • +
  • Move from PkgES hosted agents to 1ES hosted agents (#16023)
  • +
  • Use Alpine 3.12 for building PowerShell for Alpine Linux (#16008)
  • +
+ +
+ +### Documentation and Help Content + +- Fix example nuget.config (#14349) + +[7.0.8]: https://github.com/PowerShell/PowerShell/compare/v7.0.7...v7.0.8 + +## [7.0.7] - 2021-08-12 + +### Build and Packaging Improvements + +
+ + +Bump .NET SDK to 3.1.412 + + +
    +
  • Remove cat file from PSDesiredStateConfiguration module (Internal 16722)
  • +
  • Update .NET SDK to 3.1.412 (Internal 16717)
  • +
+ +
+ +[7.0.7]: https://github.com/PowerShell/PowerShell/compare/v7.0.6...v7.0.7 + ## [7.0.6] - 2021-03-11 ### General Cmdlet Updates and Fixes @@ -130,6 +366,8 @@ Bump .NET SDK to version 3.1.405 +[7.0.3]: https://github.com/PowerShell/PowerShell/compare/v7.0.2...v7.0.3 + ## [7.0.2] - 2020-06-11 ### Engine Updates and Fixes @@ -308,7 +546,7 @@ Move to .NET Core 3.1.202 SDK and update packages. - Skip null data in output data received handler to fix a `NullReferenceException` (#11448) (Thanks @iSazonov!) - Add `ssh` parameter sets for the parameter `-JobName` in `Invoke-Command` (#11444) - Adding `PowerShell Editor Services` and `PSScriptAnalyzer` to tracked modules (#11514) -- Fix key exchange hang with `SecureString` for the `OutOfProc` transports (#11380, #11406) +- Fix condition when key exchange stops responding with `SecureString` for the `OutOfProc` transports (#11380, #11406) - Add setting to disable the implicit `WinPS` module loading (#11332) ### General Cmdlet Updates and Fixes @@ -1168,7 +1406,6 @@ Move to .NET Core 3.1.202 SDK and update packages. - Update docs for `6.2.0-rc.1` release (#9022) - Update release template (#8996) - [7.0.3]: https://github.com/PowerShell/PowerShell/compare/v7.0.2...v7.0.3 [7.0.2]: https://github.com/PowerShell/PowerShell/compare/v7.0.1...v7.0.2 [7.0.1]: https://github.com/PowerShell/PowerShell/compare/v7.0.0...v7.0.1 diff --git a/CHANGELOG/7.1.md b/CHANGELOG/7.1.md index 86b7a478c30..eba1998e29c 100644 --- a/CHANGELOG/7.1.md +++ b/CHANGELOG/7.1.md @@ -1,5 +1,140 @@ # 7.1 Changelog +## [7.1.7] - 2022-04-26 + +### Engine Updates and Fixes + +- Fix for partial PowerShell module search paths, that can be resolved to CWD locations +- Do not include node names when sending telemetry. (#16981) to v7.1.7 (Internal 20187,Internal 20260) + +### Tests + +- Re-enable `PowerShellGet` tests targeting PowerShell gallery (#17062) +- Skip failing scriptblock tests (#17093) + +### Build and Packaging Improvements + +
+ + + +

Update .NET SDK to 5.0.407

+ +
+ +
    +
  • Fix build failure in `generate checksum file for packages` step - v7.1.7 (Internal 20274)
  • +
  • Updated files.wxs for 7.1.7 (Internal 20210)
  • +
  • Updated to .NET 5.0.16 / SDK 5.0.407 (Internal 20131)
  • +
  • Update Ubuntu images to use Ubuntu 20.04 (#15906)
  • +
  • Update dotnet-install script download link (Internal 19950)
  • +
  • Create checksum file for global tools (#17056) (Internal 19928)
  • +
  • Make sure global tool packages are published in stable build (Internal 19624)
  • +
+ +
+ +[7.1.7]: https://github.com/PowerShell/PowerShell/compare/v7.1.6...v7.1.7 + +## [7.1.6] - 2022-03-16 + +### Build and Packaging Improvements + +
+ + + +

Update .NET SDK to 5.0.406

+ +
+ +
    +
  • Update the mapping file (#16316, Internal 19528)
  • +
  • Remove code that handles dotnet5 feed (Internal 19525)
  • +
  • Fix issues in release build (#16332)
  • +
  • Enable ARM64 packaging for macOS (#15768)
  • +
  • Update feed and analyzer dependency (#16327)
  • +
  • Only upload stable buildinfo for stable releases (#16251)
  • +
  • Opt-in to build security monitoring (#16911)
  • +
  • Update experimental feature json files (#16838)
  • +
  • Ensure alpine and arm SKUs have the PowerShell configuration file with experimental features enabled (#16823)
  • +
  • Remove WiX install (#16834)
  • +
  • Add Linux package dependencies for packaging (#16807)
  • +
  • Switch to our custom images for build and release (#16801)
  • +
  • Remove all references to cmake for the builds in this repo (#16578)
  • +
  • Register NuGet source when generating CGManifest (#16570)
  • +
  • Update images used for release (#16580)
  • +
  • Add GitHub Workflow to keep notices up to date (#16284)
  • +
  • Update the vmImage and PowerShell root directory for macOS builds (#16611)
  • +
  • Add Software Bill of Materials to the main packages (#16202, #16641, #16711)
  • +
  • Update macOS build image and root folder for build (#16609)
  • +
  • Add diagnostics used to take corrective action when releasing buildInfo JSON file (#16404)
  • +
  • Add checkout to build json stage to get ci.psm1 (#16399)
  • +
+ +
+ +[7.1.6]: https://github.com/PowerShell/PowerShell/compare/v7.1.5...v7.1.6 + +## [7.1.5] - 2021-10-14 + +### Engine Updates and Fixes + +- Handle error from unauthorized access when removing `AppLocker` test files (#15881) +- Test more thoroughly whether a command is `Out-Default` for transcription scenarios (#15653) +- Handle error when the telemetry mutex cannot be created (#15574) (Thanks @gukoff!) +- Configure `ApplicationInsights` to not send cloud role name (Internal 17100) +- Disallow `Add-Type` in NoLanguage mode on a locked down machine (Internal 17522) + +### Tools + +- Add `.stylecop` to `filetypexml` and format it (#16025) + +### Build and Packaging Improvements + +
+ + +

Bump .NET SDK to 5.0.402

+
+ +
    +
  • Upgrade set-value package for markdown test (#16196)
  • +
  • Sign the .NET createdump executable (#16229)
  • +
  • Move vPack build to 1ES Pool (#16169)
  • +
  • Update to .NET SDK 5.0.402 (Internal 17537)
  • +
  • Move from PkgES hosted agents to 1ES hosted agents (#16023)
  • +
  • Fix the macOS build by updating the pool image name (#16010)
  • +
  • Use Alpine 3.12 for building PowerShell for Alpine Linux (#16008)
  • +
+ +
+ +### Documentation and Help Content + +- Fix example nuget.config (#14349) + +[7.1.5]: https://github.com/PowerShell/PowerShell/compare/v7.1.4...v7.1.5 + +## [7.1.4] - 2021-08-12 + +### Build and Packaging Improvements + +
+ + +Bump .NET SDK to version 5.0.400 + + +
    +
  • Remove the cat file from PSDesiredStateConfiguration module (Internal 16723)
  • +
  • Update .NET SDK version and other packages (Internal 16715)
  • +
+ +
+ +[7.1.4]: https://github.com/PowerShell/PowerShell/compare/v7.1.3...v7.1.4 + ## [7.1.3] - 2021-03-11 ### Engine Updates and Fixes diff --git a/CHANGELOG/7.2.md b/CHANGELOG/7.2.md new file mode 100644 index 00000000000..c9bde27a841 --- /dev/null +++ b/CHANGELOG/7.2.md @@ -0,0 +1,1863 @@ +# 7.2 Changelog + +## [7.2.23] - 2024-08-20 + +### Build and Packaging Improvements + +
+ + + +

Bump .NET to 6.0.425

+ +
+ +
    +
  • Add feature flags for removing network isolation
  • +
  • Bump PackageManagement to 1.4.8.1 (#24162)
  • +
  • Bump .NET to 6.0.425 (#24161)
  • +
  • Skip build steps that do not have exe packages (#23945) (#24156)
  • +
  • Use correct signing certificates for RPM and DEBs (#21522) (#24154)
  • +
  • Fix exe signing with third party signing for WiX engine (#23878) (#24155)
  • +
  • Fix error in the vPack release, debug script that blocked release (#23904)
  • +
  • Add vPack release (#23898)
  • +
  • Fix nuget publish download path
  • +
  • Use correct signing certificates for RPM and DEBs (#21522)
  • +
+ +
+ +### Documentation and Help Content + +- Update docs sample nuget.config (#24109) (#24157) + +[7.2.23]: https://github.com/PowerShell/PowerShell/compare/v7.2.22...v7.2.23 + +## [7.2.22] - 2024-07-18 + +### Engine Updates and Fixes + +- Resolve paths correctly when importing files or files referenced in the module manifest (Internal 31777 31788) + +### Build and Packaging Improvements + +
+ + + +

Bump .NET to 6.0.424

+ +
+ +
    +
  • Enumerate over all signed zip packages
  • +
  • Update TPN for release v7.2.22 (Internal 31807)
  • +
  • Update CG Manifest for 7.2.22 (Internal 31804)
  • +
  • Add macos signing for package files (#24015) (#24058)
  • +
  • Update .NET version to 6.0.424 (#24033)
  • +
+ +
+ +[7.2.22]: https://github.com/PowerShell/PowerShell/compare/v7.2.21...v7.2.22 + +## [7.2.21] - 2024-06-18 + +### Build and Packaging Improvements + +
+ + + +

Release 7.2.20 broadly (was previously just released to the .NET SDK containers.)

Release 7.2.20 broadly

+ +
+ +
    +
  • Fixes for change to new Engineering System.
  • +
  • Create powershell.config.json for PowerShell.Windows.x64 global tool (#23941) (#23942)
  • +
+ +
+ +[7.2.21]: https://github.com/PowerShell/PowerShell/compare/v7.2.19...v7.2.21 + +## [7.2.20] - 2024-06-06 + +Limited release for dotnet SDK container images. + + + +

Update .NET 6 to 6.0.31 and how global tool is generated

+ +
+ +
    +
  • Fixes for change to new Engineering System.
  • +
  • Create powershell.config.json for PowerShell.Windows.x64 global tool (#23941) (#23942)
  • +
  • Update change log for v7.2.20
  • +
  • Update installation on Wix module (#23808)
  • +
  • Updates to package and release pipelines (#23800)
  • +
  • Use feed with Microsoft Wix toolset (#21651)
  • +
  • update wix package install (#21537)
  • +
  • Use PSScriptRoot to find path to Wix module (#21611)
  • +
  • Create the Windows.x64 global tool with shim for signing (#21559)
  • +
  • Add branch counter variables for daily package builds (#21523)
  • +
  • Official PowerShell Package pipeline (#21504)
  • +
  • Add a PAT for fetching PMC cli (#21503)
  • +
  • [StepSecurity] Apply security best practices (#21480)
  • +
  • Fix build failure due to missing reference in GlobalToolShim.cs (#21388)
  • +
  • Fix argument passing in GlobalToolShim (#21333)
  • +
  • Update .NET 6 to 6.0.31 (Internal 31302)
  • +
  • Re-apply the OneBranch changes to packaging.psm1"
  • +
+ + + +[7.2.20]: https://github.com/PowerShell/PowerShell/compare/v7.2.19...v7.2.20 + +## [7.2.19] - 2024-04-11 + +### Build and Packaging Improvements + +
+ + + +

Bump to .NET 6.0.29

+ +
+ +
    +
  • Allow artifacts produced by partially successful builds to be consumed by release pipeline
  • +
  • Update SDK, dependencies and cgmanifest for 7.2.19
  • +
  • Revert changes to packaging.psm1
  • +
  • Verify environment variable for OneBranch before we try to copy (#21441)
  • +
  • Multiple fixes in official build pipeline (#21408)
  • +
  • Add dotenv install as latest version does not work with current Ruby version (#21239)
  • +
  • PowerShell co-ordinated build OneBranch pipeline (#21364)
  • +
  • Remove surrogateFile setting of APIScan (#21238)
  • +
+ +
+ +[7.2.19]: https://github.com/PowerShell/PowerShell/compare/v7.2.18...v7.2.19 + +## [7.2.18] - 2024-01-11 + +### Build and Packaging Improvements + +
+ + + +

Bump .NET to 6.0.418

+ +
+ +
    +
  • Update ThirdPartyNotices.txt for v7.2.18 (Internal 29173)
  • +
  • Update cgmanifest.json for v7.2.18 release (Internal 29161)
  • +
  • Update .NET SDK to 6.0.418 (Internal 29141)
  • +
  • Back port 3 build changes to apiscan.yml (#21036)
  • +
  • Set the ollForwardOnNoCandidateFx in runtimeconfig.json to roll forward only on minor and patch versions (#20689)
  • +
  • Remove the ref folder before running compliance (#20373)
  • +
  • Fix the tab completion tests (#20867)
  • +
+ +
+ +[7.2.18]: https://github.com/PowerShell/PowerShell/compare/v7.2.17...v7.2.18 + +## [7.2.17] - 2023-11-16 + +### General Cmdlet Updates and Fixes + +- Redact Auth header content from ErrorRecord (Internal 28411) + +### Build and Packaging Improvements + +
+ + + +

Bump to .NET to version 6.0.417

+ +
+ +
    +
  • Bump to .NET 6.0.417 (Internal 28486)
  • +
  • Copy azure blob with PowerShell global tool to private blob and move to CDN during release (Internal 28450)
  • +
+ +
+ +[7.2.17]: https://github.com/PowerShell/PowerShell/compare/v7.2.16...v7.2.17 + +## [7.2.16] - 2023-10-26 + +### Build and Packaging Improvements + +
+ + + +

Update .NET 6 to version 6.0.416

+ +
+ +
    +
  • Fix release pipeline yaml
  • +
  • Fix issues with merging backports in packaging (Internal 28158)
  • +
  • Update .NET 6 and TPN (Internal 28149)
  • +
  • Add runtime and packaging type info for mariner2 arm64 (#19450) (#20564)
  • +
  • Add mariner arm64 to PMC release (#20176) (#20567)
  • +
  • Remove HostArchitecture dynamic parameter for osxpkg (#19917) (#20565)
  • +
  • Use fxdependent-win-desktop runtime for compliance runs (#20326) (#20568)
  • +
  • Add SBOM for release pipeline (#20519) (#20570)
  • +
  • Increase timeout when publishing packages to pacakages.microsoft.com (#20470) (#20569)
  • +
  • Add mariner arm64 package build to release build (#19946) (#20566)
  • +
+ +
+ +[7.2.16]: https://github.com/PowerShell/PowerShell/compare/v7.2.15...v7.2.16 + +## [7.2.15] - 2023-10-10 + +### Security Fixes + +- Block getting help from network locations in restricted remoting sessions (Internal 27699) + +### Build and Packaging Improvements + +
+ + + +

Build infrastructure maintenance

+ +
+ +
    +
  • Release build: Change the names of the PATs (#20315)
  • +
  • Switch to GitHub Action for linting markdown (#20309)
  • +
  • Put the calls to Set-AzDoProjectInfo and Set-AzDoAuthToken` in the right order (#20312)
  • +
+ +
+ +[7.2.15]: https://github.com/PowerShell/PowerShell/compare/v7.2.14...v7.2.15 + +## [7.2.14] - 2023-09-18 + +### Build and Packaging Improvements + +
+ + + +

Bump .NET SDK version to 6.0.414

+ +
+ +
    +
  • Update to use .NET SDK 6.0.414 (Internal 27575)
  • +
  • Enable vPack provenance data (#20242)
  • +
  • Start using new packages.microsoft.com CLI (#20241)
  • +
  • Remove spelling CI in favor of GitHub Action (#20239)
  • +
  • Make PR creation tool use --web because it is more reliable (#20238)
  • +
  • Update variable used to bypass the blocking check for multiple NuGet feeds (#20237)
  • +
  • Don't publish notice on failure because it prevents retry (#20236)
  • +
  • Publish rpm package for rhel9 (#20234)
  • +
  • Add ProductCode in registry for MSI install (#20233)
  • +
+ +
+ +### Documentation and Help Content + +- Update man page to match current help for pwsh (#20240) +- Update the link for getting started in `README.md` (#20235) + +[7.2.14]: https://github.com/PowerShell/PowerShell/compare/v7.2.13...v7.2.14 + +## [7.2.13] - 2023-07-13 + +### Tests + +- Increase the timeout to make subsystem tests more reliable (#19937) +- Increase the timeout when waiting for the event log (#19936) + +### Build and Packaging Improvements + +
+ + + +

Bump .NET SDK version to 6.0.412

+ +
+ +
    +
  • Update Notice file (#19956)
  • +
  • Update cgmanifest (#19938)
  • +
  • Bump to 6.0.412 SDK (#19933)
  • +
  • Update variable used to bypass the blocking check for multiple NuGet feeds (#19935)
  • +
+ +
+ +[7.2.13]: https://github.com/PowerShell/PowerShell/compare/v7.2.12...v7.2.13 + +## [7.2.12] - 2023-06-27 + +### Build and Packaging Improvements + +
+ + + +

Bump .NET version to 6.0.411

+ +
+ +
    +
  • Disable SBOM signing for CI and add extra files for packaging tests (#19729)
  • +
  • Update ThirdPartyNotices (Internal 26349)
  • +
  • Update the cgmanifest
  • +
  • Add PoolNames variable group to compliance pipeline (#19408)
  • +
  • Add tool to trigger license information gathering for NuGet modules (#18827)
  • +
  • Update to .NET 6.0.410 (#19798)
  • +
  • Always regenerate files wxs fragment (#19803)
  • +
  • Add prompt to fix conflict during backport (#19583)
  • +
  • Add backport function to release tools (#19568)
  • +
  • Do not remove penimc_cor3.dll from build (#18438)
  • +
  • Remove unnecessary native dependencies from the package (#18213)
  • +
  • Delete symbols on Linux as well (#19735)
  • +
  • Bump Microsoft.PowerShell.MarkdownRender (#19751)
  • +
  • Backport compliance changes (#19719)
  • +
  • Delete charset regular expression test (#19585)
  • +
  • Fix issue with merge of 19068 (#19586)
  • +
  • Update the team member list in releaseTools.psm1 (#19574)
  • +
  • Verify that packages have license data (#19543) (#19575)
  • +
  • Update experimental-feature.json (#19581)
  • +
  • Fix the regular expression used for package name check in vPack build (#19573)
  • +
  • Make the vPack PAT library more obvious (#19572)
  • +
  • Add an explicit manual stage for changelog update (#19551) (#19567)
  • +
+ +
+ +[7.2.12]: https://github.com/PowerShell/PowerShell/compare/v7.2.11...v7.2.12 + +## [7.2.11] - 2023-04-12 + +### Build and Packaging Improvements + +
+ + + +

Bump .NET version to 6.0.16

+ +
+ +
    +
  • Update ThirdPartyNotices.txt
  • +
  • Update cgmanifest.json
  • +
  • Fix the template that creates nuget package
  • +
  • Update the wix file
  • +
  • Update .NET SDK to 6.0.408
  • +
  • Fix the build script and signing template
  • +
  • Fix stage dependencies and typo in release build (#19353)
  • +
  • Fix issues in release build and release pipeline (#19338)
  • +
  • Restructure the package build to simplify signing and packaging stages (#19321)
  • +
  • Skip VT100 tests on Windows Server 2012R2 as console does not support it (#19413)
  • +
  • Improve package management acceptance tests by not going to the gallery (#19412)
  • +
  • Test fixes for stabilizing tests (#19068)
  • +
  • Add stage for symbols job in Release build (#18937)
  • +
  • Use reference assemblies generated by dotnet (#19302)
  • +
  • Add URL for all distributions (#19159)
  • +
  • Update release pipeline to use Approvals and automate some manual tasks (#17837)
  • +
+ +
+ +[7.2.11]: https://github.com/PowerShell/PowerShell/compare/v7.2.10...v7.2.11 + +## [7.2.10] - 2023-02-23 + +### Build and Packaging Improvements + +
+ + + +

Bump .NET version to 6.0.14

+ +
+ +
    +
  • Fixed package names verification to support multi-digit versions (#17220)
  • +
  • Add pipeline secrets (from #17837) (Internal 24413)
  • +
  • Update to azCopy 10 (#18509)
  • +
  • Update third party notices for v7.2.10 (Internal 24346)
  • +
  • Update cgmanifest for v7.2.10 (Internal 24333)
  • +
  • Pull latest patches for 7.2.10 dependencies (Internal 24325)
  • +
  • Update SDK to 6.0.406 for v7.2.10 (Internal 24324)
  • +
  • Add test for framework dependent package in release pipeline (#18506) (#19114)
  • +
  • Mark 7.2.x releases as latest LTS but not latest stable (#19069)
  • +
+ +
+ +[7.2.10]: https://github.com/PowerShell/PowerShell/compare/v7.2.9...v7.2.10 + +## [7.2.9] - 2023-01-24 + +### Engine Updates and Fixes + +- Fix for JEA session leaking functions (Internal 23821 & 23819) + +### General Cmdlet Updates and Fixes + +- Correct incorrect cmdlet name in script (#18919) + +### Build and Packaging Improvements + +
+ + + +

Bump .NET version to 6.0.13

+ +
+ +
    +
  • Create test artifacts for windows arm64 (#18932)
  • +
  • Update dependencies for .NET release (Internal 23816)
  • +
  • Don't install based on build-id for RPM (#18921)
  • +
  • Apply expected file permissions to linux files after authenticode signing (#18922)
  • +
  • Add authenticode signing for assemblies on linux builds (#18920)
  • +
+ +
+ +[7.2.9]: https://github.com/PowerShell/PowerShell/compare/v7.2.8...v7.2.9 + +## [7.2.8] - 2022-12-13 + +### Engine Updates and Fixes + +- Remove TabExpansion for PSv2 from remote session configuration (Internal 23294) + +### Build and Packaging Improvements + +
+ + + +

Bump .NET SDK to 6.0.403

+ +
+ +
    +
  • Update CGManifest and ThirdPartyNotices
  • +
  • Update Microsoft.CSharp from 4.3.0 to 4.7.0
  • +
  • Update to latest SDK (#18610)
  • +
  • Allow two-digit revisions in vPack package validation pattern (#18569)
  • +
  • Update outdated dependencies (#18576)
  • +
  • Work around args parsing issue (#18606)
  • +
  • Bump System.Data.SqlClient from 4.8.4 to 4.8.5 (#18515)
  • +
+ +
+ +[7.2.8]: https://github.com/PowerShell/PowerShell/compare/v7.2.7...v7.2.8 + +## [7.2.7] - 2022-10-20 + +### Engine Updates and Fixes + +- On Unix, explicitly terminate the native process during cleanup only if it's not running in background (#18280) +- Stop sending telemetry about `ApplicationType` (#18168) + +### General Cmdlet Updates and Fixes + +- Remove the 1-second minimum delay in `Invoke-WebRequest` for downloading small files, and prevent file-download-error suppression (#18170) +- Enable searching for assemblies in GAC_Arm64 on Windows (#18169) +- Fix error formatting to use color defined in `$PSStyle.Formatting` (#18287) + +### Tests + +- Use Ubuntu 20.04 for SSH remoting test (#18289) + +### Build and Packaging Improvements + +
+ + + +

Bump .NET to version 6.0.402 (#18188)(#18290)

+ +
+ +
    +
  • Update cgmanifest (#18319)
  • +
  • Fix build.psm1 to find the required .NET SDK version when a higher version is installed (#17299) (#18282)
  • +
  • Update MSI exit message (#18173)
  • +
  • Remove XML files for min-size package (#18274)
  • +
  • Update list of PS team members in release tools (#18171)
  • +
  • Make the link to minimal package blob public during release (#18174)
  • +
  • Add XML reference documents to NuPkg files for SDK (#18172)
  • +
  • Update to use version 2.21.0 of Application Insights (#18271)
  • +
+ +
+ +[7.2.7]: https://github.com/PowerShell/PowerShell/compare/v7.2.6...v7.2.7 + +## [7.2.6] - 2022-08-11 + +### Engine Updates and Fixes + +- Fix `ForEach-Object -Parallel` when passing in script block variable (#16564) + +### General Cmdlet Updates and Fixes + +- Make `Out-String` and `Out-File` keep string input unchanged (#17455) +- Update regular expression used to remove ANSI escape sequences to be more specific to decoration and hyperlinks (#16811) +- Fix legacy `ErrorView` types to use `$host.PrivateData` colors (#17705) +- Fix `Export-PSSession` to not throw error when a rooted path is specified for `-OutputModule` (#17671) + +### Tests + +- Disable RPM SBOM test. (#17532) + +### Build and Packaging Improvements + +
+ + +

Bump .NET SDK to 6.0.8 (Internal 22065)

+

We thank the following contributors!

+

@tamasvajk

+
+ +
    +
  • Update Wix manifest
  • +
  • Add AppX capabilities in MSIX manifest so that PS7 can call the AppX APIs (#17416)
  • +
  • Use Quality only with Channel in dotnet-install (#17847)
  • +
  • Fix build.psm1 to not specify both version and quality for dotnet-install (#17589) (Thanks @tamasvajk!)
  • +
  • Install .NET 3.1 as it is required by the vPack task
  • +
+ +
+ +[7.2.6]: https://github.com/PowerShell/PowerShell/compare/v7.2.5...v7.2.6 + +## [7.2.5] - 2022-06-21 + +### Engine Updates and Fixes + +- Fix native library loading for osx-arm64 (#17495) (Thanks @awakecoding!) + +### Tests + +- Make Assembly Load Native test work on a FX Dependent Linux Install (#17496) +- Enable more tests to be run in a container. (#17294) +- Switch to using GitHub Action to verify Markdown links for PRs (#17281) +- Try to stabilize a few tests that fail intermittently (#17426) +- TLS test fix back-port (#17424) + +### Build and Packaging Improvements + +
+ + + +

Bump .NET SDK to 6.0.301 (Internal 21218)

+ +
+ +
    +
  • Update Wix file (Internal 21242)
  • +
  • Conditionally add output argument
  • +
  • Rename mariner package to cm (#17506)
  • +
  • Backport test fixes for 7.2 (#17494)
  • +
  • Update dotnet-runtime version (#17472)
  • +
  • Update to use windows-latest as the build agent image (#17418)
  • +
  • Publish preview versions of mariner to preview repository (#17464)
  • +
  • Move cgmanifest generation to daily (#17258)
  • +
  • Fix mariner mappings (#17413)
  • +
  • Make sure we execute tests on LTS package for older LTS releases (#17430)
  • +
  • Add a finalize template which causes jobs with issues to fail (#17428)
  • +
  • Make mariner packages Framework dependent (#17425)
  • +
  • Base work for adding mariner amd64 package (#17417)
  • +
+ +
+ +[7.2.5]: https://github.com/PowerShell/PowerShell/compare/v7.2.4...v7.2.5 + +## [7.2.4] - 2022-05-17 + +### Build and Packaging Improvements + +
+ + + +

Bump .NET SDK to 6.0.203

+ +
+ +
    +
  • Add mapping for Ubuntu22.04 Jammy (#17317)
  • +
  • Update to use mcr.microsoft.com (#17272)
  • +
  • Update third party notices
  • +
  • Update global.json and wix
  • +
  • Put Secure supply chain analysis at correct place (#17273)
  • +
  • Fix web cmdlets so that an empty Get does not include a content-length header (#16587)
  • +
  • Update package fallback list for Ubuntu (from those updated for Ubuntu 22.04) (deb) (#17217)
  • +
  • Add sha256 digests to RPM packages (#17215)
  • +
  • Allow multiple installations of dotnet. (#17216)
  • +
+ +
+ +[7.2.4]: https://github.com/PowerShell/PowerShell/compare/v7.2.3...v7.2.4 + +## [7.2.3] - 2022-04-26 + +### Engine Updates and Fixes + +- Fix for partial PowerShell module search paths, that can be resolved to CWD locations (Internal 20126) +- Do not include node names when sending telemetry. (#16981) to v7.2.3 (Internal 20188) + +### Tests + +- Re-enable `PowerShellGet` tests targeting PowerShell gallery (#17062) +- Skip failing scriptblock tests (#17093) + +### Build and Packaging Improvements + +
+ + + +

Bump .NET SDK to 6.0.202

+ +
+ +
    +
  • Making NameObscurerTelemetryInitializer internal - v7.2.3 (Internal 20239)
  • +
  • Updated files.wxs for 7.2.3 (Internal 20211)
  • +
  • Updated ThirdPartyNotices for 7.2.3 (Internal 20199)
  • +
  • Work around issue with notice generation
  • +
  • Replace . in notices container name
  • +
  • Updated cgmanifest.json by findMissingNotices.ps1 in v7.2.3 (Internal 20190)
  • +
  • v7.2.3 - Updated packages using dotnet-outdated global tool (Internal 20170)
  • +
  • Updated to .NET 6.0.4 / SDK 6.0.202 (Internal 20128)
  • +
  • Update dotnet-install script download link (Internal 19951)
  • +
  • Create checksum file for global tools (#17056) (Internal 19935)
  • +
  • Make sure global tool packages are published in stable build (Internal 19625)
  • +
  • Fix release pipeline (Internal 19617)
  • +
+ +
+ +[7.2.3]: https://github.com/PowerShell/PowerShell/compare/v7.2.2...v7.2.3 + +## [7.2.2] - 2022-03-16 + +### Build and Packaging Improvements + +
+ + + +

Bump .NET SDK to 6.0.201

+ +
+ +
    +
  • Update WiX file (Internal 19460)
  • +
  • Update .NET SDK version to 6.0.201 (Internal 19457)
  • +
  • Update experimental feature JSON files (#16838)
  • +
  • Ensure Alpine and ARM SKUs have powershell.config.json file with experimental features enabled (#16823)
  • +
  • Update the vmImage and PowerShell root directory for macOS builds (#16611)
  • +
  • Update macOS build image and root folder for build (#16609)
  • +
  • Remove WiX install (#16834)
  • +
  • Opt-in to build security monitoring (#16911)
  • +
  • Add SBOM manifest for release packages (#16641, #16711)
  • +
  • Add Linux package dependencies for packaging (#16807)
  • +
  • Switch to our custom images for build and release (#16801, #16580)
  • +
  • Remove all references to cmake for the builds in this repository (#16578)
  • +
  • Register NuGet source when generating CGManifest (#16570)
  • +
+ +
+ +[7.2.2]: https://github.com/PowerShell/PowerShell/compare/v7.2.1...v7.2.2 + +## [7.2.1] - 2021-12-14 + +### General Cmdlet Updates and Fixes + +- Remove declaration of experimental features in Utility module manifest as they are stable (#16460) +- Bring back pwsh.exe for framework dependent packages to support Start-Job (#16535) +- Change default for `$PSStyle.OutputRendering` to `Ansi` (Internal 18394) +- Update `HelpInfoUri` for 7.2 release (#16456) +- Fix typo for "privacy" in MSI installer (#16407) + +### Tests + +- Set clean state before testing `UseMU` in the MSI (#16543) + +### Build and Packaging Improvements + +
+ +
    +
  • Add explicit job name for approval tasks in Snap stage (#16579)
  • +
  • Fixing the build by removing duplicate TSAUpload entries (Internal 18399)
  • +
  • Port CGManifest fixes (Internal 18402)
  • +
  • Update CGManifest (Internal 18403)
  • +
  • Updated package dependencies for 7.2.1 (Internal 18388)
  • +
  • Use different containers for different branches (#16434)
  • +
  • Use notice task to generate license assuming CGManifest contains all components (#16340)
  • +
  • Create compliance build (#16286)
  • +
  • Update release instructions with link to new build (#16419)
  • +
  • Add diagnostics used to take corrective action when releasing buildInfoJson (#16404)
  • +
  • vPack release should use buildInfoJson new to 7.2 (#16402)
  • +
  • Add checkout to build json stage to get ci.psm1 (#16399)
  • +
  • Update the usage of metadata.json for getting LTS information (#16381)
  • +
  • Move mapping file into product repository and add Debian 11 (#16316)
  • +
+ +
+ +[7.2.1]: https://github.com/PowerShell/PowerShell/compare/v7.2.0...v7.2.1 + +## [7.2.0] - 2021-11-08 + +### General Cmdlet Updates and Fixes + +- Handle exception when trying to resolve a possible link path (#16310) + +### Tests + +- Fix global tool and SDK tests in release pipeline (#16342) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@kondratyev-nv

+ +
+ +
    +
  • Add an approval for releasing build-info json (#16351)
  • +
  • Release build info json when it is preview (#16335)
  • +
  • Update metadata.json for v7.2.0 release
  • +
  • Update to the latest notices file and update cgmanifest.json (#16339)(#16325)
  • +
  • Fix issues in release build by updating usage of powershell.exe with pwsh.exe (#16332)
  • +
  • Update feed and analyzer dependency (#16327)
  • +
  • Update to .NET 6 GA build 6.0.100-rtm.21527.11 (#16309)
  • +
  • Add a major-minor build info JSON file (#16301)
  • +
  • Fix Windows build ZIP packaging (#16299) (Thanks @kondratyev-nv!)
  • +
  • Clean up crossgen related build scripts also generate native symbols for R2R images (#16297)
  • +
  • Fix issues reported by code signing verification tool (#16291)
  • +
+ +
+ +[7.2.0]: https://github.com/PowerShell/PowerShell/compare/v7.2.0-rc.1...v7.2.0 + +## [7.2.0-rc.1] - 2021-10-21 + +### General Cmdlet Updates and Fixes + +- Disallow COM calls for AppLocker system lockdown (#16268) +- Configure `Microsoft.ApplicationInsights` to not send cloud role name (#16246) +- Disallow `Add-Type` in NoLanguage mode on a locked down machine (#16245) +- Make property names for color VT100 sequences consistent with documentation (#16212) +- Make moving a directory into itself with `Move-Item` an error (#16198) +- Change `FileSystemInfo.Target` from a `CodeProperty` to an `AliasProperty` that points to `FileSystemInfo.LinkTarget` (#16165) + +### Tests + +- Removed deprecated docker-based tests for PowerShell release packages (#16224) + +### Build and Packaging Improvements + +
+ + +

Bump .NET SDK to 6.0.100-rc.2

+
+ +
    +
  • Update .NET 6 to version 6.0.100-rc.2.21505.57 (#16249)
  • +
  • Fix RPM packaging (Internal 17704)
  • +
  • Update ThirdPartyNotices.txt (#16283)
  • +
  • Update pipeline yaml file to use ubuntu-latest image (#16279)
  • +
  • Add script to generate cgmanifest.json (#16278)
  • +
  • Update version of Microsoft.PowerShell.Native and Microsoft.PowerShell.MarkdownRender packages (#16277)
  • +
  • Add cgmanifest.json for generating correct third party notice file (#16266)
  • +
  • Only upload stable buildinfo for stable releases (#16251)
  • +
  • Don't upload .dep or .tar.gz for RPM because there are none (#16230)
  • +
  • Ensure RPM license is recognized (#16189)
  • +
  • Add condition to only generate release files in local dev build only (#16259)
  • +
  • Ensure psoptions.json and manifest.spdx.json files always exist in packages (#16258)
  • +
  • Fix CI script and split out ARM runs (#16252)
  • +
  • Update vPack task version to 12 (#16250)
  • +
  • Sign third party executables (#16229)
  • +
  • Add Software Bill of Materials to the main packages (#16202)
  • +
  • Upgrade set-value package for Markdown test (#16196)
  • +
  • Fix Microsoft update spelling issue (#16178)
  • +
  • Move vPack build to 1ES Pool (#16169)
  • +
+ +
+ +[7.2.0-rc.1]: https://github.com/PowerShell/PowerShell/compare/v7.2.0-preview.10...v7.2.0-rc.1 + +## [7.2.0-preview.10] - 2021-09-28 + +### Engine Updates and Fixes + +- Remove duplicate remote server mediator code (#16027) + +### General Cmdlet Updates and Fixes + +- Use `PlainText` when writing to a host that doesn't support VT (#16092) +- Remove support for `AppExecLinks` to retrieve target (#16044) +- Move `GetOuputString()` and `GetFormatStyleString()` to `PSHostUserInterface` as public API (#16075) +- Add `isOutputRedirected` parameter to `GetFormatStyleString()` method (#14397) +- Fix `ConvertTo-SecureString` with key regression due to .NET breaking change (#16068) +- Fix regression in `Move-Item` to only fallback to `CopyAndDelete` in specific cases (#16029) +- Set `$?` correctly for command expression with redirection (#16046) +- Use `CurrentCulture` when handling conversions to `DateTime` in `Add-History` (#16005) (Thanks @vexx32!) +- Fix `NullReferenceException` in `Format-Wide` (#15990) (Thanks @DarylGraves!) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@xtqqczze!

+ +
+ +
    +
  • Improve CommandInvocationIntrinsics API documentation and style (#14369)
  • +
  • Use bool?.GetValueOrDefault() in FormatWideCommand (#15988) (Thanks @xtqqczze!)
  • +
+ +
+ +### Tools + +- Fix typo in build.psm1 (#16038) (Thanks @eltociear!) +- Add `.stylecop` to `filetypexml` and format it (#16025) +- Enable sending Teams notification when workflow fails (#15982) + +### Tests + +- Enable two previously disabled `Get-Process` tests (#15845) (Thanks @iSazonov!) + +### Build and Packaging Improvements + +
+ + +Details + + +
    +
  • Add SHA256 hashes to release (#16147)
  • +
  • Update Microsoft.CodeAnalysis.CSharp version (#16138)
  • +
  • Change path for Component Governance for build to the path we actually use to build (#16137)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#16070) (#16045) (#16036) (#16021) (#15985)
  • +
  • Update .NET to 6.0.100-rc.1.21458.32 (#16066)
  • +
  • Update minimum required OS version for macOS (#16088)
  • +
  • Ensure locale is set correctly on Ubuntu 20.04 in CI (#16067) (#16073)
  • +
  • Update .NET SDK version from 6.0.100-preview.6.21355.2 to 6.0.100-rc.1.21455.2 (#16041) (#16028) (#15648)
  • +
  • Fix the GitHub Action for updating .NET daily builds (#16042)
  • +
  • Move from PkgES hosted agents to 1ES hosted agents (#16023)
  • +
  • Update Ubuntu images to use Ubuntu 20.04 (#15906)
  • +
  • Fix the macOS build by updating the pool image name (#16010)
  • +
  • Use Alpine 3.12 for building PowerShell for Alpine Linux (#16008)
  • +
  • Ignore error from Find-Package (#15999)
  • +
  • Find packages separately for each source in UpdateDotnetRuntime.ps1 script (#15998)
  • +
  • Update metadata to start using .NET 6 RC1 builds (#15981)
  • +
+ +
+ +[7.2.0-preview.10]: https://github.com/PowerShell/PowerShell/compare/v7.2.0-preview.9...v7.2.0-preview.10 + +## [7.2.0-preview.9] - 2021-08-23 + +### Breaking Changes + +- Change the default value of `$PSStyle.OutputRendering` to `OutputRendering.Host` and remove `OutputRendering.Automatic` (#15882) +- Fix `CA1052` for public API to make classes static when they only have static methods (#15775) (Thanks @xtqqczze!) +- Update `pwsh.exe -File` to only accept `.ps1` script files on Windows (#15859) + +### Engine Updates and Fixes + +- Update .NET adapter to handle interface static members properly (#15908) +- Catch and handle unauthorized access exception when removing AppLocker test files (#15881) + +### General Cmdlet Updates and Fixes + +- Add `-PassThru` parameter to `Set-Clipboard` (#13713) (Thanks @ThomasNieto!) +- Add `-Encoding` parameter for `Tee-Object` (#12135) (Thanks @Peter-Schneider!) +- Update `ConvertTo-Csv` and `Export-Csv` to handle `IDictionary` objects (#11029) (Thanks @vexx32!) +- Update the parameters `-Exception` and `-ErrorRecord` for `Write-Error` to be position 0 (#13813) (Thanks @ThomasNieto!) +- Don't use `ArgumentList` when creating COM object with `New-Object` as it's not applicable to the COM parameter set (#15915) +- Fix `$PSStyle` list output to correctly show `TableHeader` (#15928) +- Remove the `PSImplicitRemotingBatching` experimental feature (#15863) +- Fix issue with `Get-Process -Module` failing to stop when it's piped to `Select-Object` (#15682) (Thanks @ArmaanMcleod!) +- Make the experimental features `PSUnixFileStat`, `PSCultureInvariantReplaceOperator`, `PSNotApplyErrorActionToStderr`, `PSAnsiRendering`, `PSAnsiProgressFeatureName` stable (#15864) +- Enhance `Remove-Item` to work with OneDrive (#15571) (Thanks @iSazonov!) +- Make global tool entrypoint class static (#15880) +- Update `ServerRemoteHost` version to be same as `PSVersion` (#15809) +- Make the initialization of `HttpKnownHeaderNames` thread safe (#15519) (Thanks @iSazonov!) +- `ConvertTo-Csv`: Quote fields with quotes and newlines when using `-UseQuotes AsNeeded` (#15765) (Thanks @lselden!) +- Forwarding progress stream changes from `Foreach-Object -Parallel` runspaces (#14271) (Thanks @powercode!) +- Add validation to `$PSStyle` to reject printable text when setting a property that only expects ANSI escape sequence (#15825) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@xtqqczze

+ +
+ +
    +
  • Avoid unneeded array allocation in module code (#14329) (Thanks @xtqqczze!)
  • +
  • Enable and fix analysis rules CA1052, CA1067, and IDE0049 (#15840) (Thanks @xtqqczze!)
  • +
  • Avoid unnecessary allocation in formatting code (#15832) (Thanks @xtqqczze!)
  • +
  • Specify the analyzed API surface for all code quality rules (#15778) (Thanks @xtqqczze!)
  • +
+ +
+ +### Tools + +- Enable `/rebase` to automatically rebase a PR (#15808) +- Update `.editorconfig` to not replace tabs with spaces in `.tsv` files (#15815) (Thanks @SethFalco!) +- Update PowerShell team members in the changelog generation script (#15817) + +### Tests + +- Add more tests to validate the current command error handling behaviors (#15919) +- Make `Measure-Object` property test independent of the file system (#15879) +- Add more information when a `syslog` parsing error occurs (#15857) +- Harden logic when looking for `syslog` entries to be sure that we select based on the process ID (#15841) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@xtqqczze

+ +
+ +
    +
  • Disable implicit namespace imports for test projects (#15895)
  • +
  • Update language version to 10 and fix related issues (#15886)
  • +
  • Update CodeQL workflow to use Ubuntu 18.04 (#15868)
  • +
  • Bump the version of various packages (#15944, #15934, #15935, #15891, #15812, #15822) (Thanks @xtqqczze!)
  • +
+ +
+ +### Documentation and Help Content + +- Update `README` and `metadata files` for release `v7.2.0-preview.8` (#15819) +- Update changelogs for 7.0.7 and 7.1.4 (#15921) +- Fix spelling in XML docs (#15939) (Thanks @slowy07!) +- Update PowerShell Committee members (#15837) + +[7.2.0-preview.9]: https://github.com/PowerShell/PowerShell/compare/v7.2.0-preview.8...v7.2.0-preview.9 + +## [7.2.0-preview.8] - 2021-07-22 + +### Engine Updates and Fixes + +- Add a Windows mode to `$PSNativeCommandArgumentPassing` that allows some commands to use legacy argument passing (#15408) +- Use `nameof` to get parameter names when creating `ArgumentNullException` (#15604) (Thanks @gukoff!) +- Test if a command is 'Out-Default' more thoroughly for transcribing scenarios (#15653) +- Add `Microsoft.PowerShell.Crescendo` to telemetry allow list (#15372) + +### General Cmdlet Updates and Fixes + +- Use `$PSStyle.Formatting.FormatAccent` for `Format-List` and `$PSStyle.Formatting.TableHeader` for `Format-Table` output (#14406) +- Highlight using error color the exception `Message` and underline in `PositionMessage` for `Get-Error` (#15786) +- Implement a completion for View parameter of format cmdlets (#14513) (Thanks @iSazonov!) +- Add support to colorize `FileInfo` filenames (#14403) +- Don't serialize to JSON ETS properties for `DateTime` and `string` types (#15665) +- Fix `HyperVSocketEndPoint.ServiceId` setter (#15704) (Thanks @xtqqczze!) +- Add `DetailedView` to `$ErrorView` (#15609) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@iSazonov, @xtqqczze

+ +
+ +
    +
  • Remove consolehost.proto file (#15741) (Thanks @iSazonov!)
  • +
  • Implement IDisposable for ConvertToJsonCommand (#15787) (Thanks @xtqqczze!)
  • +
  • Fix IDisposable implementation for CommandPathSearch (#15793) (Thanks @xtqqczze!)
  • +
  • Delete IDE dispose analyzer rules (#15798) (Thanks @xtqqczze!)
  • +
  • Seal private classes (#15725) (Thanks @xtqqczze!)
  • +
  • Enable IDE0029: UseCoalesceExpression (#15770) (Thanks @xtqqczze!)
  • +
  • Enable IDE0070: UseSystemHashCode (#15715) (Thanks @xtqqczze!)
  • +
  • Enable IDE0030: UseCoalesceExpressionForNullable (#14289) (Thanks @xtqqczze!)
  • +
  • Fix CA1846 and CA1845 for using AsSpan instead of Substring (#15738)
  • +
  • Use List<T>.RemoveAll to avoid creating temporary list (#15686) (Thanks @xtqqczze!)
  • +
  • Enable IDE0044: MakeFieldReadonly (#13880) (Thanks @xtqqczze!)
  • +
  • Disable IDE0130 (#15728) (Thanks @xtqqczze!)
  • +
  • Make classes sealed (#15675) (Thanks @xtqqczze!)
  • +
  • Enable CA1043: Use integral or string argument for indexers (#14467) (Thanks @xtqqczze!)
  • +
  • Enable CA1812 (#15674) (Thanks @xtqqczze!)
  • +
  • Replace Single with First when we know the element count is 1 (#15676) (Thanks @xtqqczze!)
  • +
  • Skip analyzers for Microsoft.Management.UI.Internal (#15677) (Thanks @xtqqczze!)
  • +
  • Fix CA2243: Attribute string literals should parse correctly (#15622) (Thanks @xtqqczze!)
  • +
  • Enable CA1401 (#15621) (Thanks @xtqqczze!)
  • +
  • Fix CA1309: Use ordinal StringComparison in Certificate Provider (#14352) (Thanks @xtqqczze!)
  • +
  • Fix CA1839: Use Environment.ProcessPath (#15650) (Thanks @xtqqczze!)
  • +
  • Add new analyzer rules (#15620) (Thanks @xtqqczze!)
  • +
+ +
+ +### Tools + +- Add `SkipRoslynAnalyzers` parameter to `Start-PSBuild` (#15640) (Thanks @xtqqczze!) +- Create issue template for issues updating PowerShell through Windows update. (#15700) +- Add `DocumentationAnalyzers` to build (#14336) (Thanks @xtqqczze!) +- Convert GitHub issue templates to modern forms (#15645) + +### Tests + +- Add more tests for `ConvertFrom-Json` (#15706) (Thanks @strawgate!) +- Update `glob-parent` and `hosted-git-info` test dependencies (#15643) + +### Build and Packaging Improvements + +
+ + +Update .NET to version v6.0.0-preview.6 + + +
    +
  • Add new package name for osx-arm64 (#15813)
  • +
  • Prefer version when available for dotnet-install (#15810)
  • +
  • Make warning about MU being required dynamic (#15776)
  • +
  • Add Start-PSBootstrap before running tests (#15804)
  • +
  • Update to .NET 6 Preview 6 and use crossgen2 (#15763)
  • +
  • Enable ARM64 packaging for macOS (#15768)
  • +
  • Make Microsoft Update opt-out/in check boxes work (#15784)
  • +
  • Add Microsoft Update opt out to MSI install (#15727)
  • +
  • Bump NJsonSchema from 10.4.4 to 10.4.5 (#15769)
  • +
  • Fix computation of SHA512 checksum (#15736)
  • +
  • Update the script to use quality parameter for dotnet-install (#15731)
  • +
  • Generate SHA512 checksum file for all packages (#15678)
  • +
  • Enable signing daily release build with lifetime certificate (#15642)
  • +
  • Update metadata and README for 7.2.0-preview.7 (#15593)
  • +
+ +
+ +### Documentation and Help Content + +- Fix broken RFC links (#15807) +- Add to bug report template getting details from `Get-Error` (#15737) +- Update issue templates to link to new docs (#15711) +- Add @jborean93 to Remoting Working Group (#15683) + +[7.2.0-preview.8]: https://github.com/PowerShell/PowerShell/compare/v7.2.0-preview.7...v7.2.0-preview.8 + +## [7.2.0-preview.7] - 2021-06-17 + +### Breaking Changes + +- Remove PSDesiredStateConfiguration v2.0.5 module and published it to the PowerShell Gallery (#15536) + +### Engine Updates and Fixes + +- Fix splatting being treated as positional parameter in completions (#14623) (Thanks @MartinGC94!) +- Prevent PowerShell from crashing when a telemetry mutex can't be created (#15574) (Thanks @gukoff!) +- Ignore all exceptions when disposing an instance of a subsystem implementation (#15511) +- Wait for SSH exit when closing remote connection (#14635) (Thanks @dinhngtu!) + +### Performance + +- Retrieve `ProductVersion` using informational version attribute in `AmsiUtils.Init()` (#15527) (Thanks @Fs00!) + +### General Cmdlet Updates and Fixes + +- Fix retrieving dynamic parameters from provider even if globbed path returns no results (#15525) +- Revert "Enhance Remove-Item to work with OneDrive (#15260)" due to long path issue (#15546) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@octos4murai, @iSazonov, @Fs00

+ +
+ +
    +
  • Correct parameter name passed to exception in PSCommand constructor (#15580) (Thanks @octos4murai!)
  • +
  • Enable nullable: System.Management.Automation.ICommandRuntime (#15566) (Thanks @iSazonov!)
  • +
  • Clean up code regarding AppDomain.CreateDomain and AppDomain.Unload (#15554)
  • +
  • Replace ProcessModule.FileName with Environment.ProcessPath and remove PSUtils.GetMainModule (#15012) (Thanks @Fs00!)
  • +
+ +
+ +### Tests + +- Fix `Start-Benchmarking` to put `TargetPSVersion` and `TargetFramework` in separate parameter sets (#15508) +- Add `win-x86` test package to the build (#15517) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@schuelermine

+ +
+ +
    +
  • Update README.md and metadata.json for version 7.2.0-preview.6 (#15464)
  • +
  • Make sure GA revision increases from RC and Preview releases (#15558)
  • +
  • Remove SupportsShouldProcess from Start-PSBootstrap in build.psm1 (#15491) (Thanks @schuelermine!)
  • +
  • Update DotnetMetadataRuntime.json next channel to take daily build from .NET preview 5 (#15518)
  • +
  • Fix deps.json update in the release pipeline (#15486)
  • +
+ +
+ +### Documentation and Help Content + +- Add new members to Engine and Cmdlet Working Groups document (#15560) +- Update the `mdspell` command to exclude the folder that should be ignored (#15576) +- Replace 'User Voice' with 'Feedback Hub' in `README.md` (#15557) +- Update Virtual User Group chat links (#15505) (Thanks @Jaykul!) +- Fix typo in `FileSystemProvider.cs` (#15445) (Thanks @eltociear!) +- Add `PipelineStoppedException` notes to PowerShell API (#15324) +- Updated governance on Working Groups (WGs) (#14603) +- Correct and improve XML documentation comments on `PSCommand` (#15568) (Thanks @octos4murai!) + +[7.2.0-preview.7]: https://github.com/PowerShell/PowerShell/compare/v7.2.0-preview.6...v7.2.0-preview.7 + +## [7.2.0-preview.6] - 2021-05-27 + +### Experimental Features + +- [Breaking Change] Update prediction interface to provide additional feedback to a predictor plugin (#15421) + +### Performance + +- Avoid collecting logs in buffer if a pipeline execution event is not going to be logged (#15350) +- Avoid allocation in `LanguagePrimitives.UpdateTypeConvertFromTypeTable` (#15168) (Thanks @xtqqczze!) +- Replace `Directory.GetDirectories` with `Directory.EnumerateDirectories` to avoid array allocations (#15167) (Thanks @xtqqczze!) +- Use `List.ConvertAll` instead of `LINQ` (#15140) (Thanks @xtqqczze!) + +### General Cmdlet Updates and Fixes + +- Use `AllocConsole` before initializing CLR to ensure codepage is correct for WinRM remoting (PowerShell/PowerShell-Native#70) (Thanks @jborean93!) +- Add completions for `#requires` statements (#14596) (Thanks @MartinGC94!) +- Add completions for comment-based help keywords (#15337) (Thanks @MartinGC94!) +- Move cross platform DSC code to a PowerShell engine subsystem (#15127) +- Fix `Minimal` progress view to handle activity that is longer than console width (#15264) +- Handle exception if ConsoleHost tries to set cursor out of bounds because screen buffer changed (#15380) +- Fix `NullReferenceException` in DSC `ClearCache()` (#15373) +- Update `ControlSequenceLength` to handle colon as a virtual terminal parameter separator (#14942) +- Update the summary comment for `StopTranscriptCmdlet.cs` (#15349) (Thanks @dbaileyut!) +- Remove the unusable alias `d` for the `-Directory` parameter from `Get-ChildItem` (#15171) (Thanks @kvprasoon!) +- Fix tab completion for un-localized `about` topics (#15265) (Thanks @MartinGC94!) +- Remove the unneeded SSH stdio handle workaround (#15308) +- Add `LoadAssemblyFromNativeMemory` API to load assemblies from memory in a native PowerShell host (#14652) (Thanks @awakecoding!) +- Re-implement `Remove-Item` OneDrive support (#15260) (Thanks @iSazonov!) +- Kill native processes in pipeline when pipeline is disposed on Unix (#15287) +- Default to MTA on Windows platforms where STA is not supported (#15106) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@xtqqczze, @powercode, @bcwood

+ +
+ +
    +
  • Enable nullable in some classes (#14185, #14177, #14159, #14191, #14162, #14150, #14156, #14161, #14155, #14163, #14181, #14157, #14151) (Thanks @powercode!)
  • +
  • Annotate ThrowTerminatingError with DoesNotReturn attribute (#15352) (Thanks @powercode!)
  • +
  • Use GetValueOrDefault() for nullable PSLanguageMode (#13849) (Thanks @bcwood!)
  • +
  • Enable SA1008: Opening parenthesis should be spaced correctly (#14242) (Thanks @xtqqczze!)
  • +
+ +
+ +### Tools + +- Add `winget` release script (#15050) + +### Tests + +- Enable cross-runtime benchmarking to compare different .NET runtimes (#15387) (Thanks @adamsitnik!) +- Add the performance benchmark project for PowerShell performance testing (#15242) + +### Build and Packaging Improvements + +
+ + +Update .NET to version v6.0.0-preview.4 + + +
    +
  • Suppress prompting when uploading the msixbundle package to blob (#15227)
  • +
  • Update to .NET preview 4 SDK (#15452)
  • +
  • Update AppxManifest.xml with newer OS version to allow PowerShell installed from Windows Store to make system-level changes (#15375)
  • +
  • Ensure the build works when PSDesiredStateConfiguration module is pulled in from PSGallery (#15355)
  • +
  • Make sure daily release tag does not change when retrying failures (#15286)
  • +
  • Improve messages and behavior when there's a problem in finding zip files (#15284)
  • +
+ +
+ +### Documentation and Help Content + +- Add documentation comments section to coding guidelines (#14316) (Thanks @xtqqczze!) + +[7.2.0-preview.6]: https://github.com/PowerShell/PowerShell/compare/v7.2.0-preview.5...v7.2.0-preview.6 + +## [7.2.0-preview.5] - 2021-04-14 + +### Breaking Changes + +- Make PowerShell Linux deb and RPM packages universal (#15109) +- Enforce AppLocker Deny configuration before Execution Policy Bypass configuration (#15035) +- Disallow mixed dash and slash in command-line parameter prefix (#15142) (Thanks @davidBar-On!) + +### Experimental Features + +- `PSNativeCommandArgumentPassing`: Use `ArgumentList` for native executable invocation (breaking change) (#14692) + +### Engine Updates and Fixes + +- Add `IArgumentCompleterFactory` for parameterized `ArgumentCompleters` (#12605) (Thanks @powercode!) + +### General Cmdlet Updates and Fixes + +- Fix SSH remoting connection never finishing with misconfigured endpoint (#15175) +- Respect `TERM` and `NO_COLOR` environment variables for `$PSStyle` rendering (#14969) +- Use `ProgressView.Classic` when Virtual Terminal is not supported (#15048) +- Fix `Get-Counter` issue with `-Computer` parameter (#15166) (Thanks @krishnayalavarthi!) +- Fix redundant iteration while splitting lines (#14851) (Thanks @hez2010!) +- Enhance `Remove-Item -Recurse` to work with OneDrive (#14902) (Thanks @iSazonov!) +- Change minimum depth to 0 for `ConvertTo-Json` (#14830) (Thanks @kvprasoon!) +- Allow `Set-Clipboard` to accept empty string (#14579) +- Turn on and off `DECCKM` to modify keyboard mode for Unix native commands to work correctly (#14943) +- Fall back to `CopyAndDelete()` when `MoveTo()` fails due to an `IOException` (#15077) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@xtqqczze, @iSazonov, @ZhiZe-ZG

+ +
+ +
    +
  • Update .NET to 6.0.0-preview.3 (#15221)
  • +
  • Add space before comma to hosting test to fix error reported by SA1001 (#15224)
  • +
  • Add SecureStringHelper.FromPlainTextString helper method for efficient secure string creation (#14124) (Thanks @xtqqczze!)
  • +
  • Use static lambda keyword (#15154) (Thanks @iSazonov!)
  • +
  • Remove unnecessary Array -> List -> Array conversion in ProcessBaseCommand.AllProcesses (#15052) (Thanks @xtqqczze!)
  • +
  • Standardize grammar comments in Parser.cs (#15114) (Thanks @ZhiZe-ZG!)
  • +
  • Enable SA1001: Commas should be spaced correctly (#14171) (Thanks @xtqqczze!)
  • +
  • Refactor MultipleServiceCommandBase.AllServices (#15053) (Thanks @xtqqczze!)
  • +
+ +
+ +### Tools + +- Use Unix line endings for shell scripts (#15180) (Thanks @xtqqczze!) + +### Tests + +- Add the missing tag in Host Utilities tests (#14983) +- Update `copy-props` version in `package.json` (#15124) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@JustinGrote

+ +
+ +
    +
  • Fix yarn-lock for copy-props (#15225)
  • +
  • Make package validation regular expression accept universal Linux packages (#15226)
  • +
  • Bump NJsonSchema from 10.4.0 to 10.4.1 (#15190)
  • +
  • Make MSI and EXE signing always copy to fix daily build (#15191)
  • +
  • Sign internals of EXE package so that it works correctly when signed (#15132)
  • +
  • Bump Microsoft.NET.Test.Sdk from 16.9.1 to 16.9.4 (#15141)
  • +
  • Update daily release tag format to work with new Microsoft Update work (#15164)
  • +
  • Feature: Add Ubuntu 20.04 Support to install-powershell.sh (#15095) (Thanks @JustinGrote!)
  • +
  • Treat rebuild branches like release branches (#15099)
  • +
  • Update WiX to 3.11.2 (#15097)
  • +
  • Bump NJsonSchema from 10.3.11 to 10.4.0 (#15092)
  • +
  • Allow patching of preview releases (#15074)
  • +
  • Bump Newtonsoft.Json from 12.0.3 to 13.0.1 (#15084, #15085)
  • +
  • Update the minSize build package filter to be explicit (#15055)
  • +
  • Bump NJsonSchema from 10.3.10 to 10.3.11 (#14965)
  • +
+ +
+ +### Documentation and Help Content + +- Merge `7.2.0-preview.4` changes to master (#15056) +- Update `README` and `metadata.json` (#15046) +- Fix broken links for `dotnet` CLI (#14937) + +[7.2.0-preview.5]: https://github.com/PowerShell/PowerShell/compare/v7.2.0-preview.4...v7.2.0-preview.5 + +## [7.2.0-preview.4] - 2021-03-16 + +### Breaking Changes + +- Fix `Get-Date -UFormat` `%G` and `%g` behavior (#14555) (Thanks @brianary!) + +### Engine Updates and Fixes + +- Update engine script signature validation to match `Get-AuthenticodeSignature` logic (#14849) +- Avoid array allocations from `GetDirectories` and `GetFiles` (#14327) (Thanks @xtqqczze!) + +### General Cmdlet Updates and Fixes + +- Add `UseOSCIndicator` setting to enable progress indicator in terminal (#14927) +- Re-enable VT mode on Windows after running command in `ConsoleHost` (#14413) +- Fix `Move-Item` for `FileSystemProvider` to use copy-delete instead of move for DFS paths (#14913) +- Fix `PromptForCredential()` to add `targetName` as domain (#14504) +- Update `Concise` `ErrorView` to not show line information for errors from script module functions (#14912) +- Remove the 32,767 character limit on the environment block for `Start-Process` (#14111) (Thanks @hbuckle!) +- Don't write possible secrets to verbose stream for web cmdlets (#14788) + +### Tools + +- Update `dependabot` configuration to V2 format (#14882) +- Add tooling issue slots in PR template (#14697) + +### Tests + +- Move misplaced test file to tests directory (#14908) (Thanks @MarianoAlipi!) +- Refactor MSI CI (#14753) + +### Build and Packaging Improvements + +
+ + +Update .NET to version 6.0.100-preview.2.21155.3 + + +
    +
  • Update .NET to version 6.0.100-preview.2.21155.3 (#15007)
  • +
  • Bump Microsoft.PowerShell.Native to 7.2.0-preview.1 (#15030)
  • +
  • Create MSIX Bundle package in release pipeline (#14982)
  • +
  • Build self-contained minimal size package for Guest Config team (#14976)
  • +
  • Bump XunitXml.TestLogger from 3.0.62 to 3.0.66 (#14993) (Thanks @dependabot[bot]!)
  • +
  • Enable building PowerShell for Apple M1 runtime (#14923)
  • +
  • Fix the variable name in the condition for miscellaneous analysis CI (#14975)
  • +
  • Fix the variable usage in CI yaml (#14974)
  • +
  • Disable running Markdown link verification in release build CI (#14971)
  • +
  • Bump Microsoft.CodeAnalysis.CSharp from 3.9.0-3.final to 3.9.0 (#14934) (Thanks @dependabot[bot]!)
  • +
  • Declare which variable group is used for checking the blob in the release build (#14970)
  • +
  • Update metadata and script to enable consuming .NET daily builds (#14940)
  • +
  • Bump NJsonSchema from 10.3.9 to 10.3.10 (#14933) (Thanks @dependabot[bot]!)
  • +
  • Use template that disables component governance for CI (#14938)
  • +
  • Add suppress for nuget multi-feed warning (#14893)
  • +
  • Bump NJsonSchema from 10.3.8 to 10.3.9 (#14926) (Thanks @dependabot[bot]!)
  • +
  • Add exe wrapper to release (#14881)
  • +
  • Bump Microsoft.ApplicationInsights from 2.16.0 to 2.17.0 (#14847)
  • +
  • Bump Microsoft.NET.Test.Sdk from 16.8.3 to 16.9.1 (#14895) (Thanks @dependabot[bot]!)
  • +
  • Bump NJsonSchema from 10.3.7 to 10.3.8 (#14896) (Thanks @dependabot[bot]!)
  • +
  • Disable codesign validation where the file type is not supported (#14885)
  • +
  • Fixing broken Experimental Feature list in powershell.config.json (#14858)
  • +
  • Bump NJsonSchema from 10.3.6 to 10.3.7 (#14855)
  • +
  • Add exe wrapper for Microsoft Update scenarios (#14737)
  • +
  • Install wget on CentOS 7 docker image (#14857)
  • +
  • Fix install-dotnet download (#14856)
  • +
  • Fix Bootstrap step in Windows daily test runs (#14820)
  • +
  • Bump NJsonSchema from 10.3.5 to 10.3.6 (#14818)
  • +
  • Bump NJsonSchema from 10.3.4 to 10.3.5 (#14807)
  • +
+ +
+ +### Documentation and Help Content + +- Update `README.md` and `metadata.json` for upcoming releases (#14755) +- Merge 7.1.3 and 7.0.6 changelog to master (#15009) +- Update `README` and `metadata.json` for releases (#14997) +- Update ChangeLog for `v7.1.2` release (#14783) +- Update ChangeLog for `v7.0.5` release (#14782) (Internal 14479) + +[7.2.0-preview.4]: https://github.com/PowerShell/PowerShell/compare/v7.2.0-preview.3...v7.2.0-preview.4 + +## [7.2.0-preview.3] - 2021-02-11 + +### Breaking Changes + +- Fix `Get-Date -UFormat %u` behavior to comply with ISO 8601 (#14549) (Thanks @brianary!) + +### Engine Updates and Fixes + +- Together with `PSDesiredStateConfiguration` `v3` module allows `Get-DscResource`, `Invoke-DscResource` and DSC configuration compilation on all platforms, supported by PowerShell (using class-based DSC resources). + +### Performance + +- Avoid array allocations from `Directory.GetDirectories` and `Directory.GetFiles`. (#14326) (Thanks @xtqqczze!) +- Avoid `string.ToLowerInvariant()` from `GetEnvironmentVariableAsBool()` to avoid loading libicu at startup (#14323) (Thanks @iSazonov!) +- Get PowerShell version in `PSVersionInfo` using assembly attribute instead of `FileVersionInfo` (#14332) (Thanks @Fs00!) + +### General Cmdlet Updates and Fixes + +- Suppress `Write-Progress` in `ConsoleHost` if output is redirected and fix tests (#14716) +- Experimental feature `PSAnsiProgress`: Add minimal progress bar using ANSI rendering (#14414) +- Fix web cmdlets to properly construct URI from body when using `-NoProxy` (#14673) +- Update the `ICommandPredictor` to provide more feedback and also make feedback easier to be correlated (#14649) +- Reset color after writing `Verbose`, `Debug`, and `Warning` messages (#14698) +- Fix using variable for nested `ForEach-Object -Parallel` calls (#14548) +- When formatting, if collection is modified, don't fail the entire pipeline (#14438) +- Improve completion of parameters for attributes (#14525) (Thanks @MartinGC94!) +- Write proper error messages for `Get-Command ' '` (#13564) (Thanks @jakekerr!) +- Fix typo in the resource string `ProxyURINotSupplied` (#14526) (Thanks @romero126!) +- Add support to `$PSStyle` for strikethrough and hyperlinks (#14461) +- Fix `$PSStyle` blink codes (#14447) (Thanks @iSazonov!) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@xtqqczze, @powercode

+ +
+ +
    +
  • Fix coding style issues: RCS1215, IDE0090, SA1504, SA1119, RCS1139, IDE0032 (#14356, #14341, #14241, #14204, #14442, #14443) (Thanks @xtqqczze!)
  • +
  • Enable coding style checks: CA2249, CA1052, IDE0076, IDE0077, SA1205, SA1003, SA1314, SA1216, SA1217, SA1213 (#14395, #14483, #14494, #14495, #14441, #14476, #14470, #14471, #14472) (Thanks @xtqqczze!)
  • +
  • Enable nullable in PowerShell codebase (#14160, #14172, #14088, #14154, #14166, #14184, #14178) (Thanks @powercode!)
  • +
  • Use string.Split(char) instead of string.Split(string) (#14465) (Thanks @xtqqczze!)
  • +
  • Use string.Contains(char) overload (#14368) (Thanks @xtqqczze!)
  • +
  • Refactor complex if statements (#14398) (Thanks @xtqqczze!)
  • +
+ +
+ +### Tools + +- Update script to use .NET 6 build resources (#14705) +- Fix the daily GitHub Action (#14711) (Thanks @imba-tjd!) +- GitHub Actions: fix deprecated `::set-env` (#14629) (Thanks @imba-tjd!) +- Update Markdown test tools (#14325) (Thanks @RDIL!) +- Upgrade `StyleCopAnalyzers` to `v1.2.0-beta.312` (#14354) (Thanks @xtqqczze!) + +### Tests + +- Remove packaging from daily Windows build (#14749) +- Update link to the Manning book (#14750) +- A separate Windows packaging CI (#14670) +- Update `ini` component version in test `package.json` (#14454) +- Disable `libmi` dependent tests for macOS. (#14446) + +### Build and Packaging Improvements + +
+ +
    +
  • Fix the NuGet feed name and URL for .NET 6
  • +
  • Fix third party signing for files in sub-folders (#14751)
  • +
  • Make build script variable an ArrayList to enable Add() method (#14748)
  • +
  • Remove old .NET SDKs to make dotnet restore work with the latest SDK in CI pipeline (#14746)
  • +
  • Remove outdated Linux dependencies (#14688)
  • +
  • Bump .NET SDK version to 6.0.0-preview.1 (#14719)
  • +
  • Bump NJsonSchema to 10.3.4 (#14714)
  • +
  • Update daily GitHub action to allow manual trigger (#14718)
  • +
  • Bump XunitXml.TestLogger to 3.0.62 (#14702)
  • +
  • Make universal deb package based on the deb package specification (#14681)
  • +
  • Add manual release automation steps and improve changelog script (#14445)
  • +
  • Fix release build to upload global tool packages to artifacts (#14620)
  • +
  • Port changes from the PowerShell v7.0.4 release (#14637)
  • +
  • Port changes from the PowerShell v7.1.1 release (#14621)
  • +
  • Updated README and metadata.json (#14401, #14606, #14612)
  • +
  • Do not push nupkg artifacts to MyGet (#14613)
  • +
  • Use one feed in each nuget.config in official builds (#14363)
  • +
  • Fix path signed RPMs are uploaded from in release build (#14424)
  • +
+ +
+ +### Documentation and Help Content + +- Update distribution support request template to point to .NET 5.0 support document (#14578) +- Remove security GitHub issue template (#14453) +- Add intent for using the Discussions feature in repository (#14399) +- Fix Universal Dashboard to refer to PowerShell Universal (#14437) +- Update document link because of HTTP 301 redirect (#14431) (Thanks @xtqqczze!) + +[7.2.0-preview.3]: https://github.com/PowerShell/PowerShell/compare/v7.2.0-preview.2...v7.2.0-preview.3 + +## [7.2.0-preview.2] - 2020-12-15 + +### Breaking Changes + +- Improve detection of mutable value types (#12495) (Thanks @vexx32!) +- Ensure `-PipelineVariable` is set for all output from script cmdlets (#12766) (Thanks @vexx32!) + +### Experimental Features + +- `PSAnsiRendering`: Enable ANSI formatting via `$PSStyle` and support suppressing ANSI output (#13758) + +### Performance + +- Optimize `IEnumerable` variant of replace operator (#14221) (Thanks @iSazonov!) +- Refactor multiply operation for better performance in two `Microsoft.PowerShell.Commands.Utility` methods (#14148) (Thanks @xtqqczze!) +- Use `Environment.TickCount64` instead of `Datetime.Now` as the random seed for AppLocker test file content (#14283) (Thanks @iSazonov!) +- Avoid unnecessary array allocations when searching in GAC (#14291) (Thanks @xtqqczze!) +- Use `OrdinalIgnoreCase` in `CommandLineParser` (#14303) (Thanks @iSazonov!) +- Use `StringComparison.Ordinal` instead of `StringComparison.CurrentCulture` (#14298) (Thanks @iSazonov!) +- Avoid creating instances of the generated delegate helper class in `-replace` implementation (#14128) + +### General Cmdlet Updates and Fixes + +- Write better error message if config file is broken (#13496) (Thanks @iSazonov!) +- Make AppLocker Enforce mode take precedence over UMCI Audit mode (#14353) +- Add `-SkipLimitCheck` switch to `Import-PowerShellDataFile` (#13672) +- Restrict `New-Object` in NoLanguage mode under lock down (#14140) (Thanks @krishnayalavarthi!) +- The `-Stream` parameter now works with directories (#13941) (Thanks @kyanha!) +- Avoid an exception if file system does not support reparse points (#13634) (Thanks @iSazonov!) +- Enable `CA1012`: Abstract types should not have public constructors (#13940) (Thanks @xtqqczze!) +- Enable `SA1212`: Property accessors should follow order (#14051) (Thanks @xtqqczze!) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@xtqqczze, @matthewjdegarmo, @powercode, @Gimly

+ +
+ +
    +
  • Enable SA1007: Operator keyword should be followed by space (#14130) (Thanks @xtqqczze!)
  • +
  • Expand where alias to Where-Object in Reset-PWSHSystemPath.ps1 (#14113) (Thanks @matthewjdegarmo!)
  • +
  • Fix whitespace issues (#14092) (Thanks @xtqqczze!)
  • +
  • Add StyleCop.Analyzers package (#13963) (Thanks @xtqqczze!)
  • +
  • Enable IDE0041: UseIsNullCheck (#14041) (Thanks @xtqqczze!)
  • +
  • Enable IDE0082: ConvertTypeOfToNameOf (#14042) (Thanks @xtqqczze!)
  • +
  • Remove unnecessary usings part 4 (#14023) (Thanks @xtqqczze!)
  • +
  • Fix PriorityAttribute name (#14094) (Thanks @xtqqczze!)
  • +
  • Enable nullable: System.Management.Automation.Interpreter.IBoxableInstruction (#14165) (Thanks @powercode!)
  • +
  • Enable nullable: System.Management.Automation.Provider.IDynamicPropertyProvider (#14167) (Thanks @powercode!)
  • +
  • Enable nullable: System.Management.Automation.Language.IScriptExtent (#14179) (Thanks @powercode!)
  • +
  • Enable nullable: System.Management.Automation.Language.ICustomAstVisitor2 (#14192) (Thanks @powercode!)
  • +
  • Enable nullable: System.Management.Automation.LanguagePrimitives.IConversionData (#14187) (Thanks @powercode!)
  • +
  • Enable nullable: System.Automation.Remoting.Client.IWSManNativeApiFacade (#14186) (Thanks @powercode!)
  • +
  • Enable nullable: System.Management.Automation.Language.ISupportsAssignment (#14180) (Thanks @powercode!)
  • +
  • Enable nullable: System.Management.Automation.ICommandRuntime2 (#14183) (Thanks @powercode!)
  • +
  • Enable nullable: System.Management.Automation.IOutputProcessingState (#14175) (Thanks @powercode!)
  • +
  • Enable nullable: System.Management.Automation.IJobDebugger (#14174) (Thanks @powercode!)
  • +
  • Enable nullable: System.Management.Automation.Interpreter.IInstructionProvider (#14173) (Thanks @powercode!)
  • +
  • Enable nullable: System.Management.Automation.IHasSessionStateEntryVisibility (#14169) (Thanks @powercode!)
  • +
  • Enable nullable: System.Management.Automation.Tracing.IEtwEventCorrelator (#14168) (Thanks @powercode!)
  • +
  • Fix syntax error in Windows packaging script (#14377)
  • +
  • Remove redundant local assignment in AclCommands (#14358) (Thanks @xtqqczze!)
  • +
  • Enable nullable: System.Management.Automation.Language.IAstPostVisitHandler (#14164) (Thanks @powercode!)
  • +
  • Enable nullable: System.Management.Automation.IModuleAssemblyInitializer (#14158) (Thanks @powercode!)
  • +
  • Use Microsoft.PowerShell.MarkdownRender package from nuget.org (#14090)
  • +
  • Replace GetFiles in TestModuleManifestCommand (#14317) (Thanks @xtqqczze!)
  • +
  • Enable nullable: System.Management.Automation.Provider.IContentWriter (#14152) (Thanks @powercode!)
  • +
  • Simplify getting Encoding in TranscriptionOption.FlushContentToDisk (#13910) (Thanks @Gimly!)
  • +
  • Mark applicable structs as readonly and use in-modifier (#13919) (Thanks @xtqqczze!)
  • +
  • Enable nullable: System.Management.Automation.IArgumentCompleter (#14182) (Thanks @powercode!)
  • +
  • Enable CA1822: Mark private members as static (#13897) (Thanks @xtqqczze!)
  • +
  • Fix IDE0090: Simplify new expression part 6 (#14338) (Thanks @xtqqczze!)
  • +
  • Avoid array allocations from GetDirectories/GetFiles. (#14328) (Thanks @xtqqczze!)
  • +
  • Avoid array allocations from GetDirectories/GetFiles. (#14330) (Thanks @xtqqczze!)
  • +
  • Fix RCS1188: Remove redundant auto-property initialization part 2 (#14262) (Thanks @xtqqczze!)
  • +
  • Enable nullable: System.Management.Automation.Host.IHostSupportsInteractiveSession (#14170) (Thanks @powercode!)
  • +
  • Enable nullable: System.Management.Automation.Provider.IPropertyCmdletProvider (#14176) (Thanks @powercode!)
  • +
  • Fix IDE0090: Simplify new expression part 5 (#14301) (Thanks @xtqqczze!)
  • +
  • Enable IDE0075: SimplifyConditionalExpression (#14078) (Thanks @xtqqczze!)
  • +
  • Remove unnecessary usings part 9 (#14288) (Thanks @xtqqczze!)
  • +
  • Fix StyleCop and MarkdownLint CI failures (#14297) (Thanks @xtqqczze!)
  • +
  • Enable SA1000: Keywords should be spaced correctly (#13973) (Thanks @xtqqczze!)
  • +
  • Fix RCS1188: Remove redundant auto-property initialization part 1 (#14261) (Thanks @xtqqczze!)
  • +
  • Mark private members as static part 10 (#14235) (Thanks @xtqqczze!)
  • +
  • Mark private members as static part 9 (#14234) (Thanks @xtqqczze!)
  • +
  • Fix SA1642 for Microsoft.Management.Infrastructure.CimCmdlets (#14239) (Thanks @xtqqczze!)
  • +
  • Use AsSpan/AsMemory slice constructor (#14265) (Thanks @xtqqczze!)
  • +
  • Fix IDE0090: Simplify new expression part 4.6 (#14260) (Thanks @xtqqczze!)
  • +
  • Fix IDE0090: Simplify new expression part 4.5 (#14259) (Thanks @xtqqczze!)
  • +
  • Fix IDE0090: Simplify new expression part 4.3 (#14257) (Thanks @xtqqczze!)
  • +
  • Fix IDE0090: Simplify new expression part 4.2 (#14256) (Thanks @xtqqczze!)
  • +
  • Fix IDE0090: Simplify new expression part 2 (#14200) (Thanks @xtqqczze!)
  • +
  • Enable SA1643: Destructor summary documentation should begin with standard text (#14236) (Thanks @xtqqczze!)
  • +
  • Fix IDE0090: Simplify new expression part 4.4 (#14258) (Thanks @xtqqczze!)
  • +
  • Use xml documentation child blocks correctly (#14249) (Thanks @xtqqczze!)
  • +
  • Fix IDE0090: Simplify new expression part 4.1 (#14255) (Thanks @xtqqczze!)
  • +
  • Use consistent spacing in xml documentation tags (#14231) (Thanks @xtqqczze!)
  • +
  • Enable IDE0074: Use coalesce compound assignment (#13396) (Thanks @xtqqczze!)
  • +
  • Remove unnecessary finalizers (#14248) (Thanks @xtqqczze!)
  • +
  • Mark local variable as const (#13217) (Thanks @xtqqczze!)
  • +
  • Fix IDE0032: UseAutoProperty part 2 (#14244) (Thanks @xtqqczze!)
  • +
  • Fix IDE0032: UseAutoProperty part 1 (#14243) (Thanks @xtqqczze!)
  • +
  • Mark private members as static part 8 (#14233) (Thanks @xtqqczze!)
  • +
  • Fix CA1822: Mark members as static part 6 (#14229) (Thanks @xtqqczze!)
  • +
  • Fix CA1822: Mark members as static part 5 (#14228) (Thanks @xtqqczze!)
  • +
  • Fix CA1822: Mark members as static part 4 (#14227) (Thanks @xtqqczze!)
  • +
  • Fix CA1822: Mark members as static part 3 (#14226) (Thanks @xtqqczze!)
  • +
  • Fix CA1822: Mark members as static part 2 (#14225) (Thanks @xtqqczze!)
  • +
  • Fix CA1822: Mark members as static part 1 (#14224) (Thanks @xtqqczze!)
  • +
  • Use see keyword in documentation (#14220) (Thanks @xtqqczze!)
  • +
  • Enable CA2211: Non-constant fields should not be visible (#14073) (Thanks @xtqqczze!)
  • +
  • Enable CA1816: Dispose methods should call SuppressFinalize (#14074) (Thanks @xtqqczze!)
  • +
  • Remove incorrectly implemented finalizer (#14246) (Thanks @xtqqczze!)
  • +
  • Fix CA1822: Mark members as static part 7 (#14230) (Thanks @xtqqczze!)
  • +
  • Fix SA1122: Use string.Empty for empty strings (#14218) (Thanks @xtqqczze!)
  • +
  • Fix various xml documentation issues (#14223) (Thanks @xtqqczze!)
  • +
  • Remove unnecessary usings part 8 (#14072) (Thanks @xtqqczze!)
  • +
  • Enable SA1006: Preprocessor keywords should not be preceded by space (#14052) (Thanks @xtqqczze!)
  • +
  • Fix SA1642 for Microsoft.PowerShell.Commands.Utility (#14142) (Thanks @xtqqczze!)
  • +
  • Enable CA2216: Disposable types should declare finalizer (#14089) (Thanks @xtqqczze!)
  • +
  • Wrap and name LoadBinaryModule arguments (#14193) (Thanks @xtqqczze!)
  • +
  • Wrap and name GetListOfFilesFromData arguments (#14194) (Thanks @xtqqczze!)
  • +
  • Enable SA1002: Semicolons should be spaced correctly (#14197) (Thanks @xtqqczze!)
  • +
  • Fix IDE0090: Simplify new expression part 3 (#14201) (Thanks @xtqqczze!)
  • +
  • Enable SA1106: Code should not contain empty statements (#13964) (Thanks @xtqqczze!)
  • +
  • Code performance fixes follow-up (#14207) (Thanks @xtqqczze!)
  • +
  • Remove uninformative comments (#14199) (Thanks @xtqqczze!)
  • +
  • Fix IDE0090: Simplify new expression part 1 (#14027) (Thanks @xtqqczze!)
  • +
  • Enable SA1517: Code should not contain blank lines at start of file (#14131) (Thanks @xtqqczze!)
  • +
  • Enable SA1131: Use readable conditions (#14132) (Thanks @xtqqczze!)
  • +
  • Enable SA1507: Code should not contain multiple blank lines in a row (#14136) (Thanks @xtqqczze!)
  • +
  • Enable SA1516 Elements should be separated by blank line (#14137) (Thanks @xtqqczze!)
  • +
  • Enable IDE0031: Null check can be simplified (#13548) (Thanks @xtqqczze!)
  • +
  • Enable CA1065: Do not raise exceptions in unexpected locations (#14117) (Thanks @xtqqczze!)
  • +
  • Enable CA1000: Do not declare static members on generic types (#14097) (Thanks @xtqqczze!)
  • +
+ +
+ +### Tools + +- Fixing formatting in `Reset-PWSHSystemPath.ps1` (#13689) (Thanks @dgoldman-msft!) + +### Tests + +- Reinstate `Test-Connection` tests (#13324) +- Update Markdown test packages with security fixes (#14145) + +### Build and Packaging Improvements + +
+ +
    +
  • Fix a typo in the Get-ChangeLog function (#14129)
  • +
  • Update README and metadata.json for 7.2.0-preview.1 release (#14104)
  • +
  • Bump NJsonSchema from 10.2.2 to 10.3.1 (#14040)
  • +
  • Move windows package signing to use ESRP (#14060)
  • +
  • Use one feed in each nuget.config in official builds (#14363)
  • +
  • Fix path signed RPMs are uploaded from in release build (#14424)
  • +
  • Add Microsoft.PowerShell.MarkdownRender to the package reference list (#14386)
  • +
  • Fix issue with unsigned build (#14367)
  • +
  • Move macOS and nuget to ESRP signing (#14324)
  • +
  • Fix nuget packaging to scrub NullableAttribute (#14344)
  • +
  • Bump Microsoft.NET.Test.Sdk from 16.8.0 to 16.8.3 (#14310)
  • +
  • Bump Markdig.Signed from 0.22.0 to 0.22.1 (#14305)
  • +
  • Bump Microsoft.ApplicationInsights from 2.15.0 to 2.16.0 (#14031)
  • +
  • Move Linux to ESRP signing (#14210)
  • +
+ +
+ +### Documentation and Help Content + +- Fix example `nuget.config` (#14349) +- Fix a broken link in Code Guidelines doc (#14314) (Thanks @iSazonov!) + +[7.2.0-preview.2]: https://github.com/PowerShell/PowerShell/compare/v7.2.0-preview.1...v7.2.0-preview.2 + +## [7.2.0-preview.1] - 2020-11-17 + +### Engine Updates and Fixes + +- Change the default fallback encoding for `GetEncoding` in `Start-Transcript` to be `UTF8` without a BOM (#13732) (Thanks @Gimly!) + +### General Cmdlet Updates and Fixes + +- Update `pwsh -?` output to match docs (#13748) +- Fix `NullReferenceException` in `Test-Json` (#12942) (Thanks @iSazonov!) +- Make `Dispose` in `TranscriptionOption` idempotent (#13839) (Thanks @krishnayalavarthi!) +- Add additional Microsoft PowerShell modules to the tracked modules list (#12183) +- Relax further `SSL` verification checks for `WSMan` on non-Windows hosts with verification available (#13786) (Thanks @jborean93!) +- Add the `OutputTypeAttribute` to `Get-ExperimentalFeature` (#13738) (Thanks @ThomasNieto!) +- Fix blocking wait when starting file associated with a Windows application (#13750) +- Emit warning if `ConvertTo-Json` exceeds `-Depth` value (#13692) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@xtqqczze, @mkswd, @ThomasNieto, @PatLeong, @paul-cheung, @georgettica

+ +
+ +
    +
  • Fix RCS1049: Simplify boolean comparison (#13994) (Thanks @xtqqczze!)
  • +
  • Enable IDE0062: Make local function static (#14044) (Thanks @xtqqczze!)
  • +
  • Enable CA2207: Initialize value type static fields inline (#14068) (Thanks @xtqqczze!)
  • +
  • Enable CA1837: Use ProcessId and CurrentManagedThreadId from System.Environment (#14063) (Thanks @xtqqczze and @PatLeong!)
  • +
  • Remove unnecessary using directives (#14014, #14017, #14021, #14050, #14065, #14066, #13863, #13860, #13861, #13814) (Thanks @xtqqczze and @ThomasNieto!)
  • +
  • Remove unnecessary usage of LINQ Count method (#13545) (Thanks @xtqqczze!)
  • +
  • Fix SA1518: The code must not contain extra blank lines at the end of the file (#13574) (Thanks @xtqqczze!)
  • +
  • Enable CA1829: Use the Length or Count property instead of Count() (#13925) (Thanks @xtqqczze!)
  • +
  • Enable CA1827: Do not use Count() or LongCount() when Any() can be used (#13923) (Thanks @xtqqczze!)
  • +
  • Enable or fix nullable usage in a few files (#13793, #13805, #13808, #14018, #13804) (Thanks @mkswd and @georgettica!)
  • +
  • Enable IDE0040: Add accessibility modifiers (#13962, #13874) (Thanks @xtqqczze!)
  • +
  • Make applicable private Guid fields readonly (#14000) (Thanks @xtqqczze!)
  • +
  • Fix CA1003: Use generic event handler instances (#13937) (Thanks @xtqqczze!)
  • +
  • Simplify delegate creation (#13578) (Thanks @xtqqczze!)
  • +
  • Fix RCS1033: Remove redundant boolean literal (#13454) (Thanks @xtqqczze!)
  • +
  • Fix RCS1221: Use pattern matching instead of combination of as operator and null check (#13333) (Thanks @xtqqczze!)
  • +
  • Use is not syntax (#13338) (Thanks @xtqqczze!)
  • +
  • Replace magic number with constant in PDH (#13536) (Thanks @xtqqczze!)
  • +
  • Fix accessor order (#13538) (Thanks @xtqqczze!)
  • +
  • Enable IDE0054: Use compound assignment (#13546) (Thanks @xtqqczze!)
  • +
  • Fix RCS1098: Constant values should be on right side of comparisons (#13833) (Thanks @xtqqczze!)
  • +
  • Enable CA1068: CancellationToken parameters must come last (#13867) (Thanks @xtqqczze!)
  • +
  • Enable CA10XX rules with suggestion severity (#13870, #13928, #13924) (Thanks @xtqqczze!)
  • +
  • Enable IDE0064: Make Struct fields writable (#13945) (Thanks @xtqqczze!)
  • +
  • Run dotnet-format to improve formatting of source code (#13503) (Thanks @xtqqczze!)
  • +
  • Enable CA1825: Avoid zero-length array allocations (#13961) (Thanks @xtqqczze!)
  • +
  • Add IDE analyzer rule IDs to comments (#13960) (Thanks @xtqqczze!)
  • +
  • Enable CA1830: Prefer strongly-typed Append and Insert method overloads on StringBuilder (#13926) (Thanks @xtqqczze!)
  • +
  • Enforce code style in build (#13957) (Thanks @xtqqczze!)
  • +
  • Enable CA1836: Prefer IsEmpty over Count when available (#13877) (Thanks @xtqqczze!)
  • +
  • Enable CA1834: Consider using StringBuilder.Append(char) when applicable (#13878) (Thanks @xtqqczze!)
  • +
  • Fix IDE0044: Make field readonly (#13884, #13885, #13888, #13892, #13889, #13886, #13890, #13891, #13887, #13893, #13969, #13967, #13968, #13970, #13971, #13966, #14012) (Thanks @xtqqczze!)
  • +
  • Enable IDE0048: Add required parentheses (#13896) (Thanks @xtqqczze!)
  • +
  • Enable IDE1005: Invoke delegate with conditional access (#13911) (Thanks @xtqqczze!)
  • +
  • Enable IDE0036: Enable the check on the order of modifiers (#13958, #13881) (Thanks @xtqqczze!)
  • +
  • Use span-based String.Concat instead of String.Substring (#13500) (Thanks @xtqqczze!)
  • +
  • Enable CA1050: Declare types in namespace (#13872) (Thanks @xtqqczze!)
  • +
  • Fix minor keyword typo in C# code comment (#13811) (Thanks @paul-cheung!)
  • +
+ +
+ +### Tools + +- Enable `CodeQL` Security scanning (#13894) +- Add global `AnalyzerConfig` with default configuration (#13835) (Thanks @xtqqczze!) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@mkswd, @xtqqczze

+ +
+ +
    +
  • Bump Microsoft.NET.Test.Sdk to 16.8.0 (#14020)
  • +
  • Bump Microsoft.CodeAnalysis.CSharp to 3.8.0 (#14075)
  • +
  • Remove workarounds for .NET 5 RTM builds (#14038)
  • +
  • Migrate 3rd party signing to ESRP (#14010)
  • +
  • Fixes to release pipeline for GA release (#14034)
  • +
  • Don't do a shallow checkout (#13992)
  • +
  • Add validation and dependencies for Ubuntu 20.04 distribution to packaging script (#13993)
  • +
  • Add .NET install workaround for RTM (#13991)
  • +
  • Move to ESRP signing for Windows files (#13988)
  • +
  • Update PSReadLine version to 2.1.0 (#13975)
  • +
  • Bump .NET to version 5.0.100-rtm.20526.5 (#13920)
  • +
  • Update script to use .NET RTM feeds (#13927)
  • +
  • Add checkout step to release build templates (#13840)
  • +
  • Turn on /features:strict for all projects (#13383) (Thanks @xtqqczze!)
  • +
  • Bump NJsonSchema to 10.2.2 (#13722, #13751)
  • +
  • Add flag to make Linux script publish to production repository (#13714)
  • +
  • Bump Markdig.Signed to 0.22.0 (#13741)
  • +
  • Use new release script for Linux packages (#13705)
  • +
+ +
+ +### Documentation and Help Content + +- Fix links to LTS versions for Windows (#14070) +- Fix `crontab` formatting in example doc (#13712) (Thanks @dgoldman-msft!) + +[7.2.0-preview.1]: https://github.com/PowerShell/PowerShell/compare/v7.1.0...v7.2.0-preview.1 diff --git a/CHANGELOG/7.3.md b/CHANGELOG/7.3.md new file mode 100644 index 00000000000..25da137b1c2 --- /dev/null +++ b/CHANGELOG/7.3.md @@ -0,0 +1,1307 @@ +# 7.3 Changelog + +## [7.3.12] - 2024-04-11 + +### Build and Packaging Improvements + +
+ + + +

Bump to .NET 7.0.18

+ +
+ +
    +
  • Update SDK, dependencies and cgmanifest for 7.3.12
  • +
  • Revert changes to packaging.psm1
  • +
  • Verify environment variable for OneBranch before we try to copy (#21441)
  • +
  • Multiple fixes in official build pipeline (#21408)
  • +
  • PowerShell co-ordinated build OneBranch pipeline (#21364)
  • +
  • Add dotenv install as latest version does not work with current Ruby version (#21239)
  • +
  • Remove surrogateFile setting of APIScan (#21238)
  • +
+ +
+ +[7.3.12]: https://github.com/PowerShell/PowerShell/compare/v7.3.11...v7.3.12 + +## [7.3.11] - 2024-01-11 + +### Build and Packaging Improvements + +
+ + + +

Bump .NET to 7.0.405

+ +
+ +
    +
  • Update cgmanifest.json for v7.3.11 release (Internal 29160)
  • +
  • Update .NET SDK to 7.0.405 (Internal 29140)
  • +
  • Back port 3 build changes to apiscan.yml (#21035)
  • +
  • Set the ollForwardOnNoCandidateFx in runtimeconfig.json to roll forward only on minor and patch versions (#20689)
  • +
  • Remove the ref folder before running compliance (#20373)
  • +
  • Fix the tab completion tests (#20867)
  • +
+ +
+ +[7.3.11]: https://github.com/PowerShell/PowerShell/compare/v7.3.10...v7.3.11 + +## [7.3.10] - 2023-11-16 + +### General Cmdlet Updates and Fixes + +- Redact Auth header content from ErrorRecord (Internal 28410) + +### Build and Packaging Improvements + +
+ + + +

Update .NET to 7.0.404

+ +
+ +
    +
  • Add internal .NET SDK URL parameter to release pipeline (Internal 28505)
  • +
  • Fix release build by making the internal SDK parameter optional (#20658) (Internal 28440)
  • +
  • Make internal .NET SDK URL as a parameter for release builld (#20655) (Internal 28428)
  • +
  • Update the Notices file and cgmanifest (Internal 28500)
  • +
  • Update .NET to 7.0.404 (Internal 28485)
  • +
  • Copy azure blob with PowerShell global tool to private blob and move to CDN during release (Internal 28448)
  • +
+ +
+ +[7.3.10]: https://github.com/PowerShell/PowerShell/compare/v7.3.9...v7.3.10 + +## [7.3.9] - 2023-10-26 + +### Build and Packaging Improvements + +
+ + + +

Bump .NET 7 to version 7.0.403

+ +
+ +
    +
  • Use correct agent pool for downloading from Azure blob
  • +
  • Remove a timeout value from ADO pipeline stage to resolve a syntax issue
  • +
  • Update .NET 7 and manifests (Internal 28148)
  • +
  • Add SBOM for release pipeline (#20519) (#20573)
  • +
  • Increase timeout when publishing packages to pacakages.microsoft.com (#20470) (#20572)
  • +
  • Use fxdependent-win-desktop runtime for compliance runs (#20326) (#20571)
  • +
+ +
+ +[7.3.9]: https://github.com/PowerShell/PowerShell/compare/v7.3.8...v7.3.9 + +## [7.3.8] - 2023-10-10 + +### Security Fixes + +- Block getting help from network locations in restricted remoting sessions (Internal 27698) + +### Build and Packaging Improvements + +
+ + + +

Build infrastructure maintenance

+ +
+ +
    +
  • Release build: Change the names of the PATs (#20316)
  • +
  • Add mapping for mariner arm64 stable (#20310)
  • +
  • Switch to GitHub Action for linting markdown (#20308)
  • +
  • Put the calls to Set-AzDoProjectInfo and Set-AzDoAuthToken` in the right order (#20311)
  • +
+ +
+ +[7.3.8]: https://github.com/PowerShell/PowerShell/compare/v7.3.7...v7.3.8 + +## [7.3.7] - 2023-09-18 + +### Build and Packaging Improvements + +
+ + + +

Bump .NET SDK version to 7.0.401

+ +
+ +
    +
  • Update 'ThirdPartyNotices.txt' (Internal 27602)
  • +
  • Update to use .NET SDK 7.0.401 (Internal 27591)
  • +
  • Remove HostArchitecture dynamic parameter for osxpkg (#19917)
  • +
  • Remove spelling CI in favor of GitHub Action (#20248)
  • +
  • Enable vPack provenance data (#20253)
  • +
  • Start using new packages.microsoft.com cli (#20252)
  • +
  • Add mariner arm64 to PMC release (#20251)
  • +
  • Add mariner arm64 package build to release build (#20250)
  • +
  • Make PR creation tool use --web because it is more reliable (#20247)
  • +
  • Update variable used to bypass the blocking check for multiple NuGet feeds (#20246)
  • +
  • Publish rpm package for rhel9 (#20245)
  • +
  • Add runtime and packaging type info for mariner2 arm64 (#20244)
  • +
+ +
+ +### Documentation and Help Content + +- Update man page to match current help for pwsh (#20249) + +[7.3.7]: https://github.com/PowerShell/PowerShell/compare/v7.3.6...v7.3.7 + +## [7.3.6] - 2023-07-13 + +### Build and Packaging Improvements + +
+ + + +

Bump .NET to 7.0.306

+ +
+ +
    +
  • Update Notices file
  • +
  • Don't publish notice on failure because it prevents retry
  • +
  • Bump .NET to 7.0.306 (#19945)
  • +
  • Remove the property disabling optimization (#19952)
  • +
  • Add ProductCode in registry for MSI install (#19951)
  • +
  • Update variable used to bypass the blocking check for multiple NuGet feeds (#19953)
  • +
  • Change System.Security.AccessControl preview version to stable version (#19931)
  • +
+ +
+ +### Documentation and Help Content + +- Update the link for getting started in `README.md` (#19947) + +[7.3.6]: https://github.com/PowerShell/PowerShell/compare/v7.3.5...v7.3.6 + +## [7.3.5] - 2023-06-27 + +### Build and Packaging Improvements + +
+ + + +

Bump to use .NET 7.0.305

+ +
+ +
    +
  • Update the ThirdPartyNotice (Internal 26372)
  • +
  • Add PoolNames variable group to compliance pipeline (#19408)
  • +
  • Update cgmanifest.json
  • +
  • Update to .NET 7.0.304 (#19807)
  • +
  • Disable SBOM signing for CI and add extra files for packaging tests (#19729)
  • +
  • Increase timeout to make subsystem tests more reliable (#18380)
  • +
  • Increase the timeout when waiting for the event log (#19264)
  • +
  • Implement IDisposable in NamedPipeClient (#18341) (Thanks @xtqqczze!)
  • +
  • Always regenerate files wxs fragment (#19196)
  • +
  • Bump Microsoft.PowerShell.MarkdownRender (#19751)
  • +
  • Delete symbols on Linux as well (#19735)
  • +
  • Add prompt to fix conflict during backport (#19583)
  • +
  • Add backport function to release tools (#19568)
  • +
  • Add an explicit manual stage for changelog update (#19551)
  • +
  • Update the team member list in releaseTools.psm1 (#19544)
  • +
  • Verify that packages have license data (#19543)
  • +
  • Fix the regex used for package name check in vPack build (#19511)
  • +
  • Make the vPack PAT library more obvious (#19505)
  • +
  • Update the metadata.json to mark 7.3 releases as latest for stable channel (#19565)
  • +
+ +
+ +[7.3.5]: https://github.com/PowerShell/PowerShell/compare/v7.3.4...v7.3.5 + +## [7.3.4] - 2023-04-12 + +### Engine Updates and Fixes + +- Add instrumentation to `AmsiUtil` and make the `init` variable readonly (#18727) +- Fix support for `NanoServer` due to the lack of AMSI (#18882) +- Adding missing guard for telemetry optout to avoid `NullReferenceException` when importing modules (#18949) (Thanks @powercode!) +- Fix `VtSubstring` helper method to correctly check chars copied (#19240) +- Fix `ConciseView` to handle custom `ParserError` error records (#19239) + +### Build and Packaging Improvements + +
+ + + +

Bump to use .NET 7.0.5

+ +
+ +
    +
  • Update ThirdPartyNotices.txt
  • +
  • Update cgmanifest.json
  • +
  • Fix the template that creates nuget package
  • +
  • Update the wix file
  • +
  • Update to .NET SDK 7.0.203
  • +
  • Skip VT100 tests on Windows Server 2012R2 as console does not support it (#19413)
  • +
  • Improve package management acceptance tests by not going to the gallery (#19412)
  • +
  • Fix stage dependencies and typo in release build (#19353)
  • +
  • Fix issues in release build and release pipeline (#19338)
  • +
  • Restructure the package build to simplify signing and packaging stages (#19321)
  • +
  • Test fixes for stabilizing tests (#19068)
  • +
  • Add stage for symbols job in Release build (#18937)
  • +
  • Use reference assemblies generated by dotnet (#19302)
  • +
  • Add URL for all distributions (#19159)
  • +
+ +
+ +[7.3.4]: https://github.com/PowerShell/PowerShell/compare/v7.3.3...v7.3.4 + +## [7.3.3] - 2023-02-23 + +### Build and Packaging Improvements + +
+ + + +

Bump to use .NET 7.0.3

+ +
+ +
    +
  • Update third party notices for v7.3.3 (Internal 24353)
  • +
  • Add tool to trigger license information gathering for NuGet modules (#18827)
  • +
  • Update global.json to 7.0.200 for v7.3.3 (Internal 24334)
  • +
  • Update cgmanifest for v7.3.3 (Internal 24338)
  • +
+ +
+ +[7.3.3]: https://github.com/PowerShell/PowerShell/compare/v7.3.2...v7.3.3 + +## [7.3.2] - 2023-01-24 + +### Engine Updates and Fixes + +- Fix `SuspiciousContentChecker.Match` to detect a predefined string when the text starts with it (#18916) +- Fix for JEA session leaking functions (Internal 23820) + +### General Cmdlet Updates and Fixes + +- Fix `Start-Job` to check the existence of working directory using the PowerShell way (#18917) +- Fix `Switch-Process` error to include the command that is not found (#18650) + +### Tests + +- Allow system lock down test debug hook to work with new `WLDP` API (fixes system lock down tests) (#18962) + +### Build and Packaging Improvements + +
+ + + +

Bump to use .NET 7.0.2

+ +
+ +
    +
  • Update dependencies for .NET release (Internal 23818)
  • +
  • Remove unnecessary reference to System.Runtime.CompilerServices.Unsafe (#18918)
  • +
  • Add bootstrap after SBOM task to re-install .NET (#18891)
  • +
+ +
+ +[7.3.2]: https://github.com/PowerShell/PowerShell/compare/v7.3.1...v7.3.2 + +## [7.3.1] - 2022-12-13 + +### Engine Updates and Fixes + +- Remove TabExpansion for PSv2 from remote session configuration (Internal 23331) +- Add `sqlcmd` to list to use legacy argument passing (#18645 #18646) +- Change `exec` from alias to function to handle arbitrary args (#18644) +- Fix `Switch-Process` to copy the current env to the new process (#18632) +- Fix issue when completing the first command in a script with an empty array expression (#18355) +- Fix `Switch-Process` to set `termios` appropriate for child process (#18572) +- Fix native access violation (#18571) + +### Tests + +- Backport CI fixed from #18508 (#18626) +- Mark charset test as pending (#18609) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+ +
+ +
    +
  • Update packages (Internal 23330)
  • +
  • Apply expected file permissions to linux files after authenticode signing (#18647)
  • +
  • Bump System.Data.SqlClient (#18573)
  • +
  • Don't install based on build-id for RPM (#18570)
  • +
  • Work around args parsing issue (#18607)
  • +
  • Fix package download in vPack job
  • +
+ +
+ +[7.3.1]: https://github.com/PowerShell/PowerShell/compare/v7.3.0...v7.3.1 + +## [7.3.0] - 2022-11-08 + +### General Cmdlet Updates and Fixes + +- Correct calling cmdlet `New-PSSessionOption` in script for `Restart-Computer` (#18374) + +### Tests + +- Add test for framework dependent package in release pipeline (Internal 23139) + +### Build and Packaging Improvements + +
+ + + +

Bump to use internal .NET 7 GA build (Internal 23096)

+ +
+ +
    +
  • Fix issues with building test artifacts (Internal 23116)
  • +
  • Use AzFileCopy task instead of AzCopy.exe
  • +
  • Remove AzCopy installation from msixbundle step
  • +
  • Add TSAUpload for APIScan (#18446)
  • +
  • Add authenticode signing for assemblies on Linux builds (#18440)
  • +
  • Do not remove penimc_cor3.dll from build (#18438)
  • +
  • Allow two-digit revisions in vPack package validation pattern (#18392)
  • +
  • Bump Microsoft.PowerShell.Native from 7.3.0-rc.1 to 7.3.0 (#18413)
  • +
+ +
+ +[7.3.0]: https://github.com/PowerShell/PowerShell/compare/v7.3.0-rc.1...v7.3.0 + +## [7.3.0-rc.1] - 2022-10-26 + +### Breaking Change + +- Update to use `ComputeCore.dll` for PowerShell Direct (#18194) + +### Engine Updates and Fixes + +- On Unix, explicitly terminate the native process during cleanup only if it's not running in background (#18215) + +### General Cmdlet Updates and Fixes + +- Remove the `ProcessorArchitecture` portion from the full name as it's obsolete (#18320) + +### Tests + +- Add missing `-Tag 'CI'` to describe blocks. (#18317) + +### Build and Packaging Improvements + +
+ + +

Bump to .NET 7 to 7.0.100-rc.2.22477.20 (#18328)(#18286)

+
+ +
    +
  • Update ThirdPartyNotices (Internal 22987)
  • +
  • Remove API sets (#18304) (#18376)
  • +
  • Do not cleanup pwsh.deps.json for framework dependent packages (#18300)
  • +
  • Bump Microsoft.PowerShell.Native from 7.3.0-preview.1 to 7.3.0-rc.1 (#18217)
  • +
  • Remove unnecessary native dependencies from the package (#18213)
  • +
  • Make the link to minimal package blob public during release (#18158)
  • +
  • Create tasks to collect and publish hashes for build files. (#18276)(#18277)
  • +
  • Add branch counter to compliance build (#18214)
  • +
  • Move APIScan to compliance build (#18191)
  • +
  • Update MSI exit message (#18137)
  • +
  • Remove XML files for min-size package (#18189)
  • +
+ +
+ +[7.3.0-rc.1]: https://github.com/PowerShell/PowerShell/compare/v7.3.0-preview.8...v7.3.0-rc.1 + +## [7.3.0-preview.8] - 2022-09-20 + +### General Cmdlet Updates and Fixes + +- Filter out compiler generated types for `Add-Type -PassThru` (#18095) +- Fix error formatting to use color defined in `$PSStyle.Formatting` (#17987) +- Handle `PSObject` argument specially in method invocation logging (#18060) +- Revert the experimental feature `PSStrictModeAssignment` (#18040) +- Make experimental feature `PSAMSIMethodInvocationLogging` stable (#18041) +- Make experimental feature `PSAnsiRenderingFileInfo` stable (#18042) +- Make experimental feature `PSCleanBlock` stable (#18043) +- Make experimental feature `PSNativeCommandArgumentPassing` stable (#18044) +- Make experimental feature `PSExec` stable (#18045) +- Make experimental feature `PSRemotingSSHTransportErrorHandling` stable (#18046) +- Add the `ConfigurationFile` option to the PowerShell help content (#18093) + +### Build and Packaging Improvements + + +

Bump .NET SDK to version `7.0.100-rc.1`

+
+ +
+
    +
  • Update ThirdPartyNotices.txt for 7.3.0-preview.8 (Internal 22553)
  • +
  • Update cgmanifest.json for 7.3.0-preview.8 (Internal 22551)
  • +
  • Re-enable building with Ready-to-Run (#18107)
  • +
  • Make sure Security.types.ps1xml gets signed in release build (#17930)
  • +
  • Update DotnetRuntimeMetadata.json for .NET 7 RC1 build (#18106)
  • +
  • Add XML reference documents to NuPkg files for SDK (#18017)
  • +
  • Make Register MU timeout (#17995)
  • +
  • Bump Microsoft.NET.Test.Sdk from 17.2.0 to 17.3.0 (#17924)
  • +
  • Update list of PS team members in release tools (#17928)
  • +
  • Update to use version 2.21.0 of Application Insights (#17927)
  • +
  • Complete ongoing Write-Progress in test (#17922)
  • +
+
+ +[7.3.0-preview.8]: https://github.com/PowerShell/PowerShell/compare/v7.3.0-preview.7...v7.3.0-preview.8 + +## [7.3.0-preview.7] - 2022-08-09 + +### Breaking Changes + +- Move the type data definition of `System.Security.AccessControl.ObjectSecurity` to the `Microsoft.PowerShell.Security` module (#16355) (Thanks @iSazonov!) + +### Engine Updates and Fixes + +- Enable searching for assemblies in `GAC_Arm64` on Windows (#17816) +- Fix parser exception in using statements with empty aliases (#16745) (Thanks @MartinGC94!) +- Do not always collapse space between parameter and value for native arguments. (#17708) +- Remove `PSNativePSPathResolution` experimental feature (#17670) + +### General Cmdlet Updates and Fixes + +- Fix for deserializing imported ordered dictionary (#15545) (Thanks @davidBar-On!) +- Make generated implicit remoting modules backward compatible with PowerShell 5.1 (#17227) (Thanks @Tadas!) +- Re-enable IDE0031: Use Null propagation (#17811) (Thanks @fflaten!) +- Allow commands to still be executed even if the current working directory no longer exists (#17579) +- Stop referencing `Microsoft.PowerShell.Security` when the core snapin is used (#17771) +- Add support for HTTPS with `Set-AuthenticodeSignature -TimeStampServer` (#16134) (Thanks @Ryan-Hutchison-USAF!) +- Add type accelerator `ordered` for `OrderedDictionary` (#17804) (Thanks @fflaten!) +- Fix the definition of the `PDH_COUNTER_INFO` struct (#17779) +- Adding Virtualization Based Security feature names to Get-ComputerInfo (#16415) (Thanks @mattifestation!) +- Fix `FileSystemProvider` to work with volume and pipe paths (#15873) +- Remove pre-parse for array-based JSON (#15684) (Thanks @strawgate!) +- Improve type inference for `$_` (#17716) (Thanks @MartinGC94!) +- Prevent braces from being removed when completing variables (#17751) (Thanks @MartinGC94!) +- Fix type inference for `ICollection` (#17752) (Thanks @MartinGC94!) +- Fix `Test-Json` not handling non-object types at root (#17741) (Thanks @dkaszews!) +- Change `Get-ChildItem` to treat trailing slash in path as indicating a directory when used with `-Recurse` (#17704) +- Add `find.exe` to legacy argument binding behavior for Windows (#17715) +- Add completion for index expressions for dictionaries (#17619) (Thanks @MartinGC94!) +- Fix enum-ranges for `ValidateRange` in proxy commands (#17572) (Thanks @fflaten!) +- Fix type completion for attribute tokens (#17484) (Thanks @MartinGC94!) +- Add `-noprofileloadtime` switch to `pwsh` (#17535) (Thanks @rkeithhill!) +- Fix legacy `ErrorView` types to use `$host.PrivateData` colors (#17705) +- Improve dynamic parameter tab completion (#17661) (Thanks @MartinGC94!) +- Avoid binding positional parameters when completing parameter in front of value (#17693) (Thanks @MartinGC94!) +- Render decimal numbers in a table using current culture (#17650) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@fflaten, @Molkree, @eltociear

+ +
+ +
    +
  • Fix other path constructions using Path.Join (#17825)
  • +
  • Use null propagation (#17787)(#17789)(#17790)(#17791)(#17792)(#17795) (Thanks @fflaten!)
  • +
  • Re-enable compound assignment preference (#17784) (Thanks @Molkree!)
  • +
  • Use null-coalescing assignment (#17719)(#17720)(#17721)(#17722)(#17723)(#17724)(#17725)(#17726)(#17727)(#17728)(#17729) (Thanks @Molkree!)
  • +
  • Disable the warning IDE0031 to take .NET 7 Preview 7 (#17770)
  • +
  • Fix typo in ModuleCmdletBase.cs (#17714) (Thanks @eltociear!)
  • +
+ +
+ +### Tests + +- Re-enable tests because the corresponding dotnet issues were fixed (#17839) +- Add test for `LanguageMode` using remoting (#17803) (Thanks @fflaten!) +- Fix test perf by stopping ongoing `write-progress` (#17749) (Thanks @fflaten!) +- Re-enable the test `TestLoadNativeInMemoryAssembly` (#17738) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@varunsh-coder, @dkaszews, @Molkree, @ChuckieChen945

+ +
+ +
    +
  • Update release pipeline to use Approvals and automate some manual tasks (#17837)
  • +
  • Add GitHub token permissions for workflows (#17781) (Thanks @varunsh-coder!)
  • +
  • Bump actions/github-script from 3 to 6 (#17842)
  • +
  • Bump cirrus-actions/rebase from 1.6 to 1.7 (#17843)
  • +
  • Remove unneeded verbose message in build (#17840)
  • +
  • Detect default runtime using dotnet --info in build.psm1 (#17818) (Thanks @dkaszews!)
  • +
  • Bump actions/checkout from 2 to 3 (#17828)
  • +
  • Bump actions/download-artifact from 2 to 3 (#17829)
  • +
  • Bump github/codeql-action from 1 to 2 (#17830)
  • +
  • Bump peter-evans/create-pull-request from 3 to 4 (#17831)
  • +
  • Bump actions/upload-artifact from 2 to 3 (#17832)
  • +
  • Enable Dependabot for GitHub Actions (#17775) (Thanks @Molkree!)
  • +
  • Update .NET SDK version from 7.0.100-preview.6.22352.1 to 7.0.100-preview.7.22377.5 (#17776)
  • +
  • Fix a bug in install-powershell.ps1 (#17794) (Thanks @ChuckieChen945!)
  • +
  • Bump xunit from 2.4.1 to 2.4.2 (#17817)
  • +
  • Update how to update homebrew (#17798)
  • +
  • Don't run link check on forks (#17797)
  • +
  • Update dotnetmetadata.json to start consuming .NET 7 preview 7 builds (#17736)
  • +
  • Bump PackageManagement from 1.4.7 to 1.4.8.1 (#17709)
  • +
  • Exclude ARM images from running in CI (#17713)
  • +
+ +
+ +### Documentation and Help Content + +- Update the comment about why R2R is disabled (#17850) +- Update changelog and `.spelling` for `7.3.0-preview.6` release (#17835) +- Updated `ADOPTERS.md` for Power BI (#17766) +- Update README.md with the current Fedora version (#15717) (Thanks @ananya26-vishnoi!) +- Update `README` and `metadata.json` for next release (#17676) (Thanks @SeeminglyScience!) + +[7.3.0-preview.7]: https://github.com/PowerShell/PowerShell/compare/v7.3.0-preview.6...v7.3.0-preview.7 + +## [7.3.0-preview.6] - 2022-07-18 + +### General Cmdlet Updates and Fixes + +- Fix `Export-PSSession` to not throw error when a rooted path is specified for `-OutputModule` (#17671) +- Change `ConvertFrom-Json -AsHashtable` to use ordered hashtable (#17405) +- Remove potential ANSI escape sequences in strings before using in `Out-GridView` (#17664) +- Add the `-Milliseconds` parameter to `New-TimeSpan` (#17621) (Thanks @NoMoreFood!) +- Update `Set-AuthenticodeSignature` to use `SHA256` as the default (#17560) (Thanks @jborean93!) +- Fix tab completion regression when completing `ValidateSet` values (#17628) (Thanks @MartinGC94!) +- Show optional parameters as such when displaying method definition and overloads (#13799) (Thanks @eugenesmlv!) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@sethvs, @MartinGC94, @eltociear

+ +
+ +
    +
  • Fix comment in InternalCommands.cs (#17669) (Thanks @sethvs!)
  • +
  • Use discards for unused variables (#17620) (Thanks @MartinGC94!)
  • +
  • Fix typo in CommonCommandParameters.cs (#17524) (Thanks @eltociear!)
  • +
+ +
+ +### Tests + +- Fix SDK tests for release build (#17678) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@tamasvajk

+ +
+ +
    +
  • Create test artifacts for Windows ARM64 (#17675)
  • +
  • Update to the latest NOTICES file (#17607)
  • +
  • Update .NET SDK version from 7.0.100-preview.5.22307.18 to 7.0.100-preview.6.22352.1 (#17634)
  • +
  • Set the compound assignment preference to false (#17632)
  • +
  • Update DotnetMetadata.json to start consuming .NET 7 Preview 6 builds (#17630)
  • +
  • Install .NET 3.1 as it is required by the vPack task (#17600)
  • +
  • Update to use PSReadLine v2.2.6 (#17595)
  • +
  • Fix build.psm1 to not specify both version and quality for dotnet-install (#17589) (Thanks @tamasvajk!)
  • +
  • Bump Newtonsoft.Json in /test/perf/dotnet-tools/Reporting (#17592)
  • +
  • Bump Newtonsoft.Json in /test/perf/dotnet-tools/ResultsComparer (#17566)
  • +
  • Disable RPM SBOM test. (#17532)
  • +
+ +
+ +### Documentation and Help Content + +- Remove `katacoda.com` from doc as it now returns 404 (#17625) +- Update changelog for `v7.2.5` and `v7.3.0-preview.5` (#17565) +- Update `README.md` and `metadata.json` for upcoming releases (#17526) + +[7.3.0-preview.6]: https://github.com/PowerShell/PowerShell/compare/v7.3.0-preview.5...v7.3.0-preview.6 + +## [7.3.0-preview.5] - 2022-06-21 + +### Engine Updates and Fixes + +- Improve type inference and completions (#16963) (Thanks @MartinGC94!) +- Make `Out-String` and `Out-File` keep string input unchanged (#17455) +- Make `AnsiRegex` able to capture Hyperlink ANSI sequences (#17442) +- Add the `-ConfigurationFile` command-line parameter to `pwsh` to support local session configuration (#17447) +- Fix native library loading for `osx-arm64` (#17365) (Thanks @awakecoding!) +- Fix formatting to act appropriately when the style of table header or list label is empty string (#17463) + +### General Cmdlet Updates and Fixes + +- Fix various completion issues inside the `param` block (#17489) (Thanks @MartinGC94!) +- Add Amended switch to `Get-CimClass` cmdlet (#17477) (Thanks @iSazonov!) +- Improve completion on operators (#17486) (Thanks @MartinGC94!) +- Improve array element completion for command arguments (#17078) (Thanks @matt9ucci!) +- Use AST extent for `PSScriptRoot` path completion (#17376) +- Add type inference support for generic methods with type parameters (#16951) (Thanks @MartinGC94!) +- Write out OSC indicator only if the `stdout` is not redirected (#17419) +- Remove the assert and use a relatively larger capacity to cover possible increase of .NET reference assemblies (#17423) +- Increase reference assembly count to 161 (#17420) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@Yulv-git, @eltociear

+ +
+ +
    +
  • Fix some typos in source code (#17481) (Thanks @Yulv-git!)
  • +
  • Fix typo in `AsyncResult.cs` (#17396) (Thanks @eltociear!)
  • +
+ +
+ +### Tools + +- Update script to pin to .NET 7 preview 5 version (#17448) +- Start-PSPester: argument completer for `-Path` (#17334) (Thanks @powercode!) +- Add reminder workflows (#17387) +- Move to configuring the fabric bot via JSON (#17411) +- Update Documentation Issue Template URL (#17410) (Thanks @michaeltlombardi!) +- Update script to automatically take new preview prerelease builds (#17375) + +### Tests + +- Make Assembly Load Native test work on a FX Dependent Linux Install (#17380) +- Update `Get-Error` test to not depend on DNS APIs (#17471) + +### Build and Packaging Improvements + +
+ +
    +
  • Update .NET SDK version from 7.0.100-preview.4.22252.9 to 7.0.100-preview.5.22307.18 (#17402)
  • +
  • Downgrade the Microsoft.CodeAnalysis.NetAnalyzers package to 7.0.0-preview1.22217.1 (#17515)
  • +
  • Rename mariner package to cm (#17505)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#17476)
  • +
  • Bump NJsonSchema from 10.7.1 to 10.7.2 (#17475)
  • +
  • Publish preview versions of mariner to preview repo (#17451)
  • +
  • Update to the latest NOTICES file (#17421)
  • +
  • Do not publish package for Mariner 1.0 (#17415)
  • +
  • Add AppX capabilities in MSIX manifest so that PS7 can call the AppX APIs (#17416)
  • +
  • Update to the latest NOTICES file (#17401)
  • +
  • Fix mariner mappings (#17413)
  • +
  • Update the cgmanifest (#17393)
  • +
  • Bump `NJsonSchema` from `10.7.0` to `10.7.1` (#17381)
  • +
+ +
+ +### Documentation and Help Content + +- Update to the latest NOTICES file (#17493) (Thanks @github-actions[bot]!) +- Update the cgmanifest (#17478) (Thanks @github-actions[bot]!) +- Correct spelling in Comments and tests (#17480) (Thanks @Yulv-git!) +- Fix spelling errors introduced in changelog (#17414) +- Update changelog for v7.3.0-preview.4 release (#17412) +- Update readme and metadata for 7.3.0-preview.4 release (#17378) + +[7.3.0-preview.5]: https://github.com/PowerShell/PowerShell/compare/v7.3.0-preview.4...v7.3.0-preview.5 + +## [7.3.0-preview.4] - 2022-05-23 + +### Engine Updates and Fixes + +
    +
  • Remove the use of BinaryFormatter in PSRP serialization (#17133) (Thanks @jborean93!)
  • +
  • Update telemetry collection removing unused data and adding some new data (#17304)
  • +
  • Fix the word wrapping in formatting to handle escape sequences properly (#17316)
  • +
  • Fix the error message in Hashtable-to-object conversion (#17329)
  • +
  • Add support for new WDAC API (#17247)
  • +
  • On Windows, reset cursor visibility back to previous state when rendering progress (#16782)
  • +
  • Fix the list view to not leak VT decorations (#17262)
  • +
  • Fix formatting truncation to handle strings with VT sequences (#17251)
  • +
  • Fix line breakpoints for return statements without a value (#17179)
  • +
  • Fix for partial PowerShell module search paths, that can be resolved to CWD locations (#17231) (Internal 20126)
  • +
  • Change logic in the testing helper module for determining whether PSHOME is writable (#17218)
  • +
  • Make a variable assignment in a ParenExpression to return the variable value (#17174)
  • +
  • Use new Windows signature APIs from Microsoft.Security.Extensions package (#17159)
  • +
  • Do not include node names when sending telemetry. (#16981)
  • +
  • Support forward slashes in network share (UNC path) completion (#17111) (#17117) (Thanks @sba923!)
  • +
  • Do not generate clean block in proxy function when the feature is disabled (#17112)
  • +
  • Ignore failure attempting to set console window title (#16948)
  • +
  • Update regex used to remove ANSI escape sequences to be more specific to decoration and CSI sequences (#16811)
  • +
  • Improve member auto completion (#16504) (Thanks @MartinGC94!)
  • +
  • Prioritize ValidateSet completions over Enums for parameters (#15257) (Thanks @MartinGC94!)
  • +
  • Add Custom Remote Connections Feature (#17011)
  • +
+ +### General Cmdlet Updates and Fixes + +
    +
  • Add check for ScriptBlock wrapped in PSObject to $using used in ForEach-Object -Parallel (#17234) (Thanks @ryneandal!)
  • +
  • Fix ForEach method to set property on a scalar object (#17213)
  • +
  • Fix Sort-Object -Stable -Unique to actually do stable sorting (#17189) (Thanks @m1k0net!)
  • +
  • Add OutputType attribute to various commands (#16962) (Thanks @MartinGC94!)
  • +
  • Make Stop-Service only request needed privileges when not setting SDDL. (#16663) (Thanks @kvprasoon!)
  • +
+ +### Code Cleanup + +
    +
  • Remove EventLogLogProvider and its related legacy code (#17027)
  • +
  • Fix typos in names of method (#17003) (Thanks @al-cheb!)
  • +
  • SemanticChecks: Avoid repeated type resolution of [ordered] (#17328) (Thanks IISResetMe!)
  • +
  • Redo the change that was reverted by #15853 (#17357)
  • +
  • Correct spelling of pseudo in Compiler.cs (#17285) (Thanks @eltociear!)
  • +
  • MakeNameObscurerTelemetryInitializer internal (#17214)
  • +
  • Make NameObscurerTelemetryInitializer internal (#17167)
  • +
  • Correct Typo in the resource string PathResolvedToMultiple (#17098) (Thanks @charltonstanley!)
  • +
  • Fix typo in ComRuntimeHelpers.cs (#17104) (Thanks @eltociear!)
  • +
+ +### Documentation and Help Content + +
    +
  • Update link to PowerShell remoting in depth video (#17166)
  • +
+ +### Tests + +
    +
  • Add -because to the failing test to aid in debugging (#17030)
  • +
  • Simplify Enum generator for the -bnot operator test (#17014)
  • +
  • Improve unique naming for tests (#17043)
  • +
  • Use a random string for the missing help topic to improve the chances that the help topic really won't be found. (#17042)
  • +
+ +### Build and Packaging Improvements + +
    +
  • Update README.md and metadata.json for v7.3.0-preview.3 release (#17029)
  • +
  • Do not pull dotnet updates from internal feed (#17007)
  • +
  • Simplify Get-WSManSupport based on current .NET Distro Support (#17356)
  • +
  • Update to the latest NOTICES file (#17372, #17332, #17311, #17275)
  • +
  • Run on every PR and let the action skip (#17366)
  • +
  • Make sure verbose message is not null (#17363)
  • +
  • Release changelogs (#17364)
  • +
  • Update build versions (#17318)
  • +
  • Add Daily Link Check GitHub Workflow (#17351)
  • +
  • Update the cgmanifest (#17361, #17344, #17324, #17302, #17268)
  • +
  • Bump NJsonSchema from 10.6.10 to 10.7.0 (#17350)
  • +
  • Disable broken macOS CI job, which is unused (#17221)
  • +
  • Have rebase workflow Post a message when it starts (#17341)
  • +
  • Update DotnetRuntimeMetadata.json for .NET 7 Preview 4 (#17336)
  • +
  • Update Ubuntu 22 to be detected as not supported WSMan (#17338)
  • +
  • Bump xunit.runner.visualstudio from 2.4.3 to 2.4.5 (#17274)
  • +
  • Make sure we execute tests on LTS package for older LTS releases (#17326)
  • +
  • Bump Microsoft.NET.Test.Sdk from 17.1.0 to 17.2.0 (#17320)
  • +
  • Add fedora to the OS's that can't run WSMan (#17325)
  • +
  • Add sles15 support to install-powershell.sh (#16984)
  • +
  • Start rotating through all images (#17315)
  • +
  • Update .NET SDK version from 7.0.100-preview.2.22153.17 to 7.0.100-preview.4.22252.9 (#17061)
  • +
  • Disable release security analysis for SSH CI (#17303)
  • +
  • Add a finalize template which causes jobs with issues to fail (#17314)
  • +
  • Add mapping for ubuntu22.04 jammy (#17317)
  • +
  • Enable more tests to be run in a container. (#17294)
  • +
  • Fix build.psm1 to find the required .NET SDK version when a higher version is installed (#17299)
  • +
  • Improve how Linux container CI builds are identified (#17295)
  • +
  • Only inject NuGet security analysis if we are using secure nuget.config (#17293)
  • +
  • Reduce unneeded verbose message from build.psm1 (#17291)
  • +
  • Switch to using GitHub action to verify Markdown links for PRs (#17281)
  • +
  • Put Secure supply chain analysis at correct place (#17273)
  • +
  • Fix build id variable name when selecting CI container (#17279)
  • +
  • Add rotation between the two mariner images (#17277)
  • +
  • Update to use mcr.microsoft.com (#17272)
  • +
  • Update engine working group members (#17271)
  • +
  • Bump PSReadLine from 2.2.2 to 2.2.5 in /src/Modules (#17252)
  • +
  • Update timeout for daily (#17263)
  • +
  • Bump NJsonSchema from 10.6.9 to 10.6.10 (#16902)
  • +
  • Update the cgmanifest (#17260)
  • +
  • Fix Generate checksum file for packages build failure - v7.1.7 (#17219) (Internal 20274)
  • +
  • Move cgmanifest generation to daily (#17258)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#17245)
  • +
  • Update to the latest notice file (#17238)
  • +
  • Add container to Linux CI (#17233)
  • +
  • Mark Microsoft.Management.Infrastructure.Runtime.Win as a developer dependency to hide in notice file (#17230)
  • +
  • Fixing dotnet SDK version parsing in build.psm1 (#17198) (Thanks @powercode!)
  • +
  • Fixed package names verification to support multi-digit versions (#17220)
  • +
  • Bump Microsoft.CodeAnalysis.CSharp from 4.2.0-1.final to 4.2.0-4.final (#17210)
  • +
  • Add backport action (#17212)
  • +
  • Updated changelogs for v7.0.9 / v7.0.10 / v7.1.6 / v7.1.7 / v7.2.2 / v7.2.3 (#17207)
  • +
  • Updated metadata.json and README.md for v7.2.3 and v7.0.10 (#17158)
  • +
  • Update package fallback list for ubuntu (from those updated for ubuntu 22.04) (deb) (#17180)
  • +
  • Update wix to include security extensions package (#17171)
  • +
  • Update rebase.yml (#17170)
  • +
  • Adds sha256 digests to RPM packages (#16896) (Thanks @ngharo!)
  • +
  • Make mariner packages Framework dependent (#17151)
  • +
  • Update to the latest notice file (#17169)
  • +
  • Update to the latest notice file (#17146)
  • +
  • Replace . in notices container name (#17154)
  • +
  • Allow multiple installations of dotnet. (#17141)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#17105)
  • +
  • Update to the latest notice file (#16437)
  • +
  • Skip failing scriptblock tests (#17093)
  • +
  • Update dotnet-install script download link (#17086)
  • +
  • Fix the version of the Microsoft.CodeAnalysis.NetAnalyzers package (#17075)
  • +
  • Update dotnetmetadata.json to accept .NET 7 preview 3 builds (#17063)
  • +
  • Re-enable PowerShellGet tests targeting PowerShell gallery (#17062)
  • +
  • Add mariner 1.0 amd64 package (#17057)
  • +
  • Create checksum file for global tools (#17056)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#17065)
  • +
  • Use new cask format (#17064)
  • +
+ +[7.3.0-preview.4]: https://github.com/PowerShell/PowerShell/compare/v7.3.0-preview.3...v7.3.0-preview.4 + +## [7.3.0-preview.3] - 2022-03-21 + +### Engine Updates and Fixes + +- Fix the parsing code for .NET method generic arguments (#16937) +- Allow the `PSGetMemberBinder` to get value of `ByRef` property (#16956) +- Allow a collection that contains `Automation.Null` elements to be piped to pipeline (#16957) + +### General Cmdlet Updates and Fixes + +- Add the module `CompatPowerShellGet` to the allow-list of telemetry modules (#16935) +- Fix `Enter-PSHostProcess` and `Get-PSHostProcessInfo` cmdlets by handling processes that have exited (#16946) +- Improve Hashtable completion in multiple scenarios (#16498) (Thanks @MartinGC94!) + +### Code Cleanup + +- Fix a typo in `CommandHelpProvider.cs` (#16949) (Thanks @eltociear!) + +### Tests + +- Update a few tests to make them more stable in CI (#16944) +- Roll back Windows images used in testing to Windows Server 2019 (#16958) + +### Build and Packaging Improvements + +
+ + +

Update .NET SDK to 7.0.0-preview.2

+
+ +
    +
  • Update .NET to 7.0.0-preview.2 build (#16930)
  • +
  • Update AzureFileCopy task and fix the syntax for specifying pool (#17013)
  • +
+ +
+ +[7.3.0-preview.3]: https://github.com/PowerShell/PowerShell/compare/v7.3.0-preview.2...v7.3.0-preview.3 + +## [7.3.0-preview.2] - 2022-02-24 + +### Engine Updates and Fixes + +- Fix the `clean` block for generated proxy function (#16827) +- Add support to allow invoking method with generic type arguments (#12412 and #16822) (Thanks @vexx32!) +- Report error when PowerShell built-in modules are missing (#16628) + +### General Cmdlet Updates and Fixes + +- Prevent command completion if the word to complete is a single dash (#16781) (Thanks @ayousuf23!) +- Use `FindFirstFileW` instead of `FindFirstFileExW` to correctly handle Unicode filenames on FAT32 (#16840) (Thanks @iSazonov!) +- Add completion for loop labels after Break/Continue (#16438) (Thanks @MartinGC94!) +- Support OpenSSH options for `PSRP` over SSH commands (#12802) (Thanks @BrannenGH!) +- Adds a `.ResolvedTarget` Property to `File-System` Items to Reflect a Symlink's Target as `FileSystemInfo` (#16490) (Thanks @hammy3502!) +- Use `NotifyEndApplication` to re-enable VT mode (#16612) +- Add new parameter to `Start-Sleep`: `[-Duration] ` (#16185) (Thanks @IISResetMe!) +- Add lock and null check to remoting internals (#16542) (#16683) (Thanks @SergeyZalyadeev!) +- Make `Measure-Object` ignore missing properties unless running in strict mode (#16589) (Thanks @KiwiThePoodle!) +- Add `-StrictMode` to `Invoke-Command` to allow specifying strict mode when invoking command locally (#16545) (Thanks @Thomas-Yu!) +- Fix `$PSNativeCommandArgPassing` = `Windows` to handle empty args correctly (#16639) +- Reduce the amount of startup banner text (#16516) (Thanks @rkeithhill!) +- Add `exec` cmdlet for bash compatibility (#16462) +- Add AMSI method invocation logging as experimental feature (#16496) +- Fix web cmdlets so that an empty `Get` does not include a `content-length` header (#16587) +- Update `HelpInfoUri` for 7.3 release (#16646) +- Fix parsing `SemanticVersion` build label from version string (#16608) +- Fix `ForEach-Object -Parallel` when passing in script block variable (#16564) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@eltociear, @iSazonov, @xtqqczze

+ +
+ +
    +
  • Fix typo in PowerShellExecutionHelper.cs (#16776) (Thanks @eltociear!)
  • +
  • Use more efficient platform detection API (#16760) (Thanks @iSazonov!)
  • +
  • Seal ClientRemotePowerShell (#15802) (Thanks @xtqqczze!)
  • +
  • Fix the DSC overview URL in a Markdown file and some small cleanup changes (#16629)
  • +
+ +
+ +### Tools + +- Fix automation to update experimental JSON files in GitHub action (#16837) + +### Tests + +- Update `markdownlint` to the latest version (#16825) +- Bump the package `path-parse` from `1.0.6` to `1.0.7` (#16820) +- Remove assert that is incorrect and affecting our tests (#16588) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@dahlia

+ +
+ +
    +
  • Update NuGet Testing to not re-install dotnet, +when not needed and dynamically determine the DOTNET_ROOT (Internal 19268, 19269, 19272, 19273, and 19274)
  • +
  • Remove SkipExperimentalFeatureGeneration when building alpine (Internal 19248)
  • +
  • Revert .NET 7 changes, Update to the latest .NET 6 and Update WXS file due to blocking issue in .NET 7 Preview 1
  • +
  • Install and Find AzCopy
  • +
  • Use Start-PSBootStrap for installing .NET during nuget packaging
  • +
  • Fix pool syntax for deployments (Internal 19189)
  • +
  • Bump NJsonSchema from 10.5.2 to 10.6.9 (#16888)
  • +
  • Update projects and scripts to use .NET 7 preview 1 prerelease builds (#16856)
  • +
  • Add warning messages when package precheck fails (#16867)
  • +
  • Refactor Global Tool packaging to include SBOM generation (#16860)
  • +
  • Update to use windows-latest as the build agent image (#16831)
  • +
  • Ensure alpine and arm SKUs have powershell.config.json file with experimental features enabled (#16823)
  • +
  • Update experimental feature json files (#16838) (Thanks @github-actions[bot]!)
  • +
  • Remove WiX install (#16834)
  • +
  • Add experimental json update automation (#16833)
  • +
  • Update .NET SDK to 6.0.101 and fix Microsoft.PowerShell.GlobalTool.Shim.csproj (#16821)
  • +
  • Add SBOM manifest to nuget packages (#16711)
  • +
  • Improve logic for updating .NET in CI (#16808)
  • +
  • Add Linux package dependencies for packaging (#16807)
  • +
  • Switch to our custom images for build and release (#16801)
  • +
  • Remove all references to cmake for the builds in this repo (#16578)
  • +
  • Fix build for new InvokeCommand attributes (#16800)
  • +
  • Let macOS installer run without Rosetta on Apple Silicon (#16742) (Thanks @dahlia!)
  • +
  • Update the expect .NET SDK quality to GA for installing dotnet (#16784)
  • +
  • Change nuget release yaml to use UseDotNet task (#16701)
  • +
  • Bump Microsoft.ApplicationInsights from 2.19.0 to 2.20.0 (#16642)
  • +
  • Register NuGet source when generating CGManifest (#16570)
  • +
  • Update Images used for release (#16580)
  • +
  • Update SBOM generation (#16641)
  • +
  • Bring changes from 7.3.0-preview.1 (#16640)
  • +
  • Update the vmImage and PowerShell root directory for macOS builds (#16611)
  • +
  • Update macOS build image and root folder for build (#16609)
  • +
  • Disabled Yarn cache in markdown.yml (#16599)
  • +
  • Update cgmanifest (#16600)
  • +
  • Fix broken links in Markdown (#16598)
  • +
+ +
+ +### Documentation and Help Content + +- Add newly joined members to their respective Working Groups (#16849) +- Update Engine Working Group members (#16780) +- Replace the broken link about pull request (#16771) +- Update changelog to remove a broken URL (#16735) +- Updated `README.md` and `metadata.json` for `v7.3.0-preview.1` release (#16627) +- Updating changelog for `7.2.1` (#16616) +- Updated `README.md` and `metadata.json` for `7.2.1` release (#16586) + +[7.3.0-preview.2]: https://github.com/PowerShell/PowerShell/compare/v7.3.0-preview.1...v7.3.0-preview.2 + +## [7.3.0-preview.1] - 2021-12-16 + +### Breaking Changes + +- Add `clean` block to script block as a peer to `begin`, `process`, and `end` to allow easy resource cleanup (#15177) +- Change default for `$PSStyle.OutputRendering` to `Ansi` (Internal 18449) + +### Engine Updates and Fixes + +- Remove duplicate remote server mediator code (#16027) +- Fix `PSVersion` parameter version checks and error messages for PowerShell 7 remoting (#16228) +- Use the same temporary home directory when `HOME` env variable is not set (#16263) +- Fix parser to generate error when array has more than 32 dimensions (#16276) + +### Performance + +- Avoid validation for built-in file extension and color VT sequences (#16320) (Thanks @iSazonov!) + +### General Cmdlet Updates and Fixes + +- Update `README.md` and `metadata.json` for next preview release (#16107) +- Use `PlainText` when writing to a host that doesn't support VT (#16092) +- Remove support for `AppExeCLinks` to retrieve target (#16044) +- Move `GetOuputString()` and `GetFormatStyleString()` to `PSHostUserInterface` as public API (#16075) +- Fix `ConvertTo-SecureString` with key regression due to .NET breaking change (#16068) +- Fix regression in `Move-Item` to only fallback to `copy and delete` in specific cases (#16029) +- Set `$?` correctly for command expression with redirections (#16046) +- Use `CurrentCulture` when handling conversions to `DateTime` in `Add-History` (#16005) (Thanks @vexx32!) +- Fix link header parsing to handle unquoted `rel` types (#15973) (Thanks @StevenLiekens!) +- Fix a casting error when using `$PSNativeCommandUsesErrorActionPreference` (#15993) +- Format-Wide: Fix `NullReferenceException` (#15990) (Thanks @DarylGraves!) +- Make the native command error handling optionally honor `ErrorActionPreference` (#15897) +- Remove declaration of experimental features in Utility module manifest as they are stable (#16460) +- Fix race condition between `DisconnectAsync` and `Dispose` (#16536) (Thanks @i3arnon!) +- Fix the `Max_PATH` condition check to handle long path correctly (#16487) (Thanks @Shriram0908!) +- Update `HelpInfoUri` for 7.2 release (#16456) +- Fix tab completion within the script block specified for the `ValidateScriptAttribute`. (#14550) (Thanks @MartinGC94!) +- Update `README.md` to specify gathered telemetry (#16379) +- Fix typo for "privacy" in MSI installer (#16407) +- Remove unneeded call to `File.ResolveLinkTarget` from `IsWindowsApplication` (#16371) (Thanks @iSazonov!) +- Add `-HttpVersion` parameter to web cmdlets (#15853) (Thanks @hayhay27!) +- Add support to web cmdlets for open-ended input tags (#16193) (Thanks @farmerau!) +- Add more tests to `Tee-Object -Encoding` (#14539) (Thanks @rpolley!) +- Don't throw exception when trying to resolve a possible link path (#16310) +- Fix `ConvertTo-Json -Depth` to allow 100 at maximum (#16197) (Thanks @KevRitchie!) +- Fix for SSH remoting when banner is enabled on SSHD endpoint (#16205) +- Disallow all COM for AppLocker system lock down (#16268) +- Configure `ApplicationInsights` to not send cloud role name (#16246) +- Disallow `Add-Type` in NoLanguage mode on a locked down machine (#16245) +- Specify the executable path as `TargetObect` for non-zero exit code `ErrorRecord` (#16108) (Thanks @rkeithhill!) +- Don't allow `Move-Item` with FileSystemProvider to move a directory into itself (#16198) +- Make property names for the color VT sequences consistent with documentations (#16212) +- Fix `PipelineVariable` to set variable in the right scope (#16199) +- Invoke-Command: improve handling of variables with $using: expression (#16113) (Thanks @dwtaber!) +- Change `Target` from a `CodeProperty` to be an `AliasProperty` that points to `FileSystemInfo.LinkTarget` (#16165) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@xtqqczze, @eltociear, @iSazonov

+ +
+ +
    +
  • Improve CommandInvocationIntrinsics API documentation and style (#14369)
  • +
  • Use bool?.GetValueOrDefault() in FormatWideCommand (#15988) (Thanks @xtqqczze!)
  • +
  • Remove 4 assertions which cause debug build test runs to fail (#15963)
  • +
  • Fix typo in `Job.cs` (#16454) (Thanks @eltociear!)
  • +
  • Remove unnecessary call to `ToArray` (#16307) (Thanks @iSazonov!)
  • +
  • Remove the unused `FollowSymLink` function (#16231)
  • +
  • Fix typo in `TypeTable.cs` (#16220) (Thanks @eltociear!)
  • +
  • Fixes #16176 - replace snippet tag with code tag in comments (#16177)
  • +
+ +
+ +### Tools + +- Fix typo in build.psm1 (#16038) (Thanks @eltociear!) +- Add `.stylecop` to `filetypexml` and format it (#16025) +- Enable sending Teams notification when workflow fails (#15982) +- Use `Convert-Path` for unknown drive in `Build.psm1` (#16416) (Thanks @matt9ucci!) + +### Tests + +- Add benchmark to test compiler performance (#16083) +- Enable two previously disabled `Get-Process` tests (#15845) (Thanks @iSazonov!) +- Set clean state before testing `UseMU` in the MSI (#16543) +- Fix global tool and SDK tests in release pipeline (#16342) +- Remove the outdated test (#16269) +- Removed old not-used-anymore docker-based tests for PS release packages (#16224) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@github-actions[bot], @kondratyev-nv

+ +
+ +
    +
  • fix issue with hash file getting created before we have finished get-childitem (#16170)
  • +
  • Add sha256 hashes to release (#16147)
  • +
  • Change path for Component Governance for build to the path we actually use to build (#16137)
  • +
  • Update Microsoft.CodeAnalysis.CSharp version (#16138)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#16070)
  • +
  • Update .NET to 6.0.100-rc.1.21458.32 (#16066)
  • +
  • Update minimum required OS version for macOS (#16088)
  • +
  • Set locale correctly on Linux CI (#16073)
  • +
  • Ensure locale is set correctly on Ubuntu 20.04 in CI (#16067)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#16045)
  • +
  • Update .NET SDK version from `6.0.100-rc.1.21430.44` to `6.0.100-rc.1.21455.2` (#16041) (Thanks @github-actions[bot]!)
  • +
  • Fix the GitHub Action for updating .NET daily builds (#16042)
  • +
  • Bump Microsoft.CodeAnalysis.CSharp from 4.0.0-3.final to 4.0.0-4.21430.4 (#16036)
  • +
  • Bump .NET to `6.0.100-rc.1.21430.44` (#16028)
  • +
  • Move from PkgES hosted agents to 1ES hosted agents (#16023)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#16021)
  • +
  • Update Ubuntu images to use Ubuntu 20.04 (#15906)
  • +
  • Fix the mac build by updating the pool image name (#16010)
  • +
  • Use Alpine 3.12 for building PowerShell for alpine (#16008)
  • +
  • Update .NET SDK version from `6.0.100-preview.6.21355.2` to `6.0.100-rc.1.21426.1` (#15648) (Thanks @github-actions[bot]!)
  • +
  • Ignore error from Find-Package (#15999)
  • +
  • Find packages separately for each source in UpdateDotnetRuntime.ps1 script (#15998)
  • +
  • Update metadata to start using .NET 6 RC1 builds (#15981)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#15985)
  • +
  • Merge the v7.2.0-preview.9 release branch back to GitHub master (#15983)
  • +
  • Publish global tool package for stable releases (#15961)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers to newer version (#15962)
  • +
  • Disabled Yarn cache in markdown.yml (#16599)
  • +
  • Update cgmanifest (#16600)
  • +
  • Fix broken links in Markdown (#16598)
  • +
  • Add explicit job name for approval tasks in Snap stage (#16579)
  • +
  • Bring back pwsh.exe for framework dependent packages to support Start-Job (#16535)
  • +
  • Fix NuGet package generation in release build (#16509)
  • +
  • Add `Microsoft.PowerShell.Commands.SetStrictModeCommand.ArgumentToPSVersionTransformationAttribute` to list of patterns to remove for generated ref assembly (#16489)
  • +
  • Bump Microsoft.CodeAnalysis.CSharp from `4.0.0-6.final` to `4.0.1` (#16423)
  • +
  • use different containers for different branches (#16434)
  • +
  • Add import so we can use common GitHub workflow function. (#16433)
  • +
  • Remove prerelease .NET 6 build sources (#16418)
  • +
  • Update release instructions with link to new build (#16419)
  • +
  • Bump Microsoft.ApplicationInsights from 2.18.0 to 2.19.0 (#16413)
  • +
  • Update metadata.json to make 7.2.0 the latest LTS (#16417)
  • +
  • Make static CI a matrix (#16397)
  • +
  • Update metadata.json in preparation on 7.3.0-preview.1 release (#16406)
  • +
  • Update cgmanifest (#16405)
  • +
  • Add diagnostics used to take corrective action when releasing `buildInfoJson` (#16404)
  • +
  • `vPack` release should use `buildInfoJson` new to 7.2 (#16402)
  • +
  • Update the usage of metadata.json for getting LTS information (#16381)
  • +
  • Add checkout to build json stage to get `ci.psm1` (#16399)
  • +
  • Update CgManifest.json for 6.0.0 .NET packages (#16398)
  • +
  • Add current folder to the beginning of the module import (#16353)
  • +
  • Increment RC MSI build number by 100 (#16354)
  • +
  • Bump XunitXml.TestLogger from 3.0.66 to 3.0.70 (#16356)
  • +
  • Move PR Quantifier config to subfolder (#16352)
  • +
  • Release build info json when it is preview (#16335)
  • +
  • Add an approval for releasing build-info json (#16351)
  • +
  • Generate manifest with latest public version of the packages (#16337)
  • +
  • Update to the latest notices file (#16339) (Thanks @github-actions[bot]!)
  • +
  • Use notice task to generate license assuming cgmanifest contains all components (#16340)
  • +
  • Refactor cgmanifest generator to include all components (#16326)
  • +
  • Fix issues in release build (#16332)
  • +
  • Update feed and analyzer dependency (#16327)
  • +
  • Bump Microsoft.NET.Test.Sdk from 16.11.0 to 17.0.0 (#16312)
  • +
  • Update license and cgmanifest (#16325) (Thanks @github-actions[bot]!)
  • +
  • Fix condition in cgmanifest logic (#16324)
  • +
  • Add GitHub Workflow to keep notices up to date (#16284)
  • +
  • Update to latest .NET 6 GA build 6.0.100-rtm.21527.11 (#16309)
  • +
  • Create compliance build (#16286)
  • +
  • Move mapping file into product repo and add Debian 11 (#16316)
  • +
  • Add a major-minor build info JSON file (#16301)
  • +
  • Clean up crossgen related build scripts also generate native symbols for R2R images (#16297)
  • +
  • Fix Windows build ZIP packaging (#16299) (Thanks @kondratyev-nv!)
  • +
  • Revert "Update to use .NET 6 GA build (#16296)" (#16308)
  • +
  • Add wget as a dependency for Bootstrap script (#16303) (Thanks @kondratyev-nv!)
  • +
  • Fix issues reported by code signing verification tool (#16291)
  • +
  • Update to use .NET 6 GA build (#16296)
  • +
  • Revert "add GH workflow to keep the cgmanifest up to date." (#16294)
  • +
  • Update ChangeLog for 7.2.0-rc.1 and also fix RPM packaging (#16290)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#16271)
  • +
  • add GH workflow to keep the cgmanifest up to date.
  • +
  • Update ThirdPartyNotices.txt (#16283)
  • +
  • Update `testartifacts.yml` to use ubuntu-latest image (#16279)
  • +
  • Update version of Microsoft.PowerShell.Native and Microsoft.PowerShell.MarkdownRender packages (#16277)
  • +
  • Add script to generate cgmanifest.json (#16278)
  • +
  • Add cgmanifest.json for generating correct third party notice file (#16266)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers from `6.0.0-rtm.21504.2` to `6.0.0-rtm.21516.1` (#16264)
  • +
  • Only upload stable buildinfo for stable releases (#16251)
  • +
  • Make RPM license recognized (#16189)
  • +
  • Don't upload dep or tar.gz for RPM because there are none. (#16230)
  • +
  • Add condition to generate release files in local dev build only (#16259)
  • +
  • Update .NET 6 to version 6.0.100-rc.2.21505.57 (#16249)
  • +
  • change order of try-catch-finally and split out arm runs (#16252)
  • +
  • Ensure psoptions.json and manifest.spdx.json files always exist in packages (#16258)
  • +
  • Update to vPack task version to 12 (#16250)
  • +
  • Remove unneeded `NuGetConfigFile` resource string (#16232)
  • +
  • Add Software Bill of Materials to the main packages (#16202)
  • +
  • Sign third party exes (#16229)
  • +
  • Upgrade set-value package for Markdown test (#16196)
  • +
  • Use Ubuntu 20.04 for SSH remoting test (#16225)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#16194)
  • +
  • Bump `Microsoft.CodeAnalysis.NetAnalyzers` from `6.0.0-rc2.21458.5` to `6.0.0-rtm.21480.8` (#16183)
  • +
  • Move vPack build to 1ES Pool (#16169)
  • +
  • Fix Microsoft update spelling issue. (#16178)
  • +
+ +
+ +### Documentation and Help Content + +- Update Windows PowerShell issues link (#16105) (Thanks @andschwa!) +- Remove Joey from Committee and WG membership (#16119) +- Update more docs for `net6.0` TFM (#16102) (Thanks @xtqqczze!) +- Change `snippet` tag to `code` tag in XML comments (#16106) +- Update build documentation to reflect .NET 6 (#15751) (Thanks @Kellen-Stuart!) +- Update `README.md` about the changelogs (#16471) (Thanks @powershellpr0mpt!) +- Update changelog for 7.2.0 (#16401) +- Update `metadata.json` and `README.md` for 7.2.0 release (#16395) +- Update `README.md` and `metadata.json` files for `v7.2.0-rc.1` release (#16285) +- Update the changelogs for `v7.0.8` and `v7.1.5` releases (#16248) + +[7.3.0-preview.1]: https://github.com/PowerShell/PowerShell/compare/v7.2.0-preview.10...v7.3.0-preview.1 diff --git a/CHANGELOG/7.4.md b/CHANGELOG/7.4.md new file mode 100644 index 00000000000..a04b7c82e6e --- /dev/null +++ b/CHANGELOG/7.4.md @@ -0,0 +1,1473 @@ +# 7.4 Changelog + +## [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-lastest 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 + +
+ + + +

Bump .NET SDK to 8.0.403

+ +
+ +
    +
  • Copy to static site instead of making blob public (#24269) (#24473)
  • +
  • Add ability to capture MSBuild Binary logs when restore fails (#24128)
  • +
  • Keep the roff file when gzipping it. (#24450)
  • +
  • Update PowerShell-Coordinated_Packages-Official.yml (#24449)
  • +
  • Update and add new NuGet package sources for different environments. (#24440)
  • +
  • Add PMC mapping for Debian 12 (bookworm) (#24413)
  • +
  • Fixes to Azure Public feed usage (#24429)
  • +
  • Delete assets/AppImageThirdPartyNotices.txt (#24256)
  • +
  • Delete demos directory (#24258)
  • +
  • Add specific path for issues in tsaconfig (#24244)
  • +
  • Checkin generated manpage (#24423)
  • +
  • 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)
  • +
  • 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)
  • +
  • Use Managed Identity for APIScan authentication (#24243)
  • +
  • Check Create and Submit in vPack build by default (#24181)
  • +
  • Capture environment better (#24148)
  • +
  • Refactor Nuget package source creation to use New-NugetPackageSource function (#24104)
  • +
  • Make Microsoft feeds the default (#24426)
  • +
  • Bump to .NET 8.0.403 and update dependencies (#24405)
  • +
+ +
+ +[7.4.6]: https://github.com/PowerShell/PowerShell/compare/v7.4.5...v7.4.6 + +## [7.4.5] - 2024-08-20 + +### General Cmdlet Updates and Fixes + +- Fix WebCmdlets when `-Body` is specified but `ContentType` is not (#24145) + +### Tests + +- Rewrite the mac syslog tests to make them less flaky (#24152) + +### Build and Packaging Improvements + +
+ + + +

Bump .NET SDK to 8.0.400

+ +
+ +
    +
  • Add feature flags for removing network isolation (Internal 32126)
  • +
  • Update ThirdPartyNotices.txt for v7.4.5 (#24160)
  • +
  • Update cgmanifest.json for v7.4.5 (#24159)
  • +
  • Update .NET SDK to 8.0.400 (#24151)
  • +
  • Cleanup unused csproj (#24146)
  • +
  • Remember installation options and used them to initialize options for the next installation (#24143)
  • +
  • Fix failures in GitHub action markdown-link-check (#24142)
  • +
  • Use correct signing certificates for RPM and DEBs (#21522)
  • +
+ +
+ +### Documentation and Help Content + +- Update docs sample nuget.config (#24147) +- Fix up broken links in Markdown files (#24144) + +[7.4.5]: https://github.com/PowerShell/PowerShell/compare/v7.4.4...v7.4.5 + +## [7.4.4] - 2024-07-18 + +### Engine Updates and Fixes + +- Resolve paths correctly when importing files or files referenced in the module manifest (Internal 31780) + +### Build and Packaging Improvements + +
+ + + +

Bump .NET to 8.0.303

+ +
+ +
    +
  • Enumerate over all signed zip packages in macos signing
  • +
  • Update TPN for the v7.4.4 release (Internal 31793)
  • +
  • Add update cgmanifest (Internal 31789)
  • +
  • Add macos signing for package files (#24015) (#24059)
  • +
  • Update .NET SDK to 8.0.303 (#24038)
  • +
+ +
+ +[7.4.4]: https://github.com/PowerShell/PowerShell/compare/v7.4.3...v7.4.4 + +## [7.4.3] - 2024-06-18 + +### General Cmdlet Updates and Fixes + +- 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) + +### Engine Updates and Fixes + +- Fix generating `OutputType` when running in Constrained Language Mode (#21605) +- Expand `~` to `$home` on Windows with tab completion (#21529) +- Make sure both stdout and stderr can be redirected from a native executable (#20997) + +### Build and Packaging Improvements + +
+ + + +

Update to .NET 8.0.6

+

We thank the following contributors!

+

@ForNeVeR!

+ +
+ +
    +
  • Fixes for change to new Engineering System.
  • +
  • Fix argument passing in GlobalToolShim (#21333) (Thanks @ForNeVeR!)
  • +
  • Create powershell.config.json for PowerShell.Windows.x64 global tool (#23941)
  • +
  • Remove markdown link check on release branches (#23937)
  • +
  • Update to .NET 8.0.6 (#23936)
  • +
  • Fix error in the vPack release, debug script that blocked release (#23904)
  • +
  • Add branch counter variables for daily package builds (#21523)
  • +
  • Updates to package and release pipelines (#23800)
  • +
  • Fix exe signing with third party signing for WiX engine (#23878)
  • +
  • Use PSScriptRoot to find path to Wix module (#21611)
  • +
  • [StepSecurity] Apply security best practices (#21480)
  • +
  • Fix build failure due to missing reference in GlobalToolShim.cs (#21388)
  • +
  • Update installation on Wix module (#23808)
  • +
  • Use feed with Microsoft Wix toolset (#21651)
  • +
  • Create the Windows.x64 global tool with shim for signing (#21559)
  • +
  • Generate MSI for win-arm64 installer (#20516)
  • +
  • update wix package install (#21537)
  • +
  • Add a PAT for fetching PMC cli (#21503)
  • +
  • Official PowerShell Package pipeline (#21504)
  • +
+ +
+ +[7.4.3]: https://github.com/PowerShell/PowerShell/compare/v7.4.2...v7.4.3 + +## [7.4.2] - 2024-04-11 + +### General Cmdlet Updates and Fixes + +- Revert "Adjust PUT method behavior to POST one for default content type in WebCmdlets" (#21049) +- Fix regression with `Get-Content` when `-Tail 0` and `-Wait` are both used (#20734) (Thanks @CarloToso!) +- Fix `Get-Error` serialization of array values (#21085) (Thanks @jborean93!) +- Fix a regression in `Format-Table` when header label is empty (#21156) + +### Engine Updates and Fixes + +- 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) +- Handle the case that `Runspace.DefaultRunspace` is `null` when logging for WDAC Audit (#21344) +- Fix PowerShell class to support deriving from an abstract class with abstract properties (#21331) +- Fix the regression when doing type inference for `$_` (#21223) (Thanks @MartinGC94!) + +### Build and Packaging Improvements + +
+ + + +

Bump to .NET 8.0.4

+ +
+ +
    +
  • Revert analyzer package back to stable
  • +
  • Update SDK, deps and cgmanifest for 7.4.2
  • +
  • Revert changes to packaging.psm1
  • +
  • Update PSResourceGet version from 1.0.2 to 1.0.4.1 (#21439)
  • +
  • Verify environment variable for OneBranch before we try to copy (#21441)
  • +
  • Remove surrogateFile setting of APIScan (#21238)
  • +
  • Add dotenv install as latest version does not work with current Ruby version (#21239)
  • +
  • Multiple fixes in official build pipeline (#21408)
  • +
  • Add back 2 transitive dependency packages (#21415)
  • +
  • Update PSReadLine to v2.3.5 for the next v7.4.x servicing release (#21414)
  • +
  • PowerShell co-ordinated build OneBranch pipeline (#21364)
  • +
+ +
+ +[7.4.2]: https://github.com/PowerShell/PowerShell/compare/v7.4.1...v7.4.2 + +## [7.4.1] - 2024-01-11 + +### General Cmdlet Updates and Fixes + +- Fix `Group-Object` output using interpolated strings (#20745) (Thanks @mawosoft!) +- Fix `Start-Process -PassThru` to make sure the `ExitCode` property is accessible for the returned `Process` object (#20749) (#20866) (Thanks @CodeCyclone!) +- Fix rendering of DisplayRoot for network PSDrive (#20793) (#20863) + +### Engine Updates and Fixes + +- Ensure filename is not null when logging WDAC ETW events (#20910) (Thanks @jborean93!) +- Fix four regressions introduced by WDAC audit logging feature (#20913) + +### Build and Packaging Improvements + +
+ + + +Bump .NET 8 to version 8.0.101 + + + +
    +
  • Update .NET SDK and dependencies for v7.4.1 (Internal 29142)
  • +
  • Update cgmanifest for v7.4.1 (#20874)
  • +
  • Update package dependencies for v7.4.1 (#20871)
  • +
  • Set the rollForwardOnNoCandidateFx in runtimeconfig.json to roll forward only on minor and patch versions (#20689) (#20865)
  • +
  • Remove RHEL7 publishing to packages.microsoft.com as it's no longer supported (#20849) (#20864)
  • +
  • Fix the tab completion tests (#20867)
  • +
+ +
+ +[7.4.1]: https://github.com/PowerShell/PowerShell/compare/v7.4.0...v7.4.1 + +## [7.4.0] - 2023-11-16 + +### General Cmdlet Updates and Fixes + +- Added a missing `ConfigureAwait(false)` call to webcmdlets so they don't block (#20622) +- Fix `Group-Object` so output uses current culture (#20623) +- Block getting help from network locations in restricted remoting sessions (#20615) + +### Build and Packaging Improvements + +
+ + + +

Bump .NET 8 to 8.0.0 RTM build

+ +
+ +
    +
  • Add internal .NET SDK URL parameter to release pipeline (Internal 28474)
  • +
  • Update the CGManifest file for v7.4.0 release (Internal 28457)
  • +
  • Fix repository root for the nuget.config (Internal 28456)
  • +
  • Add internal nuget feed to compliance build (Internal 28449)
  • +
  • Copy azure blob with PowerShell global tool to private blob and move to CDN during release (Internal 28438)
  • +
  • Fix release build by making the internal SDK parameter optional (#20658) (Internal 28440)
  • +
  • Make internal .NET SDK URL as a parameter for release builld (#20655) (Internal 28428)
  • +
  • Update PSResourceGet version for 1.0.1 release (#20652) (Internal 28427)
  • +
  • Bump .NET 8 to 8.0.0 RTM build (Internal 28360)
  • +
  • Remove Auth header content from ErrorRecord (Internal 28409)
  • +
  • Fix setting of variable to consume internal SDK source (Internal 28354)
  • +
  • Bump Microsoft.Management.Infrastructure to v3.0.0 (Internal 28352)
  • +
  • Bump Microsoft.PowerShell.Native to v7.4.0 (#20617) (#20624)
  • +
+ +
+ +[7.4.0]: https://github.com/PowerShell/PowerShell/compare/v7.4.0-rc.1...v7.4.0 + +## [7.4.0-rc.1] - 2023-10-24 + +### General Cmdlet Updates and Fixes + +- Fix `Test-Connection` due to .NET 8 changes (#20369) (#20531) +- Add telemetry to check for specific tags when importing a module (#20371) (#20540) +- Fix `Copy-Item` progress to only show completed when all files are copied (#20517) (#20544) +- Fix `unixmode` to handle `setuid` and `sticky` when file is not an executable (#20366) (#20537) +- Fix UNC path completion regression (#20419) (#20541) +- Fix implicit remoting proxy cmdlets to act on common parameters (#20367) (#20530) +- Fix `Get-Service` non-terminating error message to include category (#20276) (#20529) +- Fixing regression in DSC (#20268) (#20528) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+ +
+ +
    +
  • Update ThirdPartyNotices.txt file (Internal 28110)
  • +
  • Update CGManifest for release
  • +
  • Fix package version for .NET nuget packages (#20551) (#20552)
  • +
  • Only registry App Path for release package (#20478) (#20549)
  • +
  • Bump PSReadLine from 2.2.6 to 2.3.4 (#20305) (#20533)
  • +
  • Bump Microsoft.Management.Infrastructure (#20511) (#20512) (#20433) (#20434) (#20534) (#20535) (#20545) (#20547)
  • +
  • Bump to .NET 8 RC2 (#20510) (#20543)
  • + +
  • Add SBOM for release pipeline (#20519) (#20548)
  • +
  • Bump version of Microsoft.PowerShell.PSResourceGet to v1.0.0 (#20485) (#20538)
  • +
  • Bump xunit.runner.visualstudio from 2.5.1 to 2.5.3 (#20486) (#20542)
  • +
  • Bump JsonSchema.Net from 5.2.5 to 5.2.6 (#20421) (#20532)
  • +
  • Fix alpine tar package name and do not crossgen alpine fxdependent package (#20459) (#20536)
  • +
  • Increase timeout when publishing packages to packages.microsoft.com (#20470) (#20539)
  • +
  • Block any preview vPack release (#20243) (#20526)
  • +
  • Add surrogate file for compliance scanning (#20423)
  • +
+ +
+ +[7.4.0-rc.1]: https://github.com/PowerShell/PowerShell/compare/v7.4.0-preview.6...v7.4.0-rc.1 + +## [7.4.0-preview.6] - 2023-09-28 + +### General Cmdlet Updates and Fixes + +- Set approved experimental features to stable for 7.4 release (#20362) +- Revert changes to continue using `BinaryFormatter` for `Out-GridView` (#20360) +- Remove the comment trigger from feedback provider (#20346) + +### Tests + +- Continued improvement to tests for release automation (#20259) +- Skip the test on x86 as `InstallDate` is not visible on `Wow64` (#20255) +- Harden some problematic release tests (#20254) + +### Build and Packaging Improvements + +
+ + + +

Move to .NET 8.0.100-rc.1.23463.5

+ +
+ +
    +
  • Update the regex for package name validation (Internal 27783, 27795)
  • +
  • Update ThirdPartyNotices.txt (Internal 27772)
  • +
  • Remove the ref folder before running compliance (#20375)
  • +
  • Updates RIDs used to generate component Inventory (#20372)
  • +
  • Bump Microsoft.CodeAnalysis.CSharp from 4.7.0 to 4.8.0-2.final (#20368)
  • +
  • Fix the release build by moving to the official .NET 8-rc.1 release build version (#20365)
  • +
  • Update the experimental feature JSON files (#20363)
  • +
  • Bump XunitXml.TestLogger from 3.1.11 to 3.1.17 (#20364)
  • +
  • Update Microsoft.PowerShell.PSResourceGet to 0.9.0-rc1 (#20361)
  • +
  • Update .NET SDK to version 8.0.100-rc.1.23455.8 (#20358)
  • +
  • Use fxdependent-win-desktop runtime for compliance runs (#20359)
  • +
  • Add mapping for mariner arm64 stable (#20348)
  • +
  • Bump xunit.runner.visualstudio from 2.5.0 to 2.5.1 (#20357)
  • +
  • Bump JsonSchema.Net from 5.2.1 to 5.2.5 (#20356)
  • +
  • Bump Microsoft.NET.Test.Sdk from 17.7.1 to 17.7.2 (#20355)
  • +
  • Bump Markdig.Signed from 0.32.0 to 0.33.0 (#20354)
  • +
  • Bump JsonSchema.Net from 5.1.3 to 5.2.1 (#20353)
  • +
  • Bump actions/checkout from 3 to 4 (#20352)
  • +
  • Bump Microsoft.NET.Test.Sdk from 17.7.0 to 17.7.1 (#20351)
  • +
  • Bump Microsoft.CodeAnalysis.CSharp from 4.7.0-2.final to 4.7.0 (#20350)
  • +
  • Release build: Change the names of the PATs (#20349)
  • +
  • Put the calls to Set-AzDoProjectInfo and Set-AzDoAuthToken` in the right order (#20347)
  • +
  • Bump Microsoft.Management.Infrastructure (continued) (#20262)
  • +
  • Bump Microsoft.Management.Infrastructure to 3.0.0-preview.2 (#20261)
  • +
  • Enable vPack provenance data (#20260)
  • +
  • Start using new packages.microsoft.com cli (#20258)
  • +
  • Add mariner arm64 to PMC release (#20257)
  • +
  • Fix typo donet to dotnet in build scripts and pipelines (#20256)
  • +
+ +
+ +[7.4.0-preview.6]: https://github.com/PowerShell/PowerShell/compare/v7.4.0-preview.5...v7.4.0-preview.6 + +## [7.4.0-preview.5] - 2023-08-21 + +### Breaking Changes + +- Change how relative paths in `Resolve-Path` are handled when using the `RelativeBasePath` parameter (#19755) (Thanks @MartinGC94!) + +### Engine Updates and Fixes + +- Fix dynamic parameter completion (#19510) (Thanks @MartinGC94!) +- Use `OrdinalIgnoreCase` to lookup script breakpoints (#20046) (Thanks @fflaten!) +- Guard against `null` or blank path components when adding to module path (#19922) (Thanks @stevenebutler!) +- Fix deadlock when piping to shell associated file extension (#19940) +- Fix completion regression for filesystem paths with custom `PSDrive` names (#19921) (Thanks @MartinGC94!) +- Add completion for variables assigned by the `Data` statement (#19831) (Thanks @MartinGC94!) +- Fix a null reference crash in completion code (#19916) (Thanks @MartinGC94!) + +### General Cmdlet Updates and Fixes + +- Fix `Out-GridView` by implementing `Clone()` method to replace old use of binary format serialization (#20050) +- Support Unix domain socket in WebCmdlets (#19343) (Thanks @CarloToso!) +- Wait-Process: add `-Any` and `-PassThru` parameters (#19423) (Thanks @dwtaber!) +- Added the switch parameter `-CaseInsensitive` to `Select-Object` and `Get-Unique` cmdlets (#19683) (Thanks @ArmaanMcleod!) +- `Restore-Computer` and `Stop-Computer` should fail with error when not running via `sudo` on Unix (#19824) +- Add Help proxy function for non-Windows platforms (#19972) +- Remove input text from the error message resulted by `SecureString` and `PSCredential` conversion failure (#19977) (Thanks @ArmaanMcleod!) +- Add `Microsoft.PowerShell.PSResourceGet` to the telemetry module list (#19926) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@eltociear, @Molkree, @MartinGC94

+ +
+ +
    +
  • Fix use of ThrowIf where the arguments were reversed (#20052)
  • +
  • Fix typo in Logging.Tests.ps1 (#20048) (Thanks @eltociear!)
  • +
  • Apply the InlineAsTypeCheck in the engine code - 2nd pass (#19694) (Thanks @Molkree!)
  • +
  • Apply the InlineAsTypeCheck rule in the engine code - 1st pass (#19692) (Thanks @Molkree!)
  • +
  • Remove unused string completion code (#19879) (Thanks @MartinGC94!)
  • +
+ +
+ +### Tools + +- Give the `assignPRs` workflow write permissions (#20021) + +### Tests + +- Additional test hardening for tests which fail in release pass. (#20093) +- Don't use a completion which has a space in it (#20064) +- Fixes for release tests (#20028) +- Remove spelling CI in favor of GitHub Action (#19973) +- Hide expected error for negative test on windows for script extension (#19929) +- Add more debugging to try to determine why these test fail in release build. (#19829) + +### Build and Packaging Improvements + +
    +
  • Update ThirdPartyNotices for 7.4.0-preview.5
  • +
  • Update PSResourceGet to 0.5.24-beta24 (#20118)
  • +
  • Fix build after the change to remove win-arm32 (#20102)
  • +
  • Add comment about pinned packages (#20096)
  • +
  • Bump to .NET 8 Preview 7 (#20092)
  • +
  • Remove Win-Arm32 from release build. (#20095)
  • +
  • Add alpine framework dependent package (#19995)
  • +
  • Bump JsonSchema.Net from 4.1.8 to 5.1.3 (#20089)
  • +
  • Bump Microsoft.NET.Test.Sdk from 17.6.3 to 17.7.0 (#20088)
  • +
  • Move build to .NET 8 preview 6 (#19991)
  • +
  • Bump Microsoft.Management.Infrastructure from 2.0.0 to 3.0.0-preview.1 (#20081)
  • +
  • Bump Markdig.Signed from 0.31.0 to 0.32.0 (#20076)
  • +
  • Auto assign PR Maintainer (#20020)
  • +
  • Delete rule that was supposed to round-robin assign a maintainer (#20019)
  • +
  • Update the cgmanifest (#20012)
  • +
  • Update the cgmanifest (#20008)
  • +
  • Bump JsonSchema.Net from 4.1.7 to 4.1.8 (#20006)
  • +
  • Bump JsonSchema.Net from 4.1.6 to 4.1.7 (#20000)
  • +
  • Add mariner arm64 package build to release build (#19946)
  • +
  • Check for pre-release packages when it's a stable release (#19939)
  • +
  • Make PR creation tool use --web because it is more reliable (#19944)
  • +
  • Update to the latest NOTICES file (#19971)
  • +
  • Update variable used to bypass the blocking check for multiple NuGet feeds for release pipeline (#19963)
  • +
  • Update variable used to bypass the blocking check for multiple NuGet feeds (#19967)
  • +
  • Update README.md and metadata.json for release v7.2.13 and v7.3.6 (#19964)
  • +
  • Don't publish notice on failure because it prevent retry (#19955)
  • +
  • Change variable used to bypass nuget security scanning (#19954)
  • +
  • Update the cgmanifest (#19924)
  • +
  • Publish rpm package for rhel9 (#19750)
  • +
  • Bump XunitXml.TestLogger from 3.0.78 to 3.1.11 (#19900)
  • +
  • Bump JsonSchema.Net from 4.1.5 to 4.1.6 (#19885)
  • +
  • Bump xunit from 2.4.2 to 2.5.0 (#19902)
  • +
  • Remove HostArchitecture dynamic parameter for osxpkg (#19917)
  • +
  • FabricBot: Onboarding to GitOps.ResourceManagement because of FabricBot decommissioning (#19905)
  • +
  • Change variable used to bypass nuget security scanning (#19907)
  • +
  • Checkout history for markdown lint check (#19908)
  • +
  • Switch to GitHub Action for linting markdown (#19899)
  • +
  • Bump xunit.runner.visualstudio from 2.4.5 to 2.5.0 (#19901)
  • +
  • Add runtime and packaging type info for mariner2 arm64 (#19450)
  • +
  • Update to the latest NOTICES file (#19856)
  • +
+ + + +### Documentation and Help Content + +- Update `README.md` and `metadata.json` for `7.4.0-preview.4` release (#19872) +- Fix grammatical issue in `ADOPTERS.md` (#20037) (Thanks @nikohoffren!) +- Replace docs.microsoft.com URLs in code with FWLinks (#19996) +- Change `docs.microsoft.com` to `learn.microsoft.com` (#19994) +- Update man page to match current help for pwsh (#19993) +- Merge `7.3.5`, `7.3.6`, `7.2.12` and `7.2.13` changelogs (#19968) +- Fix ///-comments that violate the docs schema (#19957) +- Update the link for getting started in `README.md` (#19932) +- Migrate user docs to the PowerShell-Docs repository (#19871) + +[7.4.0-preview.5]: https://github.com/PowerShell/PowerShell/compare/v7.4.0-preview.4...v7.4.0-preview.5 + +## [7.4.0-preview.4] - 2023-06-29 + +### Breaking Changes + +- `Test-Json`: Use `JsonSchema.Net` (`System.Text.Json`) instead of `NJsonSchema` (`Newtonsoft.Json`) (#18141) (Thanks @gregsdennis!) +- `Test-Connection`: Increase output detail when performing a TCP test (#11452) (Thanks @jackdcasey!) + +### Engine Updates and Fixes + +- Fix native executables not redirecting to file (#19842) +- Add a new experimental feature to control native argument passing style on Windows (#18706) +- Fix `TabExpansion2` variable leak when completing variables (#18763) (Thanks @MartinGC94!) +- Enable completion of variables across ScriptBlock scopes (#19819) (Thanks @MartinGC94!) +- Fix completion of the `foreach` statement variable (#19814) (Thanks @MartinGC94!) +- Fix variable type inference precedence (#18691) (Thanks @MartinGC94!) +- Fix member completion for PowerShell Enum class (#19740) (Thanks @MartinGC94!) +- Fix parsing for array literals in index expressions in method calls (#19224) (Thanks @MartinGC94!) +- Fix incorrect string to type conversion (#19560) (Thanks @MartinGC94!) +- Fix slow execution when many breakpoints are used (#14953) (Thanks @nohwnd!) +- Add a public API for getting locations of `PSModulePath` elements (#19422) +- Add WDAC Audit logging (#19641) +- Improve path completion (#19489) (Thanks @MartinGC94!) +- Fix an indexing out of bound error in `CompleteInput` for empty script input (#19501) (Thanks @MartinGC94!) +- Improve variable completion performance (#19595) (Thanks @MartinGC94!) +- Allow partial culture matching in `Update-Help` (#18037) (Thanks @dkaszews!) +- Fix the check when reading input in `NativeCommandProcessor` (#19614) +- Add support of respecting `$PSStyle.OutputRendering` on the remote host (#19601) +- Support byte stream piping between native commands and file redirection (#17857) + +### General Cmdlet Updates and Fixes + +- Disallow negative values for `Get-Content` cmdlet parameters `-Head` and `-Tail` (#19715) (Thanks @CarloToso!) +- Make `Update-Help` throw proper error when current culture is not associated with a language (#19765) (Thanks @josea!) +- Do not require activity when creating a completed progress record (#18474) (Thanks @MartinGC94!) +- WebCmdlets: Add alias for `-TimeoutSec` to `-ConnectionTimeoutSeconds` and add `-OperationTimeoutSeconds` (#19558) (Thanks @stevenebutler!) +- Avoid checking screen scraping on non-Windows platforms before launching native app (#19812) +- Add reference to PSResourceGet (#19597) +- Add `FileNameStar` to `MultipartFileContent` in WebCmdlets (#19467) (Thanks @CarloToso!) +- Add `ParameterSetName` for the `-Detailed` parameter of `Test-Connection` (#19727) +- Remove the property disabling optimization (#19701) +- Filter completion for enum parameter against `ValidateRange` attributes (#17750) (Thanks @fflaten!) +- Small cleanup `Invoke-RestMethod` (#19490) (Thanks @CarloToso!) +- Fix wildcard globbing in root of device paths (#19442) (Thanks @MartinGC94!) +- Add specific error message that creating Junctions requires absolute path (#19409) +- Fix array type parsing in generic types (#19205) (Thanks @MartinGC94!) +- Improve the verbose message of WebCmdlets to show correct HTTP version (#19616) (Thanks @CarloToso!) +- Fix HTTP status from 409 to 429 for WebCmdlets to get retry interval from Retry-After header. (#19622) (Thanks @mkht!) +- Remove minor versions from `PSCompatibleVersions` (#18635) (Thanks @xtqqczze!) +- Update `JsonSchema.Net` version to 4.1.0 (#19610) (Thanks @gregsdennis!) +- Allow combining of `-Skip` and `-SkipLast` parameters in `Select-Object` cmdlet. (#18849) (Thanks @ArmaanMcleod!) +- Fix constructing `PSModulePath` if a sub-path has trailing separator (#13147) +- Add `Get-SecureRandom` cmdlet (#19587) +- Fix `New-Item` to re-create `Junction` when `-Force` is specified (#18311) (Thanks @GigaScratch!) +- Improve Hashtable key completion for type constrained variable assignments, nested Hashtables and more (#17660) (Thanks @MartinGC94!) +- `Set-Clipboard -AsOSC52` for remote usage (#18222) (Thanks @dkaszews!) +- Refactor `MUIFileSearcher.AddFiles` in the help related code (#18825) (Thanks @xtqqczze!) +- Set `SetLastError` to `true` for symbolic and hard link native APIs (#19566) +- Fix `Get-AuthenticodeSignature -Content` to not roundtrip the bytes to a Unicode string and then back to bytes (#18774) (Thanks @jborean93!) +- WebCmdlets: Rename `-TimeoutSec` to `-ConnectionTimeoutSeconds` (with alias) and add `-OperationTimeoutSeconds` (#19558) (Thanks @stevenebutler!) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@eltociear, @ArmaanMcleod, @turbedi, @CarloToso, @Molkree, @xtqqczze

+ +
+ +
    +
  • Fix typo in NativeCommandProcessor.cs (#19846) (Thanks @eltociear!)
  • +
  • Rename file from PingPathCommand.cs to TestPathCommand.cs (#19782) (Thanks @ArmaanMcleod!)
  • +
  • Make use of the new Random.Shared property (#18417) (Thanks @turbedi!)
  • +
  • six files (#19695) (Thanks @CarloToso!)
  • +
  • Apply IDE0019: InlineAsTypeCheck in Microsoft.PowerShell.Commands (#19688)(#19690)(#19687)(#19689) (Thanks @Molkree!)
  • +
  • Remove PSv2CompletionCompleter as part of the PowerShell v2 code cleanup (#18337) (Thanks @xtqqczze!)
  • +
  • Enable more nullable annotations in WebCmdlets (#19359) (Thanks @CarloToso!)
  • +
+ +
+ +### Tools + +- Add Git mailmap for Andy Jordan (#19469) +- Add backport function to release tools (#19568) + +### Tests + +- Improve reliability of the `Ctrl+c` tests for WebCmdlets (#19532) (Thanks @stevenebutler!) +- Fix logic for `Import-CliXml` test (#19805) +- Add some debugging to the transcript test for `SilentlyContinue` (#19770) +- Re-enable `Get-ComputerInfo` pending tests (#19746) +- Update syslog parser to handle modern formats. (#19737) +- Pass `-UserScope` as required by `RunUpdateHelpTests` (#13400) (Thanks @yecril71pl!) +- Change how `isPreview` is determined for default cmdlets tests (#19650) +- Skip file signature tests on 2012R2 where PKI cmdlet do not work (#19643) +- Change logic for testing missing or extra cmdlets. (#19635) +- Fix incorrect test cases in `ExecutionPolicy.Tests.ps1` (#19485) (Thanks @xtqqczze!) +- Fixing structure typo in test setup (#17458) (Thanks @powercode!) +- Fix test failures on Windows for time zone and remoting (#19466) +- Harden 'All approved Cmdlets present' test (#19530) + +### Build and Packaging Improvements + +
+ + +

Updated to .NET 8 Preview 4 +

We thank the following contributors!

+

@krishnayalavarthi

+ +
+ +
    +
  • Update to the latest NOTICES file (#19537)(#19820)(#19784)(#19720)(#19644)(#19620)(#19605)(#19546)
  • +
  • Bump Microsoft.NET.Test.Sdk from 17.5.0 to 17.6.3 (#19867)(#19762)(#19733)(#19668)(#19613)
  • +
  • Update the cgmanifest (#19847)(#19800)(#19792)(#19776)(#19763)(#19697)(#19631)
  • +
  • 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)
  • +
  • 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)
  • +
  • Bump Microsoft.PowerShell.MarkdownRender from 7.2.0 to 7.2.1 (#19751)(#19752)
  • +
  • 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))
  • +
  • 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)
  • +
  • Add prompt to fix conflict during backport (#19583)
  • +
  • Add comment in wix detailing use of UseMU (#19371)
  • +
  • Verify that packages have license data (#19543)
  • +
  • Add an explicit manual stage for changelog update (#19551)
  • +
  • Update the team member list in releaseTools.psm1 (#19544)
  • +
+ +
+ +### Documentation and Help Content + +- Update `metadata.json` and `README.md` for upcoming releases (#19863)(#19542) +- Update message to use the actual parameter name (#19851) +- Update `CONTRIBUTING.md` to include Code of Conduct enforcement (#19810) +- Update `working-group-definitions.md` (#19809)(#19561) +- Update `working-group.md` to add section about reporting working group members (#19758) +- Correct capitalization in readme (#19666) (Thanks @Aishat452!) +- Updated the public dashboard link (#19634) +- Fix a typo in `serialization.cs` (#19598) (Thanks @eltociear!) + +[7.4.0-preview.4]: https://github.com/PowerShell/PowerShell/compare/v7.4.0-preview.3...v7.4.0-preview.4 + +## [7.4.0-preview.3] - 2023-04-20 + +### Breaking Changes + +- Remove code related to `#requires -pssnapin` (#19320) + +### Engine Updates and Fixes + +- Change the arrow used in feedback suggestion to a more common Unicode character (#19534) +- Support trigger registration in feedback provider (#19525) +- Update the `ICommandPredictor` interface to reduce boilerplate code from predictor implementation (#19414) +- Fix a crash in the type inference code (#19400) (Thanks @MartinGC94!) + +### Performance + +- Speed up `Resolve-Path` relative path resolution (#19171) (Thanks @MartinGC94!) + +### General Cmdlet Updates and Fixes + +- Infer external application output as strings (#19193) (Thanks @MartinGC94!) +- Fix a race condition in `Add-Type` (#19471) +- Detect insecure `https-to-http` redirect only if both URIs are absolute (#19468) (Thanks @CarloToso!) +- Support `Ctrl+c` when connection hangs while reading data in WebCmdlets (#19330) (Thanks @stevenebutler!) +- Enable type conversion of `AutomationNull` to `$null` for assignment (#19415) +- Add the parameter `-Environment` to `Start-Process` (#19374) +- Add the parameter `-RelativeBasePath` to `Resolve-Path` (#19358) (Thanks @MartinGC94!) +- Exclude redundant parameter aliases from completion results (#19382) (Thanks @MartinGC94!) +- Allow using a folder path in WebCmdlets' `-OutFile` parameter (#19007) (Thanks @CarloToso!) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@eltociear, @CarloToso

+ +
+ +
    +
  • Fix typo in typeDataXmlLoader.cs (#19319) (Thanks @eltociear!)
  • +
  • Fix typo in Compiler.cs (#19491) (Thanks @eltociear!)
  • +
  • Inline the GetResponseObject method (#19380) (Thanks @CarloToso!)
  • +
  • Simplify ContentHelper methods (#19367) (Thanks @CarloToso!)
  • +
  • Initialize regex lazily in BasicHtmlWebResponseObject (#19361) (Thanks @CarloToso!)
  • +
  • Fix codefactor issue in if-statement (part 5) (#19286) (Thanks @CarloToso!)
  • +
  • Add nullable annotations in WebRequestSession.cs (#19291) (Thanks @CarloToso!)
  • +
+ +
+ +### Tests + +- Harden the default command test (#19416) +- Skip VT100 tests on Windows Server 2012R2 as console does not support it (#19413) +- Improve package management acceptance tests by not going to the gallery (#19412) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@dkattan

+ +
+ +
    +
  • Fixing MSI checkbox (#19325)
  • +
  • Update the experimental feature JSON files (#19297)
  • +
  • Update the cgmanifest (#19459, #19465)
  • +
  • Update .NET SDK version to 8.0.100-preview.3.23178.7 (#19381)
  • +
  • Force updating the transitive dependency on Microsoft.CSharp (#19514)
  • +
  • Update DotnetRuntimeMetadata.json to consume the .NET 8.0.0-preview.3 release (#19529)
  • +
  • Move PSGallery sync to a pool (#19523)
  • +
  • Fix the regex used for package name check in vPack build (#19511)
  • +
  • Make the vPack PAT library more obvious (#19505)
  • +
  • Change Microsoft.CodeAnalysis.CSharp back to 4.5.0 (#19464) (Thanks @dkattan!)
  • +
  • Update to the latest NOTICES file (#19332)
  • +
  • Add PoolNames variable group to compliance pipeline (#19408)
  • +
  • Fix stage dependencies and typo in release build (#19353)
  • +
  • Fix issues in release build and release pipeline (#19338)
  • +
+ +
+ +[7.4.0-preview.3]: https://github.com/PowerShell/PowerShell/compare/v7.4.0-preview.2...v7.4.0-preview.3 + +## [7.4.0-preview.2] - 2023-03-14 + +### Breaking Changes + +- Update some PowerShell APIs to throw `ArgumentException` instead of `ArgumentNullException` when the argument is an empty string (#19215) (Thanks @xtqqczze!) +- Add the parameter `-ProgressAction` to the common parameters (#18887) + +### Engine Updates and Fixes + +- Fix `PlainText` output to correctly remove the `Reset` VT sequence without number (#19283) +- Fix `ConciseView` to handle custom `ParserError` error records (#19239) +- Fix `VtSubstring` helper method to correctly check characters copied (#19240) +- Update the `FeedbackProvider` interface to return structured data (#19133) +- Make the exception error in PowerShell able to associate with the right history entry (#19095) +- Fix for JEA session leaking functions (#19024) +- Add WDAC events and system lockdown notification (#18893) +- Fix support for nanoserver due to lack of AMSI (#18882) + +### Performance + +- Use interpolated strings (#19002)(#19003)(#18977)(#18980)(#18996)(#18979)(#18997)(#18978)(#18983)(#18992)(#18993)(#18985)(#18988) (Thanks @CarloToso!) + +### General Cmdlet Updates and Fixes + +- Fix completion for `PSCustomObject` variable properties (#18682) (Thanks @MartinGC94!) +- Improve type inference for `Get-Random` (#18972) (Thanks @MartinGC94!) +- Make `-Encoding` parameter able to take `ANSI` encoding in PowerShell (#19298) (Thanks @CarloToso!) +- Telemetry improvements for tracking experimental feature opt out (#18762) +- Support HTTP persistent connections in Web Cmdlets (#19249) (Thanks @stevenebutler!) +- Fix using XML `-Body` in webcmdlets without an encoding (#19281) (Thanks @CarloToso!) +- Add the `Statement` property to `$MyInvocation` (#19027) (Thanks @IISResetMe!) +- Fix `Start-Process` `-Wait` with `-Credential` (#19096) (Thanks @jborean93!) +- Adjust `PUT` method behavior to `POST` one for default content type in WebCmdlets (#19152) (Thanks @CarloToso!) +- Improve verbose message in web cmdlets when content length is unknown (#19252) (Thanks @CarloToso!) +- Preserve `WebSession.MaximumRedirection` from changes (#19190) (Thanks @CarloToso!) +- Take into account `ContentType` from Headers in WebCmdlets (#19227) (Thanks @CarloToso!) +- Use C# 11 UTF-8 string literals (#19243) (Thanks @turbedi!) +- Add property assignment completion for enums (#19178) (Thanks @MartinGC94!) +- Fix class member completion for classes with base types (#19179) (Thanks @MartinGC94!) +- Add `-Path` and `-LiteralPath` parameters to `Test-Json` cmdlet (#19042) (Thanks @ArmaanMcleod!) +- Allow to preserve the original HTTP method by adding `-PreserveHttpMethodOnRedirect` to Web cmdlets (#18894) (Thanks @CarloToso!) +- Webcmdlets display an error on HTTPS to http redirect (#18595) (Thanks @CarloToso!) +- 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!) +- 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) +- Add `ValidateNotNullOrEmpty` to `OutFile` and `InFile` parameters of WebCmdlets (#19044) (Thanks @CarloToso!) +- Correct spelling of "custom" in event (#19059) (Thanks @spaette!) +- Ignore expected error for file systems not supporting alternate streams (#19065) +- Adding missing guard for telemetry opt out to avoid `NullReferenceException` when importing modules (#18949) (Thanks @powercode!) +- Fix progress calculation divide by zero in Copy-Item (#19038) +- Add progress to `Copy-Item` (#18735) +- WebCmdlets parse XML declaration to get encoding value, if present. (#18748) (Thanks @CarloToso!) +- `HttpKnownHeaderNames` update headers list (#18947) (Thanks @CarloToso!) +- Fix bug with managing redirection and `KeepAuthorization` in Web cmdlets (#18902) (Thanks @CarloToso!) +- Fix `Get-Error` to work with strict mode (#18895) +- Add `AllowInsecureRedirect` switch to Web cmdlets (#18546) (Thanks @CarloToso!) +- `Invoke-RestMethod` `-FollowRelLink` fix links containing commas (#18829) (Thanks @CarloToso!) +- Prioritize the default parameter set when completing positional arguments (#18755) (Thanks @MartinGC94!) +- Add `-CommandWithArgs` parameter to pwsh (#18726) +- Enable creating composite subsystem implementation in modules (#18888) +- Fix `Format-Table -RepeatHeader` for property derived tables (#18870) +- Add `StatusCode` to `HttpResponseException` (#18842) (Thanks @CarloToso!) +- Fix type inference for all scope variables (#18758) (Thanks @MartinGC94!) +- Add completion for Using keywords (#16514) (Thanks @MartinGC94!) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@CarloToso, @iSazonov, @xtqqczze, @turbedi, @syntax-tm, @eltociear, @ArmaanMcleod

+ +
+ +
    +
  • Small cleanup in the WebCmdlet code (#19299) (Thanks @CarloToso!)
  • +
  • Remove unused GUID detection code from console host (#18871) (Thanks @iSazonov!)
  • +
  • Fix CodeFactor issues in the code base - part 4 (#19270) (Thanks @CarloToso!)
  • +
  • Fix codefactor if part 3 (#19269) (Thanks @CarloToso!)
  • +
  • Fix codefactor if part 2 (#19267) (Thanks @CarloToso!)
  • +
  • Fix codefactor if part 1 (#19266) (Thanks @CarloToso!)
  • +
  • Remove comment and simplify condition in WebCmdlets (#19251) (Thanks @CarloToso!)
  • +
  • Small style changes (#19241) (Thanks @CarloToso!)
  • +
  • Use ArgumentException.ThrowIfNullOrEmpty as appropriate [part 1] (#19215) (Thanks @xtqqczze!)
  • +
  • Use using variable to reduce the nested level (#19229) (Thanks @CarloToso!)
  • +
  • Use ArgumentException.ThrowIfNullOrEmpty() in more places (#19213) (Thanks @CarloToso!)
  • +
  • Replace BitConverter.ToString with Convert.ToHexString where appropriate (#19216) (Thanks @turbedi!)
  • +
  • Replace Requires.NotNullOrEmpty(string) with ArgumentException.ThrowIfNullOrEmpty (#19197) (Thanks @xtqqczze!)
  • +
  • Use ArgumentOutOfRangeException.ThrowIfNegativeOrZero when applicable (#19201) (Thanks @xtqqczze!)
  • +
  • Use CallerArgumentExpression on Requires.NotNull (#19200) (Thanks @xtqqczze!)
  • +
  • Revert a few change to not use 'ArgumentNullException.ThrowIfNull' (#19151)
  • +
  • Corrected some minor spelling mistakes (#19176) (Thanks @syntax-tm!)
  • +
  • Fix a typo in InitialSessionState.cs (#19177) (Thanks @eltociear!)
  • +
  • Fix a typo in pwsh help content (#19153)
  • +
  • Revert comment changes in WebRequestPSCmdlet.Common.cs (#19136) (Thanks @CarloToso!)
  • +
  • Small cleanup webcmdlets (#19128) (Thanks @CarloToso!)
  • +
  • Merge partials in WebRequestPSCmdlet.Common.cs (#19126) (Thanks @CarloToso!)
  • +
  • Cleanup WebCmdlets comments (#19124) (Thanks @CarloToso!)
  • +
  • Added minor readability and refactoring fixes to Process.cs (#19123) (Thanks @ArmaanMcleod!)
  • +
  • Small changes in Webcmdlets (#19109) (Thanks @CarloToso!)
  • +
  • Rework SetRequestContent in WebCmdlets (#18964) (Thanks @CarloToso!)
  • +
  • Small cleanup WebCmdlets (#19030) (Thanks @CarloToso!)
  • +
  • Update additional interpolated string changes (#19029)
  • +
  • Revert some of the interpolated string changes (#19018)
  • +
  • Cleanup StreamHelper.cs, WebRequestPSCmdlet.Common.cs and InvokeRestMethodCommand.Common.cs (#18950) (Thanks @CarloToso!)
  • +
  • Small cleanup common code of webcmdlets (#18946) (Thanks @CarloToso!)
  • +
  • Simplification of GetHttpMethod and HttpMethod in WebCmdlets (#18846) (Thanks @CarloToso!)
  • +
  • Fix typo in ModuleCmdletBase.cs (#18933) (Thanks @eltociear!)
  • +
  • Fix regression in RemoveNulls (#18881) (Thanks @iSazonov!)
  • +
  • Replace all NotNull with ArgumentNullException.ThrowIfNull (#18820) (Thanks @CarloToso!)
  • +
  • Cleanup InvokeRestMethodCommand.Common.cs (#18861) (Thanks @CarloToso!)
  • +
+ +
+ +### Tools + +- Add a Mariner install script (#19294) +- Add tool to trigger license information gathering for NuGet modules (#18827) + +### Tests + +- Update and enable the test for the type of `$input` (#18968) (Thanks @MartinGC94!) +- Increase the timeout for creating the `WebListener` (#19268) +- Increase the timeout when waiting for the event log (#19264) +- Add Windows ARM64 CI (#19040) +- Change test so output does not include newline (#19026) +- Allow system lock down test debug hook to work with new WLDP API (#18962) +- Add tests for `Allowinsecureredirect` parameter in Web cmdlets (#18939) (Thanks @CarloToso!) +- Enable `get-help` pattern tests on Unix (#18855) (Thanks @xtqqczze!) +- Create test to check if WebCmdlets decompress brotli-encoded data (#18905) (Thanks @CarloToso!) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@bergmeister, @xtqqczze

+ +
+ +
    +
  • Restructure the package build to simplify signing and packaging stages (#19321)
  • +
  • Bump Microsoft.CodeAnalysis.CSharp from 4.4.0 to 4.6.0-2.23152.6 (#19306)(#19233)
  • +
  • Test fixes for stabilizing tests (#19068)
  • +
  • Bump Newtonsoft.Json from 13.0.2 to 13.0.3 (#19290)(#19289)
  • +
  • Fix mariner sudo detection (#19304)
  • +
  • Add stage for symbols job in Release build (#18937)
  • +
  • Bump .NET to Preview 2 version (#19305)
  • +
  • Move workflows that create PRs to private repo (#19276)
  • +
  • Use reference assemblies generated by dotnet (#19302)
  • +
  • Update the cgmanifest (#18814)(#19165)(#19296)
  • +
  • Always regenerate files WXS fragment (#19196)
  • +
  • MSI installer: Add checkbox and MSI property DISABLE_TELEMETRY to optionally disable telemetry. (#10725) (Thanks @bergmeister!)
  • +
  • Add -Force to Move-Item to fix the GitHub workflow (#19262)
  • +
  • Update and remove outdated docs to fix the URL link checks (#19261)
  • +
  • Bump Markdig.Signed from 0.30.4 to 0.31.0 (#19232)
  • +
  • Add pattern to replace for reference API generation (#19214)
  • +
  • Split test artifact build into windows and non-windows (#19199)
  • +
  • Set LangVersion compiler option to 11.0 (#18877) (Thanks @xtqqczze!)
  • +
  • Update to .NET 8 preview 1 build (#19194)
  • +
  • Simplify Windows Packaging CI Trigger YAML (#19160)
  • +
  • Bump Microsoft.NET.Test.Sdk from 17.4.0 to 17.5.0 (#18823)(#19191)
  • +
  • Add URL for all distributions (#19159)
  • +
  • Bump Microsoft.Extensions.ObjectPool from 7.0.1 to 7.0.3 (#18925)(#19155)
  • +
  • Add verification of R2R at packaging (#19129)
  • +
  • Allow cross compiling windows (#19119)
  • +
  • Update CodeQL build agent (#19113)
  • +
  • Bump XunitXml.TestLogger from 3.0.70 to 3.0.78 (#19066)
  • +
  • Bump Microsoft.CodeAnalysis.Analyzers from 3.3.3 to 3.3.4 (#18975)
  • +
  • Bump BenchmarkDotNet to 0.13.3 (#18878) (Thanks @xtqqczze!)
  • +
  • Bump Microsoft.PowerShell.Native from 7.4.0-preview.1 to 7.4.0-preview.2 (#18910)
  • +
  • Add checks for Windows 8.1 and Server 2012 in the MSI installer (#18904)
  • +
  • Update build to include WinForms / WPF in all Windows builds (#18859)
  • +
+ +
+ +### Documentation and Help Content + +- Update to the latest NOTICES file (#19169)(#19309)(#19086)(#19077) +- Update supported distros in readme (#18667) (Thanks @techguy16!) +- Remove the 'Code Coverage Status' badge (#19265) +- Pull in changelogs for `v7.2.10` and `v7.3.3` releases (#19219) +- Update tools `metadata` and `README` (#18831)(#19204)(#19014) +- Update a broken link in the `README.md` (#19187) +- Fix typos in comments (#19064) (Thanks @spaette!) +- Add `7.2` and `7.3` changelogs (#19025) +- typos (#19058) (Thanks @spaette!) +- Fix typo in `dotnet-tools/README.md` (#19021) (Thanks @spaette!) +- Fix up all comments to be in the proper order with proper spacing (#18619) +- Changelog for `v7.4.0-preview.1` release (#18835) + +[7.4.0-preview.2]: https://github.com/PowerShell/PowerShell/compare/v7.4.0-preview.1...v7.4.0-preview.2 + +## [7.4.0-preview.1] - 2022-12-20 + +### Engine Updates and Fixes + +- Add Instrumentation to `AmsiUtil` and make the init variable readonly (#18727) +- Fix typo in `OutOfProcTransportManager.cs` (#18766) (Thanks @eltociear!) +- Allow non-default encodings to be used in user's script/code (#18605) +- Add `Dim` and `DimOff` to `$PSStyle` (#18653) +- Change `exec` from alias to function to handle arbitrary arguments (#18567) +- The command prefix should also be in the error color for `NormalView` (#18555) +- Skip cloud files marked as "not on disk" during command discovery (#18152) +- Replace `UTF8Encoding(false)` with `Encoding.Default` (#18356) (Thanks @xtqqczze!) +- Fix `Switch-Process` to set `termios` appropriate for child process (#18467) +- On Unix, only explicitly terminate the native process if not in background (#18215) +- Treat `[NullString]::Value` as the string type when resolving methods (#18080) +- Improve pseudo binding for dynamic parameters (#18030) (Thanks @MartinGC94!) +- Make experimental feature `PSAnsiRenderingFileInfo` stable (#18042) +- Update to use version `2.21.0` of Application Insights. (#17903) +- Do not preserve temporary results when no need to do so (#17856) + +### Performance + +- Remove some static constants from `Utils.Separators` (#18154) (Thanks @iSazonov!) +- Avoid using regular expression when unnecessary in `ScriptWriter` (#18348) +- Use source generator for `PSVersionInfo` to improve startup time (#15603) (Thanks @iSazonov!) +- Skip evaluating suggestions at startup (#18232) +- Avoid using `Regex` when not necessary (#18210) + +### General Cmdlet Updates and Fixes + +- Update to use `ComputeCore.dll` for PowerShell Direct (#18194) +- Replace `ArgumentNullException(nameof())` with `ArgumentNullException.ThrowIfNull()` (#18792)(#18784) (Thanks @CarloToso!) +- Remove `TabExpansion` from remote session configuration (#18795) (Internal 23331) +- WebCmdlets get Retry-After from headers if status code is 429 (#18717) (Thanks @CarloToso!) +- Implement `SupportsShouldProcess` in `Stop-Transcript` (#18731) (Thanks @JohnLBevan!) +- Fix `New-Item -ItemType Hardlink` to resolve target to absolute path and not allow link to itself (#18634) +- Add output types to Format commands (#18746) (Thanks @MartinGC94!) +- Fix the process `CommandLine` on Linux (#18710) (Thanks @jborean93!) +- Fix `SuspiciousContentChecker.Match` to detect a predefined string when the text starts with it (#18693) +- Switch `$PSNativeCommandUseErrorActionPreference` to `$true` when feature is enabled (#18695) +- Fix `Start-Job` to check the existence of working directory using the PowerShell way (#18675) +- Webcmdlets add 308 to redirect codes and small cleanup (#18536) (Thanks @CarloToso!) +- Ensure `HelpInfo.Category` is consistently a string (#18254) +- Remove `gcloud` from the legacy list because it's resolved to a .ps1 script (#18575) +- Add `gcloud` and `sqlcmd` to list to use legacy argument passing (#18559) +- Fix native access violation (#18545) (#18547) (Thanks @chrullrich!) +- Fix issue when completing the first command in a script with an empty array expression (#18355) (Thanks @MartinGC94!) +- Improve type inference of hashtable keys (#17907) (Thanks @MartinGC94!) +- Fix `Switch-Process` to copy the current env to the new process (#18452) +- Fix `Switch-Process` error to include the command that is not found (#18443) +- Update `Out-Printer` to remove all decorating ANSI escape sequences from PowerShell formatting (#18425) +- Web cmdlets set default charset encoding to `UTF8` (#18219) (Thanks @CarloToso!) +- Fix incorrect cmdlet name in the script used by `Restart-Computer` (#18374) (Thanks @urizen-source!) +- Add the function `cd~` (#18308) (Thanks @GigaScratch!) +- Fix type inference error for empty return statements (#18351) (Thanks @MartinGC94!) +- Fix the exception reporting in `ConvertFrom-StringData` (#18336) (Thanks @GigaScratch!) +- Implement `IDisposable` in `NamedPipeClient` (#18341) (Thanks @xtqqczze!) +- Replace command-error suggestion with new implementation based on subsystem plugin (#18252) +- Remove the `ProcessorArchitecture` portion from the full name as it's obsolete (#18320) +- Make the fuzzy searching flexible by passing in the fuzzy matcher (#18270) +- Add `-FuzzyMinimumDistance` parameter to `Get-Command` (#18261) +- Improve startup time by triggering initialization of additional types on background thread (#18195) +- Fix decompression in web cmdlets (#17955) (Thanks @iSazonov!) +- Add `CustomTableHeaderLabel` formatting to differentiate table header labels that are not property names (#17346) +- Remove the extra new line form List formatting (#18185) +- Minor update to the `FileInfo` table formatting on Unix to make it more concise (#18183) +- Fix Parent property on processes with complex name (#17545) (Thanks @jborean93!) +- Make PowerShell class not affiliate with `Runspace` when declaring the `NoRunspaceAffinity` attribute (#18138) +- Complete the progress bar rendering in `Invoke-WebRequest` when downloading is complete or cancelled (#18130) +- Display download progress in human readable format for `Invoke-WebRequest` (#14611) (Thanks @bergmeister!) +- Update `WriteConsole` to not use `stackalloc` for buffer with too large size (#18084) +- Filter out compiler generated types for `Add-Type -PassThru` (#18095) +- Fixing `CA2014` warnings and removing the warning suppression (#17982) (Thanks @creative-cloud!) +- Make experimental feature `PSNativeCommandArgumentPassing` stable (#18044) +- Make experimental feature `PSAMSIMethodInvocationLogging` stable (#18041) +- Handle `PSObject` argument specially in method invocation logging (#18060) +- Fix typos in `EventResource.resx` (#18063) (Thanks @eltociear!) +- Make experimental feature `PSRemotingSSHTransportErrorHandling` stable (#18046) +- Make experimental feature `PSExec` stable (#18045) +- Make experimental feature `PSCleanBlock` stable (#18043) +- Fix error formatting to use color defined in `$PSStyle.Formatting` (#17987) +- Remove unneeded use of `chmod 777` (#17974) +- Support mapping foreground/background `ConsoleColor` values to VT escape sequences (#17938) +- Make `pwsh` server modes implicitly not show banner (#17921) +- Add output type attributes for `Get-WinEvent` (#17948) (Thanks @MartinGC94!) +- Remove 1 second minimum delay in `Invoke-WebRequest` for small files, and prevent file-download-error suppression. (#17896) (Thanks @AAATechGuy!) +- Add completion for values in comparisons when comparing Enums (#17654) (Thanks @MartinGC94!) +- Fix positional argument completion (#17796) (Thanks @MartinGC94!) +- Fix member completion in attribute argument (#17902) (Thanks @MartinGC94!) +- Throw when too many parameter sets are defined (#17881) (Thanks @fflaten!) +- Limit searching of `charset` attribute in `meta` tag for HTML to first 1024 characters in webcmdlets (#17813) +- Fix `Update-Help` failing silently with implicit non-US culture. (#17780) (Thanks @dkaszews!) +- Add the `ValidateNotNullOrWhiteSpace` attribute (#17191) (Thanks @wmentha!) +- Improve enumeration of inferred types in pipeline (#17799) (Thanks @MartinGC94!) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@MartinGC94, @CarloToso, @iSazonov, @xtqqczze, @turbedi, @trossr32, @eltociear, @AtariDreams, @jborean93

+ +
+ +
    +
  • Add TSAUpload for APIScan (#18446)
  • +
  • Use Pattern matching in ast.cs (#18794) (Thanks @MartinGC94!)
  • +
  • Cleanup webrequestpscmdlet.common.cs (#18596) (Thanks @CarloToso!)
  • +
  • Unify CreateFile pinvoke in SMA (#18751) (Thanks @iSazonov!)
  • +
  • Cleanup webresponseobject.common (#18785) (Thanks @CarloToso!)
  • +
  • InvokeRestMethodCommand.Common cleanup and merge partials (#18736) (Thanks @CarloToso!)
  • +
  • Replace GetDirectories in CimDscParser (#14319) (Thanks @xtqqczze!)
  • +
  • WebResponseObject.Common merge partials atomic commits (#18703) (Thanks @CarloToso!)
  • +
  • Enable pending test for Start-Process (#18724) (Thanks @iSazonov!)
  • +
  • Remove one CreateFileW (#18732) (Thanks @iSazonov!)
  • +
  • Replace DllImport with LibraryImport for WNetAddConnection2 (#18721) (Thanks @iSazonov!)
  • +
  • Use File.OpenHandle() instead CreateFileW pinvoke (#18722) (Thanks @iSazonov!)
  • +
  • Replace DllImport with LibraryImport for WNetGetConnection (#18690) (Thanks @iSazonov!)
  • +
  • Replace DllImport with LibraryImport - 1 (#18603) (Thanks @iSazonov!)
  • +
  • Replace DllImport with LibraryImport in SMA 3 (#18564) (Thanks @iSazonov!)
  • +
  • Replace DllImport with LibraryImport in SMA - 7 (#18594) (Thanks @iSazonov!)
  • +
  • Use static DateTime.UnixEpoch and RandomNumberGenerator.Fill() (#18621) (Thanks @turbedi!)
  • +
  • Rewrite Get-FileHash to use static HashData methods (#18471) (Thanks @turbedi!)
  • +
  • Replace DllImport with LibraryImport in SMA 8 (#18599) (Thanks @iSazonov!)
  • +
  • Replace DllImport with LibraryImport in SMA 4 (#18579) (Thanks @iSazonov!)
  • +
  • Remove NativeCultureResolver as dead code (#18582) (Thanks @iSazonov!)
  • +
  • Replace DllImport with LibraryImport in SMA 6 (#18581) (Thanks @iSazonov!)
  • +
  • Replace DllImport with LibraryImport in SMA 2 (#18543) (Thanks @iSazonov!)
  • +
  • Use standard SBCS detection (#18593) (Thanks @iSazonov!)
  • +
  • Remove unused pinvokes in RemoteSessionNamedPipe (#18583) (Thanks @iSazonov!)
  • +
  • Replace DllImport with LibraryImport in SMA 5 (#18580) (Thanks @iSazonov!)
  • +
  • Remove SafeRegistryHandle (#18597) (Thanks @iSazonov!)
  • +
  • Remove ArchitectureSensitiveAttribute from the code base (#18598) (Thanks @iSazonov!)
  • +
  • Build COM adapter only on Windows (#18590)
  • +
  • Include timer instantiation for legacy telemetry in conditional compiler statements in Get-Help (#18475) (Thanks @trossr32!)
  • +
  • Convert DllImport to LibraryImport for recycle bin, clipboard, and computerinfo cmdlets (#18526)
  • +
  • Replace DllImport with LibraryImport in SMA 1 (#18520) (Thanks @iSazonov!)
  • +
  • Replace DllImport with LibraryImport in engine (#18496)
  • +
  • Fix typo in InitialSessionState.cs (#18435) (Thanks @eltociear!)
  • +
  • Remove remaining unused strings from resx files (#18448)
  • +
  • Use new LINQ Order() methods instead of OrderBy(static x => x) (#18395) (Thanks @turbedi!)
  • +
  • Make use of StringSplitOptions.TrimEntries when possible (#18412) (Thanks @turbedi!)
  • +
  • Replace some string.Join(string) calls with string.Join(char) (#18411) (Thanks @turbedi!)
  • +
  • Remove unused strings from FileSystem and Registry providers (#18403)
  • +
  • Use generic GetValues<T>, GetNames<T> enum methods (#18391) (Thanks @xtqqczze!)
  • +
  • Remove unused resource strings from SessionStateStrings (#18394)
  • +
  • Remove unused resource strings in System.Management.Automation (#18388)
  • +
  • Use Enum.HasFlags part 1 (#18386) (Thanks @xtqqczze!)
  • +
  • Remove unused strings from parser (#18383)
  • +
  • Remove unused strings from Utility module (#18370)
  • +
  • Remove unused console strings (#18369)
  • +
  • Remove unused strings from ConsoleInfoErrorStrings.resx (#18367)
  • +
  • Code cleanup in ContentHelper.Common.cs (#18288) (Thanks @CarloToso!)
  • +
  • Remove FusionAssemblyIdentity and GlobalAssemblyCache as they are not used (#18334) (Thanks @iSazonov!)
  • +
  • Remove some static initializations in StringManipulationHelper (#18243) (Thanks @xtqqczze!)
  • +
  • Use MemoryExtensions.IndexOfAny in PSv2CompletionCompleter (#18245) (Thanks @xtqqczze!)
  • +
  • Use MemoryExtensions.IndexOfAny in WildcardPattern (#18242) (Thanks @xtqqczze!)
  • +
  • Small cleanup of the stub code (#18301) (Thanks @CarloToso!)
  • +
  • Fix typo in RemoteRunspacePoolInternal.cs (#18263) (Thanks @eltociear!)
  • +
  • Some more code cleanup related to the use of PSVersionInfo (#18231)
  • +
  • Use MemoryExtensions.IndexOfAny in SessionStateInternal (#18244) (Thanks @xtqqczze!)
  • +
  • Use overload APIs that take char instead of string when it's possible (#18179) (Thanks @iSazonov!)
  • +
  • Replace UTF8Encoding(false) with Encoding.Default (#18144) (Thanks @xtqqczze!)
  • +
  • Remove unused variables (#18058) (Thanks @AtariDreams!)
  • +
  • Fix typo in PowerShell.Core.Instrumentation.man (#17963) (Thanks @eltociear!)
  • +
  • Migrate WinTrust functions to a common location (#17598) (Thanks @jborean93!)
  • +
+ +
+ +### Tools + +- Add a function to get the PR Back-port report (#18299) +- Add a workaround in automatic rebase workflow to continue on error (#18176) +- Update list of PowerShell team members in release tools (#17909) +- Don't block if we fail to create the comment (#17869) + +### Tests + +- Add `testexe.exe -echocmdline` to output raw command-line received by the process on Windows (#18591) +- Mark charset test as pending (#18511) +- Skip output rendering tests on Windows Server 2012 R2 (#18382) +- Increase timeout to make subsystem tests more reliable (#18380) +- Add missing -Tag 'CI' to describe blocks. (#18316) +- Use short path instead of multiple quotes in `Get-Item` test relying on node (#18250) +- Replace the CIM class used for `-Amended` parameter test (#17884) (Thanks @sethvs!) +- Stop ongoing progress-bar in `Write-Progress` test (#17880) (Thanks @fflaten!) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+ +
+ +
    +
  • Fix reference assembly generation logic for Microsoft.PowerShell.Commands.Utility (#18818)
  • +
  • Update the cgmanifest (#18676)(#18521)(#18415)(#18408)(#18197)(#18111)(#18051)(#17913)(#17867)(#17934)(#18088)
  • +
  • Bump Microsoft.PowerShell.Native to the latest preview version v7.4.0-preview.1 (#18805)
  • +
  • Remove unnecessary reference to System.Runtime.CompilerServices.Unsafe (#18806)
  • +
  • Update the release tag in metadata.json for next preview (#18799)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#18750)
  • +
  • Bump .NET SDK to version 7.0.101 (#18786)
  • +
  • Bump cirrus-actions/rebase from 1.7 to 1.8 (#18788)
  • +
  • Bump decode-uri-component from 0.2.0 to 0.2.2 (#18712)
  • +
  • Bump Microsoft.CodeAnalysis.CSharp from 4.4.0-4.final to 4.4.0 (#18562)
  • +
  • Bump Newtonsoft.Json from 13.0.1 to 13.0.2 (#18657)
  • +
  • Apply expected file permissions to Linux files after Authenticode signing (#18643)
  • +
  • Remove extra quotes after agent moves to pwsh 7.3 (#18577)
  • +
  • Don't install based on build-id for RPM (#18560)
  • +
  • Bump Microsoft.NET.Test.Sdk from 17.3.2 to 17.4.0 (#18487)
  • +
  • Bump minimatch from 3.0.4 to 3.1.2 (#18514)
  • +
  • Avoid depending on the pre-generated experimental feature list in private and CI builds (#18484)
  • +
  • Update release-MsixBundle.yml to add retries (#18465)
  • +
  • Bump System.Data.SqlClient from 4.8.4 to 4.8.5 in /src/Microsoft.PowerShell.SDK (#18515)
  • +
  • Bump to use internal .NET 7 GA build (#18508)
  • +
  • Insert the pre-release nuget feed before building test artifacts (#18507)
  • +
  • Add test for framework dependent package in release pipeline (#18506) (Internal 23139)
  • +
  • Update to azCopy 10 (#18509)
  • +
  • Fix issues with uploading changelog to GitHub release draft (#18504)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#18442)
  • +
  • Add authenticode signing for assemblies on linux builds (#18440)
  • +
  • Do not remove penimc_cor3.dll from build (#18438)
  • +
  • Bump Microsoft.PowerShell.Native from 7.3.0-rc.1 to 7.3.0 (#18405)
  • +
  • Allow two-digit revisions in vPack package validation pattern (#18392)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#18363)
  • +
  • Bump to .NET 7 RC2 official version (#18328)
  • +
  • Bump to .NET 7 to version 7.0.100-rc.2.22477.20 (#18286)
  • +
  • Replace win7 runtime with win8 and remove APISets (#18304)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#18312)
  • +
  • Recurse the file listing. (#18277)
  • +
  • Create tasks to collect and publish hashes for build files. (#18276)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#18262)
  • +
  • Remove ETW trace collection and uploading for CLR CAP (#18253)
  • +
  • Do not cleanup pwsh.deps.json for framework dependent packages (#18226)
  • +
  • Add branch counter to APIScan build (#18214)
  • +
  • Remove unnecessary native dependencies from the package (#18213)
  • +
  • Remove XML files for min-size package (#18189)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#18216)
  • +
  • Bump Microsoft.PowerShell.Native from 7.3.0-preview.1 to 7.3.0-rc.1 (#18217)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#18201)
  • +
  • Move ApiScan to compliance build (#18191)
  • +
  • Fix the verbose message when using dotnet-install.sh (#18184)
  • +
  • Bump Microsoft.NET.Test.Sdk from 17.3.1 to 17.3.2 (#18163)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#18164)
  • +
  • Make the link to minimal package blob public during release (#18158)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#18147)
  • +
  • Update MSI exit message (#18137)
  • +
  • Bump Microsoft.CodeAnalysis.CSharp from 4.4.0-1.final to 4.4.0-2.final (#18132)
  • +
  • Re-enable building with Ready-to-Run (#18105)
  • +
  • Update DotnetRuntimeMetadata.json for .NET 7 RC1 build (#18091)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#18096)
  • +
  • Add schema for cgmanifest.json (#18036)
  • +
  • Bump Microsoft.CodeAnalysis.CSharp from 4.3.0-3.final to 4.3.0 (#18012)
  • +
  • Add XML reference documents to NuPkg files for SDK (#17997)
  • +
  • Bump Microsoft.NET.Test.Sdk from 17.3.0 to 17.3.1 (#18000)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#17988)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#17983)
  • +
  • Bump Microsoft.CodeAnalysis.NetAnalyzers (#17945)
  • +
  • Make sure Security.types.ps1xml gets signed in release build (#17916)
  • +
  • Make Register Microsoft Update timeout (#17910)
  • +
  • Merge changes from v7.0.12 v7.2.6 and v7.3.0-preview.7
  • +
  • Bump Microsoft.NET.Test.Sdk from 17.2.0 to 17.3.0 (#17871)
  • +
+ +
+ +### Documentation and Help Content + +- Update readme and metadata for releases (#18780)(#18493)(#18393)(#18332)(#18128)(#17870) +- Remove 'please' and 'Core' from README.md per MS style guide (#18578) (Thanks @Rick-Anderson!) +- Change unsupported XML documentation tag (#18608) +- Change public API mention of `monad` to PowerShell (#18491) +- Update security reporting policy to recommend security portal for more streamlined reporting (#18437) +- Changelog for v7.3.0 (#18505) (Internal 23161) +- Replace `msh` in public API comment based documentation with PowerShell equivalent (#18483) +- Add missing XML doc elements for methods in `RunspaceFactory` (#18450) +- Changelog for `v7.3.0-rc.1` (#18400) +- Update changelogs for `v7.2.7` and `v7.0.13` (#18342) +- Update the changelog for v7.3.0-preview.8 (#18136) +- Add the `ConfigurationFile` option to the PowerShell help content (#18093) +- Update help content about the PowerShell flag `-NonInteractive` (#17952) + +[7.4.0-preview.1]: https://github.com/PowerShell/PowerShell/compare/v7.3.0-preview.8...v7.4.0-preview.1 diff --git a/CHANGELOG/7.5.md b/CHANGELOG/7.5.md new file mode 100644 index 00000000000..8cf3dba6282 --- /dev/null +++ b/CHANGELOG/7.5.md @@ -0,0 +1,743 @@ +# 7.5 Changelog + +## [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 file name 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/README.md b/CHANGELOG/README.md index 83efcb0fed5..c20cd311ff5 100644 --- a/CHANGELOG/README.md +++ b/CHANGELOG/README.md @@ -1,8 +1,11 @@ # Changelogs -* [Current preview changelog](preview.md) -* [7.1 changelog](7.1.md) -* [7.0 changelog](7.0.md) -* [6.2 changelog](6.2.md) -* [6.1 changelog](6.1.md) -* [6.0 changelog](6.0.md) +- [Current preview changelog](preview.md) +- [7.4 changelog](7.4.md) +- [7.3 changelog](7.3.md) +- [7.2 changelog](7.2.md) +- [7.1 changelog](7.1.md) +- [7.0 changelog](7.0.md) +- [6.2 changelog](6.2.md) +- [6.1 changelog](6.1.md) +- [6.0 changelog](6.0.md) diff --git a/CHANGELOG/preview.md b/CHANGELOG/preview.md index a4ad0c67431..2663b2e5f0c 100644 --- a/CHANGELOG/preview.md +++ b/CHANGELOG/preview.md @@ -1,118 +1,148 @@ -# Current preview release +# Preview Changelog -## [7.2.0-preview.4] - 2021-03-16 +## [7.6.0-preview.4] ### Breaking Changes -- Fix `Get-Date -UFormat` `%G` and `%g` behavior (#14555) (Thanks @brianary!) +- 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 -- Update engine script signature validation to match `Get-AuthenticodeSignature` logic (#14849) -- Avoid array allocations from `GetDirectories` and `GetFiles` (#14327) (Thanks @xtqqczze!) +- 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 -- Add `UseOSCIndicator` setting to enable progress indicator in terminal (#14927) -- Re-enable VT mode on Windows after running command in `ConsoleHost` (#14413) -- Fix `Move-Item` for `FileSystemProvider` to use copy-delete instead of move for DFS paths (#14913) -- Fix `PromptForCredential()` to add `targetName` as domain (#14504) -- Update `Concise` `ErrorView` to not show line information for errors from script module functions (#14912) -- Remove the 32,767 character limit on the environment block for `Start-Process` (#14111) (Thanks @hbuckle!) -- Don't write possible secrets to verbose stream for web cmdlets (#14788) +- 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 -- Update `dependabot` configuration to V2 format (#14882) -- Add tooling issue slots in PR template (#14697) +- Check GH token availability for Get-Changelog (#25133) ### Tests -- Move misplaced test file to tests directory (#14908) (Thanks @MarianoAlipi!) -- Refactor MSI CI (#14753) +- Add XUnit test for `HandleDoubleAndSingleQuote` in CompletionHelpers class (#25181) (Thanks @ArmaanMcleod!) ### Build and Packaging Improvements
- -Update .NET to version 6.0.100-preview.2.21155.3 - -
    -
  • Update .NET to version 6.0.100-preview.2.21155.3 (#15007)
  • -
  • Bump Microsoft.PowerShell.Native to 7.2.0-preview.1 (#15030)
  • -
  • Create MSIX Bundle package in release pipeline (#14982)
  • -
  • Build self-contained minimal size package for Guest Config team (#14976)
  • -
  • Bump XunitXml.TestLogger from 3.0.62 to 3.0.66 (#14993) (Thanks @dependabot[bot]!)
  • -
  • Enable building PowerShell for Apple M1 runtime (#14923)
  • -
  • Fix the variable name in the condition for miscellaneous analysis CI (#14975)
  • -
  • Fix the variable usage in CI yaml (#14974)
  • -
  • Disable running markdown link verification in release build CI (#14971)
  • -
  • Bump Microsoft.CodeAnalysis.CSharp from 3.9.0-3.final to 3.9.0 (#14934) (Thanks @dependabot[bot]!)
  • -
  • Declare which variable group is used for checking the blob in the release build (#14970)
  • -
  • Update metadata and script to enable consuming .NET daily builds (#14940)
  • -
  • Bump NJsonSchema from 10.3.9 to 10.3.10 (#14933) (Thanks @dependabot[bot]!)
  • -
  • Use template that disables component governance for CI (#14938)
  • -
  • Add suppress for nuget multi-feed warning (#14893)
  • -
  • Bump NJsonSchema from 10.3.8 to 10.3.9 (#14926) (Thanks @dependabot[bot]!)
  • -
  • Add exe wrapper to release (#14881)
  • -
  • Bump Microsoft.ApplicationInsights from 2.16.0 to 2.17.0 (#14847)
  • -
  • Bump Microsoft.NET.Test.Sdk from 16.8.3 to 16.9.1 (#14895) (Thanks @dependabot[bot]!)
  • -
  • Bump NJsonSchema from 10.3.7 to 10.3.8 (#14896) (Thanks @dependabot[bot]!)
  • -
  • Disable codesign validation where the file type is not supported (#14885)
  • -
  • Fixing broken Experimental Feature list in powershell.config.json (#14858)
  • -
  • Bump NJsonSchema from 10.3.6 to 10.3.7 (#14855)
  • -
  • Add exe wrapper for Microsoft Update scenarios (#14737)
  • -
  • Install wget on CentOS 7 docker image (#14857)
  • -
  • Fix install-dotnet download (#14856)
  • -
  • Fix Bootstrap step in Windows daily test runs (#14820)
  • -
  • Bump NJsonSchema from 10.3.5 to 10.3.6 (#14818)
  • -
  • Bump NJsonSchema from 10.3.4 to 10.3.5 (#14807)
  • +
  • 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 -- Update `README.md` and `metadata.json` for upcoming releases (#14755) -- Merge 7.1.3 and 7.0.6 Change log to master (#15009) -- Update `README` and `metadata.json` for releases (#14997) -- Update ChangeLog for `v7.1.2` release (#14783) -- Update ChangeLog for `v7.0.5` release (#14782) (Internal 14479) +- Add 7.4.9 changelog (#25169) +- Create changelog for 7.4.8 (#25089) -[7.2.0-preview.4]: https://github.com/PowerShell/PowerShell/compare/v7.2.0-preview.3...v7.2.0-preview.4 +[7.6.0-preview.4]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.3...v7.6.0-preview.4 -## [7.2.0-preview.3] - 2021-02-11 +## [7.6.0-preview.3] ### Breaking Changes -- Fix `Get-Date -UFormat %u` behavior to comply with ISO 8601 (#14549) (Thanks @brianary!) - -### Engine Updates and Fixes - -- Together with `PSDesiredStateConfiguration` `v3` module allows `Get-DscResource`, `Invoke-DscResource` and DSC configuration compilation on all platforms, supported by PowerShell (using class-based DSC resources). - -### Performance - -- Avoid array allocations from `Directory.GetDirectories` and `Directory.GetFiles`. (#14326) (Thanks @xtqqczze!) -- Avoid `string.ToLowerInvariant()` from `GetEnvironmentVariableAsBool()` to avoid loading libicu at startup (#14323) (Thanks @iSazonov!) -- Get PowerShell version in `PSVersionInfo` using assembly attribute instead of `FileVersionInfo` (#14332) (Thanks @Fs00!) +- Remove trailing space from event source name (#24192) (Thanks @MartinGC94!) ### General Cmdlet Updates and Fixes -- Suppress `Write-Progress` in `ConsoleHost` if output is redirected and fix tests (#14716) -- Experimental feature `PSAnsiProgress`: Add minimal progress bar using ANSI rendering (#14414) -- Fix web cmdlets to properly construct URI from body when using `-NoProxy` (#14673) -- Update the `ICommandPredictor` to provide more feedback and also make feedback easier to be correlated (#14649) -- Reset color after writing `Verbose`, `Debug`, and `Warning` messages (#14698) -- Fix using variable for nested `ForEach-Object -Parallel` calls (#14548) -- When formatting, if collection is modified, don't fail the entire pipeline (#14438) -- Improve completion of parameters for attributes (#14525) (Thanks @MartinGC94!) -- Write proper error messages for `Get-Command ' '` (#13564) (Thanks @jakekerr!) -- Fix typo in the resource string `ProxyURINotSupplied` (#14526) (Thanks @romero126!) -- Add support to `$PSStyle` for strikethrough and hyperlinks (#14461) -- Fix `$PSStyle` blink codes (#14447) (Thanks @iSazonov!) +- 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 @@ -121,105 +151,91 @@ Update .NET to version 6.0.100-preview.2.21155.3

We thank the following contributors!

-

@xtqqczze, @powercode

+

@xtqqczze, @fMichaleczek, @ArmaanMcleod

    -
  • Fix coding style issues: RCS1215, IDE0090, SA1504, SA1119, RCS1139, IDE0032 (#14356, #14341, #14241, #14204, #14442, #14443) (Thanks @xtqqczze!)
  • -
  • Enable coding style checks: CA2249, CA1052, IDE0076, IDE0077, SA1205, SA1003, SA1314, SA1216, SA1217, SA1213 (#14395, #14483, #14494, #14495, #14441, #14476, #14470, #14471, #14472) (Thanks @xtqqczze!)
  • -
  • Enable nullable in PowerShell codebase (#14160, #14172, #14088, #14154, #14166, #14184, #14178) (Thanks @powercode!)
  • -
  • Use string.Split(char) instead of string.Split(string) (#14465) (Thanks @xtqqczze!)
  • -
  • Use string.Contains(char) overload (#14368) (Thanks @xtqqczze!)
  • -
  • Refactor complex if statements (#14398) (Thanks @xtqqczze!)
  • +
  • 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 script to use .NET 6 build resources (#14705) -- Fix the daily GitHub action (#14711) (Thanks @imba-tjd!) -- GitHub Actions: fix deprecated `::set-env` (#14629) (Thanks @imba-tjd!) -- Update markdown test tools (#14325) (Thanks @RDIL!) -- Upgrade `StyleCopAnalyzers` to `v1.2.0-beta.312` (#14354) (Thanks @xtqqczze!) - -### Tests - -- Remove packaging from daily Windows build (#14749) -- Update link to the Manning book (#14750) -- A separate Windows packaging CI (#14670) -- Update `ini` component version in test `package.json` (#14454) -- Disable `libmi` dependent tests for macOS. (#14446) +- Update CODEOWNERS (#24989) ### Build and Packaging Improvements
+ + +

We thank the following contributors!

+

@xtqqczze, @KyZy7

+ +
+
    -
  • Fix the NuGet feed name and URL for .NET 6
  • -
  • Fix third party signing for files in sub-folders (#14751)
  • -
  • Make build script variable an ArrayList to enable Add() method (#14748)
  • -
  • Remove old .NET SDKs to make dotnet restore work with the latest SDK in CI pipeline (#14746)
  • -
  • Remove outdated Linux dependencies (#14688)
  • -
  • Bump .NET SDK version to 6.0.0-preview.1 (#14719)
  • -
  • Bump NJsonSchema to 10.3.4 (#14714)
  • -
  • Update daily GitHub action to allow manual trigger (#14718)
  • -
  • Bump XunitXml.TestLogger to 3.0.62 (#14702)
  • -
  • Make universal deb package based on the deb package specification (#14681)
  • -
  • Add manual release automation steps and improve changelog script (#14445)
  • -
  • Fix release build to upload global tool packages to artifacts (#14620)
  • -
  • Port changes from the PowerShell v7.0.4 release (#14637)
  • -
  • Port changes from the PowerShell v7.1.1 release (#14621)
  • -
  • Updated README and metadata.json (#14401, #14606, #14612)
  • -
  • Do not push nupkg artifacts to MyGet (#14613)
  • -
  • Use one feed in each nuget.config in official builds (#14363)
  • -
  • Fix path signed RPMs are uploaded from in release build (#14424)
  • +
  • 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 -- Update distribution support request template to point to .NET 5.0 support document (#14578) -- Remove security GitHub issue template (#14453) -- Add intent for using the Discussions feature in repo (#14399) -- Fix Universal Dashboard to refer to PowerShell Universal (#14437) -- Update document link because of HTTP 301 redirect (#14431) (Thanks @xtqqczze!) - -[7.2.0-preview.3]: https://github.com/PowerShell/PowerShell/compare/v7.2.0-preview.2...v7.2.0-preview.3 - -## [7.2.0-preview.2] - 2020-12-15 - -### Breaking Changes - -- Improve detection of mutable value types (#12495) (Thanks @vexx32!) -- Ensure `-PipelineVariable` is set for all output from script cmdlets (#12766) (Thanks @vexx32!) +- Add 7.4.7 Changelog (#24844) +- Create changelog for v7.5.0 (#24808) +- Update Changelog for v7.6.0-preview.2 (#24775) -### Experimental Features +[7.6.0-preview.3]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.2...v7.6.0-preview.3 -- `PSAnsiRendering`: Enable ANSI formatting via `$PSStyle` and support suppressing ANSI output (#13758) - -### Performance - -- Optimize `IEnumerable` variant of replace operator (#14221) (Thanks @iSazonov!) -- Refactor multiply operation for better performance in two `Microsoft.PowerShell.Commands.Utility` methods (#14148) (Thanks @xtqqczze!) -- Use `Environment.TickCount64` instead of `Datetime.Now` as the random seed for AppLocker test file content (#14283) (Thanks @iSazonov!) -- Avoid unnecessary array allocations when searching in GAC (#14291) (Thanks @xtqqczze!) -- Use `OrdinalIgnoreCase` in `CommandLineParser` (#14303) (Thanks @iSazonov!) -- Use `StringComparison.Ordinal` instead of `StringComparison.CurrentCulture` (#14298) (Thanks @iSazonov!) -- Avoid creating instances of the generated delegate helper class in `-replace` implementation (#14128) +## [7.6.0-preview.2] - 2025-01-14 ### General Cmdlet Updates and Fixes -- Write better error message if config file is broken (#13496) (Thanks @iSazonov!) -- Make AppLocker Enforce mode take precedence over UMCI Audit mode (#14353) -- Add `-SkipLimitCheck` switch to `Import-PowerShellDataFile` (#13672) -- Restrict `New-Object` in NoLanguage mode under lock down (#14140) (Thanks @krishnayalavarthi!) -- The `-Stream` parameter now works with directories (#13941) (Thanks @kyanha!) -- Avoid an exception if file system does not support reparse points (#13634) (Thanks @iSazonov!) -- Enable `CA1012`: Abstract types should not have public constructors (#13940) (Thanks @xtqqczze!) -- Enable `SA1212`: Property accessors should follow order (#14051) (Thanks @xtqqczze!) +- 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 @@ -228,166 +244,82 @@ Update .NET to version 6.0.100-preview.2.21155.3

We thank the following contributors!

-

@xtqqczze, @matthewjdegarmo, @powercode, @Gimly

+

@xtqqczze

    -
  • Enable SA1007: Operator keyword should be followed by space (#14130) (Thanks @xtqqczze!)
  • -
  • Expand where alias to Where-Object in Reset-PWSHSystemPath.ps1 (#14113) (Thanks @matthewjdegarmo!)
  • -
  • Fix whitespace issues (#14092) (Thanks @xtqqczze!)
  • -
  • Add StyleCop.Analyzers package (#13963) (Thanks @xtqqczze!)
  • -
  • Enable IDE0041: UseIsNullCheck (#14041) (Thanks @xtqqczze!)
  • -
  • Enable IDE0082: ConvertTypeOfToNameOf (#14042) (Thanks @xtqqczze!)
  • -
  • Remove unnecessary usings part 4 (#14023) (Thanks @xtqqczze!)
  • -
  • Fix PriorityAttribute name (#14094) (Thanks @xtqqczze!)
  • -
  • Enable nullable: System.Management.Automation.Interpreter.IBoxableInstruction (#14165) (Thanks @powercode!)
  • -
  • Enable nullable: System.Management.Automation.Provider.IDynamicPropertyProvider (#14167) (Thanks @powercode!)
  • -
  • Enable nullable: System.Management.Automation.Language.IScriptExtent (#14179) (Thanks @powercode!)
  • -
  • Enable nullable: System.Management.Automation.Language.ICustomAstVisitor2 (#14192) (Thanks @powercode!)
  • -
  • Enable nullable: System.Management.Automation.LanguagePrimitives.IConversionData (#14187) (Thanks @powercode!)
  • -
  • Enable nullable: System.Automation.Remoting.Client.IWSManNativeApiFacade (#14186) (Thanks @powercode!)
  • -
  • Enable nullable: System.Management.Automation.Language.ISupportsAssignment (#14180) (Thanks @powercode!)
  • -
  • Enable nullable: System.Management.Automation.ICommandRuntime2 (#14183) (Thanks @powercode!)
  • -
  • Enable nullable: System.Management.Automation.IOutputProcessingState (#14175) (Thanks @powercode!)
  • -
  • Enable nullable: System.Management.Automation.IJobDebugger (#14174) (Thanks @powercode!)
  • -
  • Enable nullable: System.Management.Automation.Interpreter.IInstructionProvider (#14173) (Thanks @powercode!)
  • -
  • Enable nullable: System.Management.Automation.IHasSessionStateEntryVisibility (#14169) (Thanks @powercode!)
  • -
  • Enable nullable: System.Management.Automation.Tracing.IEtwEventCorrelator (#14168) (Thanks @powercode!)
  • -
  • Fix syntax error in Windows packaging script (#14377)
  • -
  • Remove redundant local assignment in AclCommands (#14358) (Thanks @xtqqczze!)
  • -
  • Enable nullable: System.Management.Automation.Language.IAstPostVisitHandler (#14164) (Thanks @powercode!)
  • -
  • Enable nullable: System.Management.Automation.IModuleAssemblyInitializer (#14158) (Thanks @powercode!)
  • -
  • Use Microsoft.PowerShell.MarkdownRender package from nuget.org (#14090)
  • -
  • Replace GetFiles in TestModuleManifestCommand (#14317) (Thanks @xtqqczze!)
  • -
  • Enable nullable: System.Management.Automation.Provider.IContentWriter (#14152) (Thanks @powercode!)
  • -
  • Simplify getting Encoding in TranscriptionOption.FlushContentToDisk (#13910) (Thanks @Gimly!)
  • -
  • Mark applicable structs as readonly and use in-modifier (#13919) (Thanks @xtqqczze!)
  • -
  • Enable nullable: System.Management.Automation.IArgumentCompleter (#14182) (Thanks @powercode!)
  • -
  • Enable CA1822: Mark private members as static (#13897) (Thanks @xtqqczze!)
  • -
  • Fix IDE0090: Simplify new expression part 6 (#14338) (Thanks @xtqqczze!)
  • -
  • Avoid array allocations from GetDirectories/GetFiles. (#14328) (Thanks @xtqqczze!)
  • -
  • Avoid array allocations from GetDirectories/GetFiles. (#14330) (Thanks @xtqqczze!)
  • -
  • Fix RCS1188: Remove redundant auto-property initialization part 2 (#14262) (Thanks @xtqqczze!)
  • -
  • Enable nullable: System.Management.Automation.Host.IHostSupportsInteractiveSession (#14170) (Thanks @powercode!)
  • -
  • Enable nullable: System.Management.Automation.Provider.IPropertyCmdletProvider (#14176) (Thanks @powercode!)
  • -
  • Fix IDE0090: Simplify new expression part 5 (#14301) (Thanks @xtqqczze!)
  • -
  • Enable IDE0075: SimplifyConditionalExpression (#14078) (Thanks @xtqqczze!)
  • -
  • Remove unnecessary usings part 9 (#14288) (Thanks @xtqqczze!)
  • -
  • Fix StyleCop and MarkdownLint CI failures (#14297) (Thanks @xtqqczze!)
  • -
  • Enable SA1000: Keywords should be spaced correctly (#13973) (Thanks @xtqqczze!)
  • -
  • Fix RCS1188: Remove redundant auto-property initialization part 1 (#14261) (Thanks @xtqqczze!)
  • -
  • Mark private members as static part 10 (#14235) (Thanks @xtqqczze!)
  • -
  • Mark private members as static part 9 (#14234) (Thanks @xtqqczze!)
  • -
  • Fix SA1642 for Microsoft.Management.Infrastructure.CimCmdlets (#14239) (Thanks @xtqqczze!)
  • -
  • Use AsSpan/AsMemory slice constructor (#14265) (Thanks @xtqqczze!)
  • -
  • Fix IDE0090: Simplify new expression part 4.6 (#14260) (Thanks @xtqqczze!)
  • -
  • Fix IDE0090: Simplify new expression part 4.5 (#14259) (Thanks @xtqqczze!)
  • -
  • Fix IDE0090: Simplify new expression part 4.3 (#14257) (Thanks @xtqqczze!)
  • -
  • Fix IDE0090: Simplify new expression part 4.2 (#14256) (Thanks @xtqqczze!)
  • -
  • Fix IDE0090: Simplify new expression part 2 (#14200) (Thanks @xtqqczze!)
  • -
  • Enable SA1643: Destructor summary documentation should begin with standard text (#14236) (Thanks @xtqqczze!)
  • -
  • Fix IDE0090: Simplify new expression part 4.4 (#14258) (Thanks @xtqqczze!)
  • -
  • Use xml documentation child blocks correctly (#14249) (Thanks @xtqqczze!)
  • -
  • Fix IDE0090: Simplify new expression part 4.1 (#14255) (Thanks @xtqqczze!)
  • -
  • Use consistent spacing in xml documentation tags (#14231) (Thanks @xtqqczze!)
  • -
  • Enable IDE0074: Use coalesce compound assignment (#13396) (Thanks @xtqqczze!)
  • -
  • Remove unnecessary finalizers (#14248) (Thanks @xtqqczze!)
  • -
  • Mark local variable as const (#13217) (Thanks @xtqqczze!)
  • -
  • Fix IDE0032: UseAutoProperty part 2 (#14244) (Thanks @xtqqczze!)
  • -
  • Fix IDE0032: UseAutoProperty part 1 (#14243) (Thanks @xtqqczze!)
  • -
  • Mark private members as static part 8 (#14233) (Thanks @xtqqczze!)
  • -
  • Fix CA1822: Mark members as static part 6 (#14229) (Thanks @xtqqczze!)
  • -
  • Fix CA1822: Mark members as static part 5 (#14228) (Thanks @xtqqczze!)
  • -
  • Fix CA1822: Mark members as static part 4 (#14227) (Thanks @xtqqczze!)
  • -
  • Fix CA1822: Mark members as static part 3 (#14226) (Thanks @xtqqczze!)
  • -
  • Fix CA1822: Mark members as static part 2 (#14225) (Thanks @xtqqczze!)
  • -
  • Fix CA1822: Mark members as static part 1 (#14224) (Thanks @xtqqczze!)
  • -
  • Use see keyword in documentation (#14220) (Thanks @xtqqczze!)
  • -
  • Enable CA2211: Non-constant fields should not be visible (#14073) (Thanks @xtqqczze!)
  • -
  • Enable CA1816: Dispose methods should call SuppressFinalize (#14074) (Thanks @xtqqczze!)
  • -
  • Remove incorrectly implemented finalizer (#14246) (Thanks @xtqqczze!)
  • -
  • Fix CA1822: Mark members as static part 7 (#14230) (Thanks @xtqqczze!)
  • -
  • Fix SA1122: Use string.Empty for empty strings (#14218) (Thanks @xtqqczze!)
  • -
  • Fix various xml documentation issues (#14223) (Thanks @xtqqczze!)
  • -
  • Remove unnecessary usings part 8 (#14072) (Thanks @xtqqczze!)
  • -
  • Enable SA1006: Preprocessor keywords should not be preceded by space (#14052) (Thanks @xtqqczze!)
  • -
  • Fix SA1642 for Microsoft.PowerShell.Commands.Utility (#14142) (Thanks @xtqqczze!)
  • -
  • Enable CA2216: Disposable types should declare finalizer (#14089) (Thanks @xtqqczze!)
  • -
  • Wrap and name LoadBinaryModule arguments (#14193) (Thanks @xtqqczze!)
  • -
  • Wrap and name GetListOfFilesFromData arguments (#14194) (Thanks @xtqqczze!)
  • -
  • Enable SA1002: Semicolons should be spaced correctly (#14197) (Thanks @xtqqczze!)
  • -
  • Fix IDE0090: Simplify new expression part 3 (#14201) (Thanks @xtqqczze!)
  • -
  • Enable SA1106: Code should not contain empty statements (#13964) (Thanks @xtqqczze!)
  • -
  • Code performance fixes follow-up (#14207) (Thanks @xtqqczze!)
  • -
  • Remove uninformative comments (#14199) (Thanks @xtqqczze!)
  • -
  • Fix IDE0090: Simplify new expression part 1 (#14027) (Thanks @xtqqczze!)
  • -
  • Enable SA1517: Code should not contain blank lines at start of file (#14131) (Thanks @xtqqczze!)
  • -
  • Enable SA1131: Use readable conditions (#14132) (Thanks @xtqqczze!)
  • -
  • Enable SA1507: Code should not contain multiple blank lines in a row (#14136) (Thanks @xtqqczze!)
  • -
  • Enable SA1516 Elements should be separated by blank line (#14137) (Thanks @xtqqczze!)
  • -
  • Enable IDE0031: Null check can be simplified (#13548) (Thanks @xtqqczze!)
  • -
  • Enable CA1065: Do not raise exceptions in unexpected locations (#14117) (Thanks @xtqqczze!)
  • -
  • Enable CA1000: Do not declare static members on generic types (#14097) (Thanks @xtqqczze!)
  • +
  • Seal ClientRemoteSessionDSHandlerImpl (#21218) (Thanks @xtqqczze!)
  • +
  • Seal internal type ClientRemoteSessionDSHandlerImpl (#24705) (Thanks @xtqqczze!)
  • +
  • Seal classes in RemotingProtocol2 (#21164) (Thanks @xtqqczze!)
### Tools -- Fixing formatting in `Reset-PWSHSystemPath.ps1` (#13689) (Thanks @dgoldman-msft!) +- Added Justin Chung as Powershell team memeber on releaseTools.psm1 (#24672) ### Tests -- Reinstate `Test-Connection` tests (#13324) -- Update markdown test packages with security fixes (#14145) +- Skip CIM ETS member test on older Windows platforms (#24681) ### Build and Packaging Improvements
+ + +

Updated SDK to 9.0.101

+ +
+
    -
  • Fix a typo in the Get-ChangeLog function (#14129)
  • -
  • Update README and metadata.json for 7.2.0-preview.1 release (#14104)
  • -
  • Bump NJsonSchema from 10.2.2 to 10.3.1 (#14040)
  • -
  • Move windows package signing to use ESRP (#14060)
  • -
  • Use one feed in each nuget.config in official builds (#14363)
  • -
  • Fix path signed RPMs are uploaded from in release build (#14424)
  • -
  • Add Microsoft.PowerShell.MarkdownRender to the package reference list (#14386)
  • -
  • Fix issue with unsigned build (#14367)
  • -
  • Move macOS and nuget to ESRP signing (#14324)
  • -
  • Fix nuget packaging to scrub NullableAttribute (#14344)
  • -
  • Bump Microsoft.NET.Test.Sdk from 16.8.0 to 16.8.3 (#14310)
  • -
  • Bump Markdig.Signed from 0.22.0 to 0.22.1 (#14305)
  • -
  • Bump Microsoft.ApplicationInsights from 2.15.0 to 2.16.0 (#14031)
  • -
  • Move Linux to ESRP signing (#14210)
  • +
  • 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 -- Fix example `nuget.config` (#14349) -- Fix a broken link in Code Guidelines doc (#14314) (Thanks @iSazonov!) +- Update cmdlets WG members (#24275) (Thanks @kilasuit!) -[7.2.0-preview.2]: https://github.com/PowerShell/PowerShell/compare/v7.2.0-preview.1...v7.2.0-preview.2 +[7.6.0-preview.2]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.1...v7.6.0-preview.2 -## [7.2.0-preview.1] - 2020-11-17 +## [7.6.0-preview.1] - 2024-12-16 -### Engine Updates and Fixes +### Breaking Changes -- Change the default fallback encoding for `GetEncoding` in `Start-Transcript` to be `UTF8` without a BOM (#13732) (Thanks @Gimly!) +- Treat large Enum values as numbers in `ConvertTo-Json` (#20999) (Thanks @jborean93!) ### General Cmdlet Updates and Fixes -- Update `pwsh -?` output to match docs (#13748) -- Fix `NullReferenceException` in `Test-Json` (#12942) (Thanks @iSazonov!) -- Make `Dispose` in `TranscriptionOption` idempotent (#13839) (Thanks @krishnayalavarthi!) -- Add additional Microsoft PowerShell modules to the tracked modules list (#12183) -- Relax further `SSL` verification checks for `WSMan` on non-Windows hosts with verification available (#13786) (Thanks @jborean93!) -- Add the `OutputTypeAttribute` to `Get-ExperimentalFeature` (#13738) (Thanks @ThomasNieto!) -- Fix blocking wait when starting file associated with a Windows application (#13750) -- Emit warning if `ConvertTo-Json` exceeds `-Depth` value (#13692) +- 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 @@ -396,57 +328,27 @@ Update .NET to version 6.0.100-preview.2.21155.3

We thank the following contributors!

-

@xtqqczze, @mkswd, @ThomasNieto, @PatLeong, @paul-cheung, @georgettica

+

@eltociear, @JayBazuzi

    -
  • Fix RCS1049: Simplify boolean comparison (#13994) (Thanks @xtqqczze!)
  • -
  • Enable IDE0062: Make local function static (#14044) (Thanks @xtqqczze!)
  • -
  • Enable CA2207: Initialize value type static fields inline (#14068) (Thanks @xtqqczze!)
  • -
  • Enable CA1837: Use ProcessId and CurrentManagedThreadId from System.Environment (#14063) (Thanks @xtqqczze and @PatLeong!)
  • -
  • Remove unnecessary using directives (#14014, #14017, #14021, #14050, #14065, #14066, #13863, #13860, #13861, #13814) (Thanks @xtqqczze and @ThomasNieto!)
  • -
  • Remove unnecessary usage of LINQ Count method (#13545) (Thanks @xtqqczze!)
  • -
  • Fix SA1518: The code must not contain extra blank lines at the end of the file (#13574) (Thanks @xtqqczze!)
  • -
  • Enable CA1829: Use the Length or Count property instead of Count() (#13925) (Thanks @xtqqczze!)
  • -
  • Enable CA1827: Do not use Count() or LongCount() when Any() can be used (#13923) (Thanks @xtqqczze!)
  • -
  • Enable or fix nullable usage in a few files (#13793, #13805, #13808, #14018, #13804) (Thanks @mkswd and @georgettica!)
  • -
  • Enable IDE0040: Add accessibility modifiers (#13962, #13874) (Thanks @xtqqczze!)
  • -
  • Make applicable private Guid fields readonly (#14000) (Thanks @xtqqczze!)
  • -
  • Fix CA1003: Use generic event handler instances (#13937) (Thanks @xtqqczze!)
  • -
  • Simplify delegate creation (#13578) (Thanks @xtqqczze!)
  • -
  • Fix RCS1033: Remove redundant boolean literal (#13454) (Thanks @xtqqczze!)
  • -
  • Fix RCS1221: Use pattern matching instead of combination of as operator and null check (#13333) (Thanks @xtqqczze!)
  • -
  • Use is not syntax (#13338) (Thanks @xtqqczze!)
  • -
  • Replace magic number with constant in PDH (#13536) (Thanks @xtqqczze!)
  • -
  • Fix accessor order (#13538) (Thanks @xtqqczze!)
  • -
  • Enable IDE0054: Use compound assignment (#13546) (Thanks @xtqqczze!)
  • -
  • Fix RCS1098: Constant values should be on right side of comparisons (#13833) (Thanks @xtqqczze!)
  • -
  • Enable CA1068: CancellationToken parameters must come last (#13867) (Thanks @xtqqczze!)
  • -
  • Enable CA10XX rules with suggestion severity (#13870, #13928, #13924) (Thanks @xtqqczze!)
  • -
  • Enable IDE0064: Make Struct fields writable (#13945) (Thanks @xtqqczze!)
  • -
  • Run dotnet-format to improve formatting of source code (#13503) (Thanks @xtqqczze!)
  • -
  • Enable CA1825: Avoid zero-length array allocations (#13961) (Thanks @xtqqczze!)
  • -
  • Add IDE analyzer rule IDs to comments (#13960) (Thanks @xtqqczze!)
  • -
  • Enable CA1830: Prefer strongly-typed Append and Insert method overloads on StringBuilder (#13926) (Thanks @xtqqczze!)
  • -
  • Enforce code style in build (#13957) (Thanks @xtqqczze!)
  • -
  • Enable CA1836: Prefer IsEmpty over Count when available (#13877) (Thanks @xtqqczze!)
  • -
  • Enable CA1834: Consider using StringBuilder.Append(char) when applicable (#13878) (Thanks @xtqqczze!)
  • -
  • Fix IDE0044: Make field readonly (#13884, #13885, #13888, #13892, #13889, #13886, #13890, #13891, #13887, #13893, #13969, #13967, #13968, #13970, #13971, #13966, #14012) (Thanks @xtqqczze!)
  • -
  • Enable IDE0048: Add required parentheses (#13896) (Thanks @xtqqczze!)
  • -
  • Enable IDE1005: Invoke delegate with conditional access (#13911) (Thanks @xtqqczze!)
  • -
  • Enable IDE0036: Enable the check on the order of modifiers (#13958, #13881) (Thanks @xtqqczze!)
  • -
  • Use span-based String.Concat instead of String.Substring (#13500) (Thanks @xtqqczze!)
  • -
  • Enable CA1050: Declare types in namespace (#13872) (Thanks @xtqqczze!)
  • -
  • Fix minor keyword typo in C# code comment (#13811) (Thanks @paul-cheung!)
  • +
  • Fix typos in ShowModuleControl.xaml.cs (#24248) (Thanks @eltociear!)
  • +
  • Fix a typo in the build doc (#24172) (Thanks @JayBazuzi!)
### Tools -- Enable `CodeQL` Security scanning (#13894) -- Add global `AnalyzerConfig` with default configuration (#13835) (Thanks @xtqqczze!) +- 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 @@ -455,36 +357,91 @@ Update .NET to version 6.0.100-preview.2.21155.3

We thank the following contributors!

-

@mkswd, @xtqqczze

+

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

    -
  • Bump Microsoft.NET.Test.Sdk to 16.8.0 (#14020)
  • -
  • Bump Microsoft.CodeAnalysis.CSharp to 3.8.0 (#14075)
  • -
  • Remove workarounds for .NET 5 RTM builds (#14038)
  • -
  • Migrate 3rd party signing to ESRP (#14010)
  • -
  • Fixes to release pipeline for GA release (#14034)
  • -
  • Don't do a shallow checkout (#13992)
  • -
  • Add validation and dependencies for Ubuntu 20.04 distribution to packaging script (#13993)
  • -
  • Add .NET install workaround for RTM (#13991)
  • -
  • Move to ESRP signing for Windows files (#13988)
  • -
  • Update PSReadLine version to 2.1.0 (#13975)
  • -
  • Bump .NET to version 5.0.100-rtm.20526.5 (#13920)
  • -
  • Update script to use .NET RTM feeds (#13927)
  • -
  • Add checkout step to release build templates (#13840)
  • -
  • Turn on /features:strict for all projects (#13383) (Thanks @xtqqczze!)
  • -
  • Bump NJsonSchema to 10.2.2 (#13722, #13751)
  • -
  • Add flag to make Linux script publish to production repo (#13714)
  • -
  • Bump Markdig.Signed to 0.22.0 (#13741)
  • -
  • Use new release script for Linux packages (#13705)
  • +
  • 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 -- Fix links to LTS versions for Windows (#14070) -- Fix `crontab` formatting in example doc (#13712) (Thanks @dgoldman-msft!) - -[7.2.0-preview.1]: https://github.com/PowerShell/PowerShell/compare/v7.1.0...v7.2.0-preview.1 +- 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/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 90768d1293e..686e5e7a090 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,8 +1,10 @@ -# Code of Conduct +# Microsoft Open Source Code of Conduct -This project has adopted the [Microsoft Open Source Code of Conduct][conduct-code]. -For more information see the [Code of Conduct FAQ][conduct-FAQ] or contact [opencode@microsoft.com][conduct-email] with any additional questions or comments. +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). -[conduct-code]: https://opensource.microsoft.com/codeofconduct/ -[conduct-FAQ]: https://opensource.microsoft.com/codeofconduct/faq/ -[conduct-email]: mailto:opencode@microsoft.com +Resources: + +- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) +- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) +- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns +- Employees can reach out at [aka.ms/opensource/moderation-support](https://aka.ms/opensource/moderation-support) diff --git a/DotnetRuntimeMetadata.json b/DotnetRuntimeMetadata.json index c1f96b55956..d4c6229d22d 100644 --- a/DotnetRuntimeMetadata.json +++ b/DotnetRuntimeMetadata.json @@ -1,11 +1,15 @@ { "sdk": { - "channel": "release/6.0.1xx-preview2", - "packageVersionPattern": "6.0.0-preview.2", - "sdkImageVersion": "6.0.100", - "nextChannel": "6.0.1xx-preview2/daily" + "channel": "9.0.1xx-preview6", + "quality": "daily", + "qualityFallback": "preview", + "packageVersionPattern": "9.0.0-preview.6", + "sdkImageVersion": "10.0.100-preview.3.25201.16", + "nextChannel": "9.0.0-preview.7", + "azureFeed": "", + "sdkImageOverride": "" }, - "internalfeed" : { - "url": null + "internalfeed": { + "url": "" } } diff --git a/PowerShell.Common.props b/PowerShell.Common.props index 38cd13e007d..5dce6eac9a5 100644 --- a/PowerShell.Common.props +++ b/PowerShell.Common.props @@ -49,13 +49,20 @@ $([System.Text.RegularExpressions.Regex]::Match($(ReleaseTag), $(RegexReleaseTag)).Groups[6].Value) 100 + + 500 $([MSBuild]::Add($(ReleaseTagSemVersionPart), $(RCIncrementValue))) $(ReleaseTag) $(ReleaseTagVersionPart).$(ReleaseTagSemVersionPart) - $(ReleaseTagVersionPart) + $(ReleaseTagVersionPart).$(GAIncrementValue) + + $(PSCoreFileVersion) + $([System.Version]::Parse($(PSCoreFileVersion)).Major).$([System.Version]::Parse($(PSCoreFileVersion)).Minor).0.$([System.Version]::Parse($(PSCoreFileVersion)).Revision) @@ -82,7 +89,7 @@ --> $(PSCoreFileVersion) @@ -102,6 +109,9 @@ + + + + true + true true + portable - - + + true full - - - - false - portable - - - - - - portable - - strict diff --git a/PowerShell.sln b/PowerShell.sln index 224d27ab3fc..4938316281d 100644 --- a/PowerShell.sln +++ b/PowerShell.sln @@ -31,6 +31,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "powershell-unix", "src\powe EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "xUnit.tests", "test\xUnit\xUnit.tests.csproj", "{08704934-9764-48CE-86DB-BCF0A1CF7899}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PSVersionInfoGenerator", "src\System.Management.Automation\SourceGenerators\PSVersionInfoGenerator\PSVersionInfoGenerator.csproj", "{B22424E8-0516-4FC3-A9CB-D84D15EF0589}" +EndProject # Configuration mapping comment # All global configurations must be mapped to project configurations # diff --git a/README.md b/README.md index b469018e5d0..dade0207250 100644 --- a/README.md +++ b/README.md @@ -1,155 +1,50 @@ # ![logo][] PowerShell Welcome to the PowerShell GitHub Community! -PowerShell Core is a cross-platform (Windows, Linux, and macOS) automation and configuration tool/framework that works well with your existing tools and is optimized +[PowerShell](https://learn.microsoft.com/powershell/scripting/overview) 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. +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 Core +## Windows PowerShell vs. PowerShell 7+ -Although this repository started as a fork of the Windows PowerShell code base, changes made in this repository do not make their way back to Windows PowerShell 5.1 automatically. -This also means that [issues tracked here][issues] are only for PowerShell Core 6 and higher. -Windows PowerShell specific issues should be opened on [UserVoice][]. +Although this repository started as a fork of the Windows PowerShell codebase, changes made in this repository are not ported back to Windows PowerShell 5.1. +This also means that [issues tracked here][issues] are only for PowerShell 7.x and higher. +Windows PowerShell specific issues should be reported with the [Feedback Hub app][feedback-hub], by choosing "Apps > PowerShell" in the category. [issues]: https://github.com/PowerShell/PowerShell/issues -[UserVoice]: https://windowsserver.uservoice.com/forums/301869-powershell +[feedback-hub]: https://support.microsoft.com/windows/send-feedback-to-microsoft-with-the-feedback-hub-app-f59187f8-8739-22d6-ba93-f66612949332 ## New to PowerShell? -If you are new to PowerShell and would like to learn more, we recommend reviewing the [getting started][] documentation. +If you are new to PowerShell and want to learn more, we recommend reviewing the [getting started][] documentation. -[getting started]: https://github.com/PowerShell/PowerShell/tree/master/docs/learning-powershell +[getting started]: https://learn.microsoft.com/powershell/scripting/learn/more-powershell-learning ## Get PowerShell -You can download and install a PowerShell package for any of the following platforms. - -| Supported Platform | Download (LTS) | Downloads (stable) | Downloads (preview) | How to Install | -| -------------------------------------------| ------------------------| ------------------------| ----------------------| ------------------------------| -| [Windows (x64)][corefx-win] | [.msi][lts-windows-64] | [.msi][rl-windows-64] | [.msi][pv-windows-64] | [Instructions][in-windows] | -| [Windows (x86)][corefx-win] | [.msi][lts-windows-86] | [.msi][rl-windows-86] | [.msi][pv-windows-86] | [Instructions][in-windows] | -| [Ubuntu 20.04][corefx-linux] | | [.deb][rl-ubuntu20] | [.deb][pv-ubuntu20] | [Instructions][in-ubuntu20] | -| [Ubuntu 18.04][corefx-linux] | [.deb][lts-ubuntu18] | [.deb][rl-ubuntu18] | [.deb][pv-ubuntu18] | [Instructions][in-ubuntu18] | -| [Ubuntu 16.04][corefx-linux] | [.deb][lts-ubuntu16] | [.deb][rl-ubuntu16] | [.deb][pv-ubuntu16] | [Instructions][in-ubuntu16] | -| [Debian 9][corefx-linux] | [.deb][lts-debian9] | [.deb][rl-debian9] | [.deb][pv-debian9] | [Instructions][in-deb9] | -| [Debian 10][corefx-linux] | [.deb][lts-debian10] | [.deb][rl-debian10] | [.deb][pv-debian10] | [Instructions][in-deb9] | -| [Debian 11][corefx-linux] | | [.deb][rl-debian11] | [.deb][pv-debian11] | | -| [CentOS 7][corefx-linux] | [.rpm][lts-centos] | [.rpm][rl-centos] | [.rpm][pv-centos] | [Instructions][in-centos] | -| [CentOS 8][corefx-linux] | [.rpm][lts-centos8] | [.rpm][rl-centos8] | [.rpm][pv-centos8] | | -| [Red Hat Enterprise Linux 7][corefx-linux] | [.rpm][lts-centos] | [.rpm][rl-centos] | [.rpm][pv-centos] | [Instructions][in-rhel7] | -| [openSUSE 42.3][corefx-linux] | [.rpm][lts-centos] | [.rpm][rl-centos] | [.rpm][pv-centos] | [Instructions][in-opensuse] | -| [Fedora 30][corefx-linux] | [.rpm][lts-centos] | [.rpm][rl-centos] | [.rpm][pv-centos] | [Instructions][in-fedora] | -| [macOS 10.13+][corefx-macos] | [.pkg][lts-macos] | [.pkg][rl-macos] | [.pkg][pv-macos] | [Instructions][in-macos] | -| Docker | | | | [Instructions][in-docker] | - -You can download and install a PowerShell package for any of the following platforms, **which are supported by the community.** - -| Platform | Downloads (stable) | Downloads (preview) | How to Install | -| -------------------------| ------------------------| ----------------------------- | ------------------------------| -| Arch Linux | | | [Instructions][in-archlinux] | -| Kali Linux | [.deb][rl-ubuntu16] | [.deb][pv-ubuntu16] | [Instructions][in-kali] | -| Many Linux distributions | [Snapcraft][rl-snap] | [Snapcraft][pv-snap] | | - -You can also download the PowerShell binary archives for Windows, macOS and Linux. - -| Platform | Downloads (stable) | Downloads (preview) | How to Install | -| ---------------| --------------------------------------------------- | ------------------------------------------------| -----------------------------------------------| -| Windows | [32-bit][rl-winx86-zip]/[64-bit][rl-winx64-zip] | [32-bit][pv-winx86-zip]/[64-bit][pv-winx64-zip] | [Instructions][in-windows-zip] | -| macOS | [64-bit][rl-macos-tar] | [64-bit][pv-macos-tar] | [Instructions][in-tar-macos] | -| Linux | [64-bit][rl-linux-tar] | [64-bit][pv-linux-tar] | [Instructions][in-tar-linux] | -| Windows (Arm) | [64-bit][rl-winarm64] (preview) | [64-bit][pv-winarm64] | [Instructions][in-arm] | -| Raspbian (Arm) | [32-bit][rl-arm32]/[64-bit][rl-arm64] | [32-bit][pv-arm32]/[64-bit][pv-arm64] | [Instructions][in-raspbian] | - -[lts-windows-86]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.6/PowerShell-7.0.6-win-x86.msi -[lts-windows-64]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.6/PowerShell-7.0.6-win-x64.msi -[lts-ubuntu18]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.6/powershell-lts_7.0.6-1.ubuntu.18.04_amd64.deb -[lts-ubuntu16]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.6/powershell-lts_7.0.6-1.ubuntu.16.04_amd64.deb -[lts-debian9]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.6/powershell-lts_7.0.6-1.debian.9_amd64.deb -[lts-debian10]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.6/powershell-lts_7.0.6-1.debian.10_amd64.deb -[lts-centos]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.6/powershell-lts-7.0.6-1.rhel.7.x86_64.rpm -[lts-centos8]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.6/powershell-lts-7.0.6-1.centos.8.x86_64.rpm -[lts-macos]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.6/powershell-lts-7.0.6-osx-x64.pkg - -[rl-windows-64]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.3/PowerShell-7.1.3-win-x64.msi -[rl-windows-86]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.3/PowerShell-7.1.3-win-x86.msi -[rl-ubuntu20]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.3/powershell_7.1.3-1.ubuntu.20.04_amd64.deb -[rl-ubuntu18]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.3/powershell_7.1.3-1.ubuntu.18.04_amd64.deb -[rl-ubuntu16]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.3/powershell_7.1.3-1.ubuntu.16.04_amd64.deb -[rl-debian9]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.3/powershell_7.1.3-1.debian.9_amd64.deb -[rl-debian10]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.3/powershell_7.1.3-1.debian.10_amd64.deb -[rl-debian11]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.3/powershell_7.1.3-1.debian.11_amd64.deb -[rl-centos]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.3/powershell-7.1.3-1.rhel.7.x86_64.rpm -[rl-centos8]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.3/powershell-7.1.3-1.centos.8.x86_64.rpm -[rl-macos]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.3/powershell-7.1.3-osx-x64.pkg -[rl-winarm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.3/PowerShell-7.1.3-win-arm64.zip -[rl-winx86-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.3/PowerShell-7.1.3-win-x86.zip -[rl-winx64-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.3/PowerShell-7.1.3-win-x64.zip -[rl-macos-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.3/powershell-7.1.3-osx-x64.tar.gz -[rl-linux-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.3/powershell-7.1.3-linux-x64.tar.gz -[rl-arm32]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.3/powershell-7.1.3-linux-arm32.tar.gz -[rl-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.3/powershell-7.1.3-linux-arm64.tar.gz -[rl-snap]: https://snapcraft.io/powershell - -[pv-windows-64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-preview.4/PowerShell-7.2.0-preview.4-win-x64.msi -[pv-windows-86]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-preview.4/PowerShell-7.2.0-preview.4-win-x86.msi -[pv-ubuntu20]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-preview.4/powershell-preview_7.2.0-preview.4-1.ubuntu.20.04_amd64.deb -[pv-ubuntu18]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-preview.4/powershell-preview_7.2.0-preview.4-1.ubuntu.18.04_amd64.deb -[pv-ubuntu16]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-preview.4/powershell-preview_7.2.0-preview.4-1.ubuntu.16.04_amd64.deb -[pv-debian9]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-preview.4/powershell-preview_7.2.0-preview.4-1.debian.9_amd64.deb -[pv-debian10]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-preview.4/powershell-preview_7.2.0-preview.4-1.debian.10_amd64.deb -[pv-debian11]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-preview.4/powershell-preview_7.2.0-preview.4-1.debian.11_amd64.deb -[pv-centos]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-preview.4/powershell-preview-7.2.0_preview.4-1.rhel.7.x86_64.rpm -[pv-centos8]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-preview.4/powershell-preview-7.2.0_preview.4-1.centos.8.x86_64.rpm -[pv-macos]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-preview.4/powershell-7.2.0-preview.4-osx-x64.pkg -[pv-winarm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-preview.4/PowerShell-7.2.0-preview.4-win-arm64.zip -[pv-winx86-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-preview.4/PowerShell-7.2.0-preview.4-win-x86.zip -[pv-winx64-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-preview.4/PowerShell-7.2.0-preview.4-win-x64.zip -[pv-macos-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-preview.4/powershell-7.2.0-preview.4-osx-x64.tar.gz -[pv-linux-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-preview.4/powershell-7.2.0-preview.4-linux-x64.tar.gz -[pv-arm32]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-preview.4/powershell-7.2.0-preview.4-linux-arm32.tar.gz -[pv-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-preview.4/powershell-7.2.0-preview.4-linux-arm64.tar.gz -[pv-snap]: https://snapcraft.io/powershell-preview - -[in-windows]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-windows -[in-ubuntu16]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-linux#ubuntu-1604 -[in-ubuntu18]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-linux#ubuntu-1804 -[in-ubuntu20]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-linux#ubuntu-2004 -[in-deb9]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-linux#debian-9 -[in-deb10]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-linux#debian-10 -[in-centos]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-linux#centos-7 -[in-rhel7]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-linux#red-hat-enterprise-linux-rhel-7 -[in-opensuse]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-linux#opensuse -[in-fedora]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-linux#fedora -[in-archlinux]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-linux#arch-linux -[in-macos]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-macos -[in-docker]: https://github.com/PowerShell/PowerShell-Docker -[in-kali]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-linux#kali -[in-windows-zip]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-windows#zip -[in-tar-linux]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-linux#binary-archives -[in-tar-macos]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-macos#binary-archives -[in-raspbian]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-linux#raspbian -[in-arm]: https://docs.microsoft.com/powershell/scripting/install/powershell-core-on-arm -[corefx-win]:https://github.com/dotnet/core/blob/master/release-notes/3.0/3.0-supported-os.md#windows -[corefx-linux]:https://github.com/dotnet/core/blob/master/release-notes/3.0/3.0-supported-os.md#linux -[corefx-macos]:https://github.com/dotnet/core/blob/master/release-notes/3.0/3.0-supported-os.md#macos - -To install a specific version, visit [releases](https://github.com/PowerShell/PowerShell/releases). +PowerShell is supported on Windows, macOS, and a variety of Linux platforms. For +more information, see [Installing PowerShell](https://learn.microsoft.com/powershell/scripting/install/installing-powershell). + +## Upgrading PowerShell + +For best results when upgrading, you should use the same install method you used when you first +installed PowerShell. The update method is different for each platform and install method. ## Community Dashboard -[Dashboard](https://aka.ms/psgithubbi) with visualizations for community contributions and project status using PowerShell, Azure, and PowerBI. +[Dashboard](https://aka.ms/PSPublicDashboard) with visualizations for community contributions and project status using PowerShell, Azure, and PowerBI. For more information on how and why we built this dashboard, check out this [blog post](https://devblogs.microsoft.com/powershell/powershell-open-source-community-dashboard/). ## Discussions -[GitHub Discussions](https://docs.github.com/en/free-pro-team@latest/discussions/quickstart) is a feature to enable fluid and open discussions within the community +[GitHub Discussions](https://docs.github.com/discussions/quickstart) is a feature to enable free and open discussions within the community for topics that are not related to code, unlike issues. -This is an experiment we are trying in our repositories to see if it helps move discussions out of issues so that issues remain actionable by the team or members of the community. -There should be no expectation that PowerShell team members are regular participants in the discussions. +This is an experiment we are trying in our repositories, to see if it helps move discussions out of issues so that issues remain actionable by the team or members of the community. +There should be no expectation that PowerShell team members are regular participants in these discussions. Individual PowerShell team members may choose to participate in discussions, but the expectation is that community members help drive discussions so that team members can focus on issues. @@ -159,38 +54,18 @@ Create or join a [discussion](https://github.com/PowerShell/PowerShell/discussio Want to chat with other members of the PowerShell community? -We have a Gitter Room which you can join below. - -[![Join the chat](https://img.shields.io/static/v1.svg?label=chat&message=on%20gitter&color=informational&logo=gitter)](https://gitter.im/PowerShell/PowerShell?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -There is also the community-driven PowerShell Virtual User Group, which you can join on: +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) -* [Discord](https://aka.ms/psdiscord) - -## Add-ons and libraries - -[Awesome PowerShell](https://github.com/janikvonrotz/awesome-powershell) has a great curated list of add-ons and resources. - -## Building the Repository - -| Linux | Windows | macOS | -|--------------------------|----------------------------|------------------------| -| [Instructions][bd-linux] | [Instructions][bd-windows] | [Instructions][bd-macOS] | - -If you have any problems building, please consult the developer [FAQ][]. ### Build status of nightly builds -| Azure CI (Windows) | Azure CI (Linux) | Azure CI (macOS) | Code Coverage Status | CodeFactor Grade | -|:-----------------------------------------|:-----------------------------------------------|:-----------------------------------------------|:-------------------------|:-------------------------| -| [![windows-nightly-image][]][windows-nightly-site] | [![linux-nightly-image][]][linux-nightly-site] | [![macOS-nightly-image][]][macos-nightly-site] | [![cc-image][]][cc-site] | [![cf-image][]][cf-site] | - -[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 - -[FAQ]: https://github.com/PowerShell/PowerShell/tree/master/docs/FAQ.md +| Azure CI (Windows) | Azure CI (Linux) | Azure CI (macOS) | CodeFactor Grade | +|:-----------------------------------------|:-----------------------------------------------|:-----------------------------------------------|:-------------------------| +| [![windows-nightly-image][]][windows-nightly-site] | [![linux-nightly-image][]][linux-nightly-site] | [![macOS-nightly-image][]][macos-nightly-site] | [![cf-image][]][cf-site] | [windows-nightly-site]: https://powershell.visualstudio.com/PowerShell/_build?definitionId=32 [linux-nightly-site]: https://powershell.visualstudio.com/PowerShell/_build?definitionId=23 @@ -198,34 +73,46 @@ If you have any problems building, please consult the developer [FAQ][]. [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 -[cc-site]: https://codecov.io/gh/PowerShell/PowerShell -[cc-image]: https://codecov.io/gh/PowerShell/PowerShell/branch/master/graph/badge.svg [cf-site]: https://www.codefactor.io/repository/github/powershell/powershell [cf-image]: https://www.codefactor.io/repository/github/powershell/powershell/badge -## Downloading the Source Code +## Developing and Contributing -You can just clone the repository: +Want to contribute to PowerShell? Please start with the [Contribution Guide][] to learn how to develop and contribute. -```sh -git clone https://github.com/PowerShell/PowerShell.git -``` +If you are developing .NET Core C# applications targeting PowerShell Core, [check out our FAQ][] to learn more about the PowerShell SDK NuGet package. -See [working with the PowerShell repository](https://github.com/PowerShell/PowerShell/tree/master/docs/git) for more information. +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. -## Developing and Contributing +[Contribution Guide]: .github/CONTRIBUTING.md +[check out our FAQ]: docs/FAQ.md#where-do-i-get-the-powershell-core-sdk-package -Please see the [Contribution Guide][] for how to develop and contribute. -If you are developing .NET Core C# applications targeting PowerShell Core, please [check out our FAQ][] to learn more about the PowerShell SDK NuGet package. +## Building PowerShell -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. +| Linux | Windows | macOS | +|--------------------------|----------------------------|------------------------| +| [Instructions][bd-linux] | [Instructions][bd-windows] | [Instructions][bd-macOS] | + +If you have any problems building PowerShell, please start by consulting the developer [FAQ]. + +[bd-linux]: docs/building/linux.md +[bd-windows]: docs/building/windows-core.md +[bd-macOS]: docs/building/macos.md +[FAQ]: docs/FAQ.md -[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 +## Downloading the Source Code + +You can clone the repository: + +```sh +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). ## Support -For support, please see the [Support Section][]. +For support, see the [Support Section][]. [Support Section]: https://github.com/PowerShell/PowerShell/tree/master/.github/SUPPORT.md @@ -235,31 +122,30 @@ 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 -License: By requesting and using the Container OS Image for Windows containers, you acknowledge, understand, and consent to the Supplemental License Terms available on Docker Hub: +> [!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. -- [Windows Server Core](https://hub.docker.com/r/microsoft/windowsservercore/) -- [Nano Server](https://hub.docker.com/r/microsoft/nanoserver/) +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]. + +[mcr]: https://mcr.microsoft.com/en-us/product/powershell/tags ### Telemetry -By default, PowerShell collects the OS description and the version of PowerShell (equivalent to `$PSVersionTable.OS` and `$PSVersionTable.GitCommitId`) using [Application Insights](https://azure.microsoft.com/services/application-insights/). -To opt-out of sending telemetry, create an environment variable called `POWERSHELL_TELEMETRY_OPTOUT` set to a value of `1` before starting PowerShell from the installed location. -The telemetry we collect falls under the [Microsoft Privacy Statement](https://privacy.microsoft.com/privacystatement/). +Please visit our [about_Telemetry](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_telemetry) +topic to read details about telemetry gathered by PowerShell. ## Governance -The governance policy for the PowerShell project is described [here][]. +The governance policy for the PowerShell project is described the [PowerShell Governance][gov] document. + +[gov]: https://github.com/PowerShell/PowerShell/blob/master/docs/community/governance.md -[here]: https://github.com/PowerShell/PowerShell/blob/master/docs/community/governance.md +## [Code of Conduct](CODE_OF_CONDUCT.md) -## [Code of Conduct][conduct-md] +Please see our [Code of Conduct](CODE_OF_CONDUCT.md) before participating in this project. -This project has adopted the [Microsoft Open Source Code of Conduct][conduct-code]. -For more information see the [Code of Conduct FAQ][conduct-FAQ] or contact [opencode@microsoft.com][conduct-email] with any additional questions or comments. +## [Security Policy](.github/SECURITY.md) -[conduct-code]: https://opensource.microsoft.com/codeofconduct/ -[conduct-FAQ]: https://opensource.microsoft.com/codeofconduct/faq/ -[conduct-email]: mailto:opencode@microsoft.com -[conduct-md]: https://github.com/PowerShell/PowerShell/tree/master/CODE_OF_CONDUCT.md +For any security issues, please see our [Security Policy](.github/SECURITY.md). diff --git a/Settings.StyleCop b/Settings.StyleCop index 7fa179ee02e..e10c02bdd12 100644 --- a/Settings.StyleCop +++ b/Settings.StyleCop @@ -162,6 +162,7 @@ op my sb + vt diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 06747f655ca..28a06a82744 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -15,288 +15,1129 @@ USA Notwithstanding any other terms, you may reverse engineer this software to the extent required to debug changes to any libraries licensed under the GNU Lesser General Public License. +--------------------------------------------------------- -------------------------------------------------------------------- +Markdig.Signed 0.40.0 - BSD-2-Clause -Microsoft.CodeAnalysis.Common 3.3.1 - Apache-2.0 -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. -Apache License -Version 2.0, January 2004 +Copyright (c) . All rights reserved. -http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - 1. Definitions. + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - +--------------------------------------------------------- - "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. +--------------------------------------------------------- - +Humanizer.Core 2.14.1 - MIT - "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - +Copyright .NET Foundation and Contributors +Copyright (c) .NET Foundation and Contributors - "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. +MIT License - +Copyright (c) - "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/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. - "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - +--------------------------------------------------------- - "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). +--------------------------------------------------------- - +Json.More.Net 2.1.1 - MIT - "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - +Copyright (c) .NET Foundation and Contributors - "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." +MIT License - +Copyright (c) .NET Foundation and Contributors - "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. - 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. - 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and +--------------------------------------------------------- - (b) You must cause any modified files to carry prominent notices stating that You changed the files; and +--------------------------------------------------------- - (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +JsonPointer.Net 5.3.1 - MIT - (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. - You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. +Copyright (c) .NET Foundation and Contributors - 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. +MIT License - 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. +Copyright (c) .NET Foundation and Contributors - 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this 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: - 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. - 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND 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. -APPENDIX: How to apply the Apache License to your work. -To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. +--------------------------------------------------------- -Copyright [yyyy] [name of copyright owner] +--------------------------------------------------------- -Licensed under the Apache License, Version 2.0 (the "License"); +JsonSchema.Net 7.3.4 - MIT -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +Copyright (c) .NET Foundation and Contributors -http://www.apache.org/licenses/LICENSE-2.0 +MIT License -Unless required by applicable law or agreed to in writing, software +Copyright (c) .NET Foundation and Contributors -distributed under the License is distributed on an "AS IS" BASIS, +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -See the License for the specific language governing permissions and +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. -limitations under the License. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -Microsoft.CodeAnalysis.CSharp 3.3.1 - Apache-2.0 -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. -Copyright (c) Microsoft Corporation. -9Copyright (c) Microsoft Corporation. -ACopyright (c) Microsoft Corporation. -BCopyright (c) Microsoft Corporation. -CCopyright (c) Microsoft Corporation. -DCopyright (c) Microsoft Corporation. -OCopyright (c) Microsoft Corporation. -Copyright (c) Microsoft Corporation. Alle Rechte +Microsoft.ApplicationInsights 2.23.0 - MIT -Apache License -Version 2.0, January 2004 +(c) Microsoft Corporation -http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +MIT License - 1. Definitions. +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: - "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. +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. - "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. +--------------------------------------------------------- - +--------------------------------------------------------- - "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. +Microsoft.Bcl.AsyncInterfaces 9.0.3 - MIT - - "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - +The MIT License (MIT) - "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. +Copyright (c) .NET Foundation and Contributors - +All rights reserved. - "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/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. - "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. - - "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. +--------------------------------------------------------- - +--------------------------------------------------------- - "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." +Microsoft.CodeAnalysis.Common 4.13.0 - MIT - - "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors - 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. +MIT License - 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. +Copyright (c) - 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet 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: - (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - (b) You must cause any modified files to carry prominent notices stating that You changed the files; and +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +--------------------------------------------------------- - (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. +--------------------------------------------------------- - You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. +Microsoft.CodeAnalysis.CSharp 4.13.0 - MIT - 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. +(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 - 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. +MIT License - 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. +Copyright (c) - 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND 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: -APPENDIX: How to apply the Apache License to your work. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Copyright [yyyy] [name of copyright owner] +--------------------------------------------------------- -Licensed under the Apache License, Version 2.0 (the "License"); +--------------------------------------------------------- -you may not use this file except in compliance with the License. +Microsoft.Extensions.ObjectPool 9.0.3 - MIT -You may obtain a copy of the License at -http://www.apache.org/licenses/LICENSE-2.0 +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 +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 2013 - 2018 AngleSharp +Copyright (c) 2000-2013 Julian Seward +Copyright (c) 2011-2021 Twitter, Inc. +Copyright (c) 2014-2018 Michael Daines +Copyright (c) 1996-1998 John D. Polstra +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) .NET Foundation Contributors +Copyright (c) 2011-2021 The Bootstrap Authors +Copyright (c) 2019-2023 The Bootstrap Authors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2019-2020 West Wind Technologies +Copyright (c) 2007 John Birrell (jb@freebsd.org) +Copyright (c) 2011 Alex MacCaw (info@eribium.org) +Copyright (c) Nicolas Gallagher and Jonathan Neal +Copyright (c) 2010-2019 Google LLC. http://angular.io/license +Copyright (c) 2011 Nicolas Gallagher (nicolas@nicolasgallagher.com) +Copyright (c) 1989, 1993 The Regents of the University of California +Copyright (c) 1990, 1993 The Regents of the University of California +Copyright OpenJS Foundation and other contributors, https://openjsf.org +Copyright (c) Sindre Sorhus (https://sindresorhus.com) -Unless required by applicable law or agreed to in writing, software +MIT License -distributed under the License is distributed on an "AS IS" BASIS, +Copyright (c) -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -See the License for the specific language governing permissions and +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -limitations under the License. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -Markdig.Signed 0.17.1 - BSD-2-Clause -(c) 2008 VeriSign, Inc. +Microsoft.PowerShell.MarkdownRender 7.2.1 - MIT -Copyright (c) . All rights reserved. -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +(c) Microsoft Corporation +(c) Microsoft Corporation. PowerShell's Markdown Rendering project PowerShell Markdown Renderer + +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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Security.Extensions 1.4.0 - MIT + + +(c) Microsoft Corporation +Copyright (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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Win32.Registry.AccessControl 9.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Win32.SystemEvents 9.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Windows.Compatibility 9.0.3 - 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Newtonsoft.Json 13.0.3 - MIT + + +Copyright James Newton-King 2008 +Copyright (c) 2007 James Newton-King +Copyright (c) James Newton-King 2008 +Copyright James Newton-King 2008 Json.NET + +The MIT License (MIT) + +Copyright (c) 2007 James Newton-King + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/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. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.android-arm.runtime.native.System.IO.Ports 9.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.android-arm64.runtime.native.System.IO.Ports 9.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.android-x64.runtime.native.System.IO.Ports 9.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.android-x86.runtime.native.System.IO.Ports 9.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.linux-arm.runtime.native.System.IO.Ports 9.0.3 - MIT - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +The MIT License (MIT) -------------------------------------------------------------------- +Copyright (c) .NET Foundation and Contributors -------------------------------------------------------------------- +All rights reserved. -Microsoft.ApplicationInsights 2.11.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/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. -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/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. +--------------------------------------------------------- -------------------------------------------------------------------- +runtime.linux-arm64.runtime.native.System.IO.Ports 9.0.3 - MIT -------------------------------------------------------------------- -Microsoft.NETCore.Platforms 3.0.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -323,51 +1164,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- - -------------------------------------------------------------------- - -Microsoft.PowerShell.Native 7.0.0-preview.2 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) by P.J. Plauger - -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. +--------------------------------------------------------- -------------------------------------------------------------------- +runtime.linux-bionic-arm64.runtime.native.System.IO.Ports 9.0.3 - MIT -------------------------------------------------------------------- -Microsoft.Win32.Registry 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -394,32 +1254,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -Microsoft.Win32.Registry.AccessControl 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +runtime.linux-bionic-x64.runtime.native.System.IO.Ports 9.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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -446,32 +1344,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -Microsoft.Win32.SystemEvents 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +runtime.linux-musl-arm.runtime.native.System.IO.Ports 9.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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -498,32 +1434,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -Microsoft.Windows.Compatibility 3.0.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +runtime.linux-musl-arm64.runtime.native.System.IO.Ports 9.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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -550,101 +1524,160 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- - -------------------------------------------------------------------- - -Namotion.Reflection 1.0.7 - MIT -(c) 2008 VeriSign, Inc. - -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. +--------------------------------------------------------- -------------------------------------------------------------------- +runtime.linux-musl-x64.runtime.native.System.IO.Ports 9.0.3 - MIT -------------------------------------------------------------------- -Newtonsoft.Json 12.0.2 - MIT -(c) 2008 VeriSign, Inc. -Copyright James Newton-King 2008 +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) James Newton-King 2008 +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) -Copyright (c) 2007 James Newton-King +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: +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. - - -------------------------------------------------------------------- - -------------------------------------------------------------------- - -NJsonSchema 10.0.27 - MIT -(c) 2008 VeriSign, Inc. -Copyright Rico Suter, 2018 -Copyright (c) Rico Suter, 2018 -Copyright Rico Suter, 2018 4JSON Schema - -MIT License - -Copyright (c) +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION 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. +--------------------------------------------------------- -------------------------------------------------------------------- +runtime.linux-x64.runtime.native.System.IO.Ports 9.0.3 - MIT -------------------------------------------------------------------- -runtime.linux-arm.runtime.native.System.IO.Ports 4.6.0-rc2.19462.14 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -671,32 +1704,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.maccatalyst-arm64.runtime.native.System.IO.Ports 9.0.3 - MIT -------------------------------------------------------------------- -runtime.linux-arm64.runtime.native.System.IO.Ports 4.6.0-rc2.19462.14 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -723,32 +1794,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.maccatalyst-x64.runtime.native.System.IO.Ports 9.0.3 - MIT -------------------------------------------------------------------- -runtime.linux-x64.runtime.native.System.IO.Ports 4.6.0-rc2.19462.14 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -775,32 +1884,25 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -runtime.native.System.Data.SqlClient.sni 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. -Copyright (c) 2011, Google Inc. -(c) 1997-2005 Sean Eron Anderson. -Copyright (c) 2007 James Newton-King +runtime.native.System.Data.SqlClient.sni 4.4.0 - MIT + + +(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) @@ -827,32 +1929,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -runtime.native.System.IO.Ports 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +runtime.native.System.IO.Ports 9.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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -879,32 +2019,70 @@ 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 4.6.0-rc2.19462.14 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +runtime.osx-arm64.runtime.native.System.IO.Ports 9.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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -931,32 +2109,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -System.CodeDom 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +runtime.osx-x64.runtime.native.System.IO.Ports 9.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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -983,26 +2199,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -System.Collections.Immutable 1.5.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. +System.CodeDom 9.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. -(c) 1997-2005 Sean Eron Anderson. -Copyright (c) 1991-2017 Unicode, Inc. -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -1029,32 +2289,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- + +System.ComponentModel.Composition 9.0.3 - MIT -System.ComponentModel.Composition 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. + +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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -1081,32 +2379,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- + +System.ComponentModel.Composition.Registration 9.0.3 - MIT -System.ComponentModel.Composition.Registration 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. + +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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -1133,32 +2469,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- + +System.Configuration.ConfigurationManager 9.0.3 - MIT -System.Configuration.ConfigurationManager 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. + +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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -1185,11 +2559,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- + +System.Data.Odbc 9.0.3 - MIT -System.Data.DataSetExtensions 4.5.0 - MIT + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -1216,32 +2649,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- + +System.Data.OleDb 9.0.3 - MIT -System.Data.Odbc 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. + +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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -1268,32 +2739,89 @@ 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. -System.Data.OleDb 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Diagnostics.EventLog 9.0.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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -1320,33 +2848,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -System.Data.SqlClient 4.7.0 - MIT -2008 SQL Server 2012 -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +System.Diagnostics.PerformanceCounter 9.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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -1373,32 +2938,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -System.Diagnostics.EventLog 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +System.DirectoryServices 9.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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -1425,32 +3028,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -System.Diagnostics.PerformanceCounter 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +System.DirectoryServices.AccountManagement 9.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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -1477,32 +3118,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -System.DirectoryServices 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +System.DirectoryServices.Protocols 9.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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -1529,32 +3208,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -System.DirectoryServices.AccountManagement 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(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 +System.Drawing.Common 9.0.3 - MIT + + +(c) Microsoft Corporation +Copyright (c) Sven Groot (Ookii.org) 2009 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) @@ -1580,33 +3243,70 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- + +System.IO.Packaging 9.0.3 - MIT -------------------------------------------------------------------- -System.DirectoryServices.Protocols 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -1633,32 +3333,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -System.Drawing.Common 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +System.IO.Ports 9.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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -1685,32 +3423,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -System.IO.FileSystem.AccessControl 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +System.Management 9.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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -1737,32 +3513,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -System.IO.Packaging 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +System.Net.Http.WinHttpHandler 9.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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -1789,26 +3603,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -System.IO.Pipes.AccessControl 4.5.1 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) 2011, Google Inc. -(c) 1997-2005 Sean Eron Anderson. -Copyright (c) 1991-2017 Unicode, Inc. -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) .NET Foundation Contributors +System.Private.ServiceModel 4.10.3 - MIT + + +(c) Microsoft Corporation 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) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) The MIT License (MIT) @@ -1835,32 +3639,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- + +System.Reflection.Context 9.0.3 - MIT -System.IO.Ports 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. + +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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -1887,32 +3729,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- + +System.Runtime.Caching 9.0.3 - MIT -System.Management 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. + +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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -1939,25 +3819,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- + +System.Security.Cryptography.Pkcs 9.0.3 - MIT -System.Memory 4.5.3 - MIT -(c) 2008 VeriSign, Inc. + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation Copyright (c) 2011, Google Inc. -(c) 1997-2005 Sean Eron Anderson. -Copyright (c) 1991-2017 Unicode, Inc. -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -1984,32 +3909,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Security.Cryptography.ProtectedData 9.0.3 - MIT -------------------------------------------------------------------- -System.Net.Http.WinHttpHandler 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -2036,15 +3999,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Security.Cryptography.Xml 9.0.3 - MIT -------------------------------------------------------------------- -System.Private.ServiceModel 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -2071,32 +4089,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Security.Permissions 9.0.3 - MIT -------------------------------------------------------------------- -System.Reflection.Context 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -2123,11 +4179,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- + +--------------------------------------------------------- + +System.ServiceModel.Duplex 4.10.3 - MIT -------------------------------------------------------------------- -System.Reflection.DispatchProxy 4.5.0 - 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) @@ -2154,32 +4215,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -System.Reflection.Emit 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(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 +System.ServiceModel.Http 4.10.3 - MIT + + +(c) Microsoft Corporation 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) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) The MIT License (MIT) @@ -2206,32 +4251,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -System.Reflection.Emit.ILGeneration 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(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 +System.ServiceModel.NetTcp 4.10.3 - MIT + + +(c) Microsoft Corporation 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) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) The MIT License (MIT) @@ -2258,32 +4287,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -System.Reflection.Emit.Lightweight 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(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 +System.ServiceModel.Primitives 4.10.3 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) The MIT License (MIT) @@ -2310,11 +4323,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -System.Reflection.Metadata 1.6.0 - MIT +System.ServiceModel.Security 4.10.3 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) The MIT License (MIT) @@ -2341,32 +4359,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- + +--------------------------------------------------------- + +System.ServiceModel.Syndication 9.0.3 - MIT -------------------------------------------------------------------- -System.Runtime.Caching 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +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. -(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 1991-2022 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 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) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -2393,15 +4449,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- + +--------------------------------------------------------- + +System.ServiceProcess.ServiceController 9.0.3 - MIT -------------------------------------------------------------------- -System.ServiceModel.Duplex 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -2428,15 +4539,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Speech 9.0.3 - MIT -------------------------------------------------------------------- -System.ServiceModel.Http 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -2463,15 +4629,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Threading.AccessControl 9.0.3 - MIT -------------------------------------------------------------------- -System.ServiceModel.NetTcp 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -2498,15 +4719,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Web.Services.Description 8.1.1 - MIT -------------------------------------------------------------------- -System.ServiceModel.Primitives 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. +(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) @@ -2533,15 +4755,70 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- + +System.Windows.Extensions 9.0.3 - MIT -System.ServiceModel.Security 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass The MIT License (MIT) @@ -2568,38 +4845,22 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--------------------------------------------------------- + + ------------------------------------------------------------------- ------------------------------------------------------------------- -System.Threading.AccessControl 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(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 +Additional - -The MIT License (MIT) +------------------------------------------------- +Microsoft.PowerShell.Archive +------------------------------------------------- -Copyright (c) .NET Foundation and Contributors +Copyright (c) 2016 Microsoft Corporation. -All rights reserved. +The MIT License (MIT) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -2608,47 +4869,75 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/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. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +------------------------------------------------- +Microsoft.Management.Infrastructure.Runtime.Unix +Microsoft.Management.Infrastructure +------------------------------------------------- -------------------------------------------------------------------- +Copyright (c) Microsoft Corporation -------------------------------------------------------------------- +All rights reserved. -System.Threading.Tasks.Extensions 4.5.3 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) 2011, Google Inc. -(c) 1997-2005 Sean Eron Anderson. -Copyright (c) 1991-2017 Unicode, Inc. -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. -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 THIS WORK IS PROVIDED AS +MIT License -The MIT License (MIT) +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/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) .NET Foundation and Contributors +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. + +-------------------------------------------------------- +• NuGet.Common +• NuGet.Configuration +• NuGet.DependencyResolver.Core +• NuGet.Frameworks +• NuGet.LibraryModel +• NuGet.Packaging +• NuGet.Packaging.Core +• NuGet.Packaging.Core.Types +• NuGet.ProjectModel +• NuGet.Protocol.Core.Types +• NuGet.Protocol.Core.v3 +• NuGet.Repositories +• NuGet.RuntimeModel +• NuGet.Versioning +---------------------------------------------------------- + +Copyright (c) .NET Foundation. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +these files except in compliance with the License. You may obtain a copy of the +License at + +https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +------------------------------------------------- +PackageManagement +------------------------------------------------- +Copyright (c) Microsoft Corporation 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 +of this software and associated documentation files (the Software), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is @@ -2657,7 +4946,7 @@ 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 +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER @@ -2665,40 +4954,16 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------- +PowerShellGet +------------------------------------------------- -------------------------------------------------------------------- - -------------------------------------------------------------------- +Copyright (c) Microsoft Corporation -System.Windows.Extensions 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(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 +All rights reserved. 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 @@ -2717,18 +4982,11 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -------------------------------------------------------------------- - -------------------------------------------------------------------- - -Additional - - --------------------------------------------- File: PSReadLine --------------------------------------------- -https://github.com/lzybkr/PSReadLine +https://github.com/PowerShell/PSReadLine Copyright (c) 2013, Jason Shirk @@ -2756,35 +5014,16 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ----------------------------------------------- -File: Hashtables from ConvertFrom-json ----------------------------------------------- - -https://stackoverflow.com/questions/22002748/hashtables-from-convertfrom-json-have-different-type-from-powershells-built-in-h - -Copyright (c) 2015 Dave Wyatt. All rights reserved. - -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: - -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. - ------------------------------------------------- -PackageManagement +ThreadJob ------------------------------------------------- -Copyright (c) Microsoft Corporation -All rights reserved. +Copyright (c) 2018 Paul Higinbotham 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 +of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is @@ -2793,7 +5032,7 @@ 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 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER @@ -2801,34 +5040,3 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -• NuGet.Common -• NuGet.Configuration -• NuGet.DependencyResolver.Core -• NuGet.Frameworks -• NuGet.LibraryModel -• NuGet.Packaging -• NuGet.Packaging.Core -• NuGet.Packaging.Core.Types -• NuGet.ProjectModel -• NuGet.Protocol.Core.Types -• NuGet.Protocol.Core.v3 -• NuGet.Repositories -• NuGet.RuntimeModel -• NuGet.Versioning ----------------------------------------------------------- - -Copyright (c) .NET Foundation. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -these files except in compliance with the License. You may obtain a copy of the -License at - -https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed -under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -CONDITIONS OF ANY KIND, either express or implied. See the License for the -specific language governing permissions and limitations under the License. - -------------------------------------------------------------------- diff --git a/assets/AppImageThirdPartyNotices.txt b/assets/AppImageThirdPartyNotices.txt deleted file mode 100644 index d492e7c3b53..00000000000 --- a/assets/AppImageThirdPartyNotices.txt +++ /dev/null @@ -1,506 +0,0 @@ -------------------------------------------- START OF THIRD PARTY NOTICE ----------------------------------------- - - This file is based on or incorporates material from the projects listed below (Third Party IP). The original copyright notice and the license under which Microsoft received such Third Party IP, are set forth below. Such licenses and notices are provided for informational purposes only. Microsoft licenses the Third Party IP to you under the licensing terms for the Microsoft product. Microsoft reserves all other rights not expressly granted under this agreement, whether by implication, estoppel or otherwise. - - - - -Copyright (c) 1991-2016 Unicode, Inc. All rights reserved. -Distributed under the Terms of Use in http://www.unicode.org/copyright.html - -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Unicode data files and any associated documentation -(the "Data Files") or Unicode software and any associated documentation -(the "Software") to deal in the Data Files or Software -without restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, and/or sell copies of -the Data Files or Software, and to permit persons to whom the Data Files -or Software are furnished to do so, provided that either -(a) this copyright and permission notice appear with all copies -of the Data Files or Software, or -(b) this copyright and permission notice appear in associated -Documentation. - -THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT OF THIRD PARTY RIGHTS. -IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS -NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL -DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, -DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THE DATA FILES OR SOFTWARE. - -Except as contained in this notice, the name of a copyright holder -shall not be used in advertising or otherwise to promote the sale, -use or other dealings in these Data Files or Software without prior -written authorization of the copyright holder. - ---------------------- - -Third-Party Software Licenses - -This section contains third-party software notices and/or additional -terms for licensed third-party software components included within ICU -libraries. - -1. ICU License - ICU 1.8.1 to ICU 57.1 - -COPYRIGHT AND PERMISSION NOTICE - -Copyright (c) 1995-2016 International Business Machines Corporation and others -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, and/or sell copies of the Software, and to permit persons -to whom the Software is furnished to do so, provided that the above -copyright notice(s) and this permission notice appear in all copies of -the Software and that both the above copyright notice(s) and this -permission notice appear in supporting documentation. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR -HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY -SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER -RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF -CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -Except as contained in this notice, the name of a copyright holder -shall not be used in advertising or otherwise to promote the sale, use -or other dealings in this Software without prior written authorization -of the copyright holder. - -All trademarks and registered trademarks mentioned herein are the -property of their respective owners. - -2. Chinese/Japanese Word Break Dictionary Data (cjdict.txt) - - # The Google Chrome software developed by Google is licensed under - # the BSD license. Other software included in this distribution is - # provided under other licenses, as set forth below. - # - # The BSD License - # https://opensource.org/licenses/bsd-license.php - # Copyright (C) 2006-2008, Google Inc. - # - # All rights reserved. - # - # Redistribution and use in source and binary forms, with or without - # modification, are permitted provided that the following conditions are met: - # - # Redistributions of source code must retain the above copyright notice, - # this list of conditions and the following disclaimer. - # Redistributions in binary form must reproduce the above - # copyright notice, this list of conditions and the following - # disclaimer in the documentation and/or other materials provided with - # the distribution. - # Neither the name of Google Inc. nor the names of its - # contributors may be used to endorse or promote products derived from - # this software without specific prior written permission. - # - # - # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR - # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - # - # - # The word list in cjdict.txt are generated by combining three word lists - # listed below with further processing for compound word breaking. The - # frequency is generated with an iterative training against Google web - # corpora. - # - # * Libtabe (Chinese) - # - https://sourceforge.net/project/?group_id=1519 - # - Its license terms and conditions are shown below. - # - # * IPADIC (Japanese) - # - http://chasen.aist-nara.ac.jp/chasen/distribution.html - # - Its license terms and conditions are shown below. - # - # ---------COPYING.libtabe ---- BEGIN-------------------- - # - # /* - # * Copyrighy (c) 1999 TaBE Project. - # * Copyright (c) 1999 Pai-Hsiang Hsiao. - # * All rights reserved. - # * - # * Redistribution and use in source and binary forms, with or without - # * modification, are permitted provided that the following conditions - # * are met: - # * - # * . Redistributions of source code must retain the above copyright - # * notice, this list of conditions and the following disclaimer. - # * . Redistributions in binary form must reproduce the above copyright - # * notice, this list of conditions and the following disclaimer in - # * the documentation and/or other materials provided with the - # * distribution. - # * . Neither the name of the TaBE Project nor the names of its - # * contributors may be used to endorse or promote products derived - # * from this software without specific prior written permission. - # * - # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - # * OF THE POSSIBILITY OF SUCH DAMAGE. - # */ - # - # /* - # * Copyright (c) 1999 Computer Systems and Communication Lab, - # * Institute of Information Science, Academia - # * Sinica. All rights reserved. - # * - # * Redistribution and use in source and binary forms, with or without - # * modification, are permitted provided that the following conditions - # * are met: - # * - # * . Redistributions of source code must retain the above copyright - # * notice, this list of conditions and the following disclaimer. - # * . Redistributions in binary form must reproduce the above copyright - # * notice, this list of conditions and the following disclaimer in - # * the documentation and/or other materials provided with the - # * distribution. - # * . Neither the name of the Computer Systems and Communication Lab - # * nor the names of its contributors may be used to endorse or - # * promote products derived from this software without specific - # * prior written permission. - # * - # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - # * OF THE POSSIBILITY OF SUCH DAMAGE. - # */ - # - # Copyright 1996 Chih-Hao Tsai @ Beckman Institute, - # University of Illinois - # c-tsai4@uiuc.edu http://casper.beckman.uiuc.edu/~c-tsai4 - # - # ---------------COPYING.libtabe-----END-------------------------------- - # - # - # ---------------COPYING.ipadic-----BEGIN------------------------------- - # - # Copyright 2000, 2001, 2002, 2003 Nara Institute of Science - # and Technology. All Rights Reserved. - # - # Use, reproduction, and distribution of this software is permitted. - # Any copy of this software, whether in its original form or modified, - # must include both the above copyright notice and the following - # paragraphs. - # - # Nara Institute of Science and Technology (NAIST), - # the copyright holders, disclaims all warranties with regard to this - # software, including all implied warranties of merchantability and - # fitness, in no event shall NAIST be liable for - # any special, indirect or consequential damages or any damages - # whatsoever resulting from loss of use, data or profits, whether in an - # action of contract, negligence or other tortuous action, arising out - # of or in connection with the use or performance of this software. - # - # A large portion of the dictionary entries - # originate from ICOT Free Software. The following conditions for ICOT - # Free Software applies to the current dictionary as well. - # - # Each User may also freely distribute the Program, whether in its - # original form or modified, to any third party or parties, PROVIDED - # that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear - # on, or be attached to, the Program, which is distributed substantially - # in the same form as set out herein and that such intended - # distribution, if actually made, will neither violate or otherwise - # contravene any of the laws and regulations of the countries having - # jurisdiction over the User or the intended distribution itself. - # - # NO WARRANTY - # - # The program was produced on an experimental basis in the course of the - # research and development conducted during the project and is provided - # to users as so produced on an experimental basis. Accordingly, the - # program is provided without any warranty whatsoever, whether express, - # implied, statutory or otherwise. The term "warranty" used herein - # includes, but is not limited to, any warranty of the quality, - # performance, merchantability and fitness for a particular purpose of - # the program and the nonexistence of any infringement or violation of - # any right of any third party. - # - # Each user of the program will agree and understand, and be deemed to - # have agreed and understood, that there is no warranty whatsoever for - # the program and, accordingly, the entire risk arising from or - # otherwise connected with the program is assumed by the user. - # - # Therefore, neither ICOT, the copyright holder, or any other - # organization that participated in or was otherwise related to the - # development of the program and their respective officials, directors, - # officers and other employees shall be held liable for any and all - # damages, including, without limitation, general, special, incidental - # and consequential damages, arising out of or otherwise in connection - # with the use or inability to use the program or any product, material - # or result produced or otherwise obtained by using the program, - # regardless of whether they have been advised of, or otherwise had - # knowledge of, the possibility of such damages at any time during the - # project or thereafter. Each user will be deemed to have agreed to the - # foregoing by his or her commencement of use of the program. The term - # "use" as used herein includes, but is not limited to, the use, - # modification, copying and distribution of the program and the - # production of secondary products from the program. - # - # In the case where the program, whether in its original form or - # modified, was distributed or delivered to or received by a user from - # any person, organization or entity other than ICOT, unless it makes or - # grants independently of ICOT any specific warranty to the user in - # writing, such person, organization or entity, will also be exempted - # from and not be held liable to the user for any such damages as noted - # above as far as the program is concerned. - # - # ---------------COPYING.ipadic-----END---------------------------------- - -3. Lao Word Break Dictionary Data (laodict.txt) - - # Copyright (c) 2013 International Business Machines Corporation - # and others. All Rights Reserved. - # - # Project: https://code.google.com/p/lao-dictionary/ - # Dictionary: http://lao-dictionary.googlecode.com/git/Lao-Dictionary.txt - # License: http://lao-dictionary.googlecode.com/git/Lao-Dictionary-LICENSE.txt - # (copied below) - # - # This file is derived from the above dictionary, with slight - # modifications. - # ---------------------------------------------------------------------- - # Copyright (C) 2013 Brian Eugene Wilson, Robert Martin Campbell. - # All rights reserved. - # - # Redistribution and use in source and binary forms, with or without - # modification, - # are permitted provided that the following conditions are met: - # - # - # Redistributions of source code must retain the above copyright notice, this - # list of conditions and the following disclaimer. Redistributions in - # binary form must reproduce the above copyright notice, this list of - # conditions and the following disclaimer in the documentation and/or - # other materials provided with the distribution. - # - # - # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - # OF THE POSSIBILITY OF SUCH DAMAGE. - # -------------------------------------------------------------------------- - -4. Burmese Word Break Dictionary Data (burmesedict.txt) - - # Copyright (c) 2014 International Business Machines Corporation - # and others. All Rights Reserved. - # - # This list is part of a project hosted at: - # github.com/kanyawtech/myanmar-karen-word-lists - # - # -------------------------------------------------------------------------- - # Copyright (c) 2013, LeRoy Benjamin Sharon - # All rights reserved. - # - # Redistribution and use in source and binary forms, with or without - # modification, are permitted provided that the following conditions - # are met: Redistributions of source code must retain the above - # copyright notice, this list of conditions and the following - # disclaimer. Redistributions in binary form must reproduce the - # above copyright notice, this list of conditions and the following - # disclaimer in the documentation and/or other materials provided - # with the distribution. - # - # Neither the name Myanmar Karen Word Lists, nor the names of its - # contributors may be used to endorse or promote products derived - # from this software without specific prior written permission. - # - # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS - # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - # SUCH DAMAGE. - # -------------------------------------------------------------------------- - -5. Time Zone Database - - ICU uses the public domain data and code derived from Time Zone -Database for its time zone support. The ownership of the TZ database -is explained in BCP 175: Procedure for Maintaining the Time Zone -Database section 7. - - # 7. Database Ownership - # - # The TZ database itself is not an IETF Contribution or an IETF - # document. Rather it is a pre-existing and regularly updated work - # that is in the public domain, and is intended to remain in the - # public domain. Therefore, BCPs 78 [RFC5378] and 79 [RFC3979] do - # not apply to the TZ Database or contributions that individuals make - # to it. Should any claims be made and substantiated against the TZ - # Database, the organization that is providing the IANA - # Considerations defined in this RFC, under the memorandum of - # understanding with the IETF, currently ICANN, may act in accordance - # with all competent court orders. No ownership claims will be made - # by ICANN or the IETF Trust on the database or the code. Any person - # making a contribution to the database or code waives all rights to - # future claims in that contribution or in the TZ Database. - - -8. liblzma - -XZ Utils Licensing -================== - - Different licenses apply to different files in this package. Here - is a rough summary of which licenses apply to which parts of this - package (but check the individual files to be sure!): - - - liblzma is in the public domain. - - - xz, xzdec, and lzmadec command line tools are in the public - domain unless GNU getopt_long had to be compiled and linked - in from the lib directory. The getopt_long code is under - GNU LGPLv2.1+. - - - The scripts to grep, diff, and view compressed files have been - adapted from gzip. These scripts and their documentation are - under GNU GPLv2+. - - - All the documentation in the doc directory and most of the - XZ Utils specific documentation files in other directories - are in the public domain. - - - Translated messages are in the public domain. - - - The build system contains public domain files, and files that - are under GNU GPLv2+ or GNU GPLv3+. None of these files end up - in the binaries being built. - - - Test files and test code in the tests directory, and debugging - utilities in the debug directory are in the public domain. - - - The extra directory may contain public domain files, and files - that are under various free software licenses. - - You can do whatever you want with the files that have been put into - the public domain. If you find public domain legally problematic, - take the previous sentence as a license grant. If you still find - the lack of copyright legally problematic, you have too many - lawyers. - - As usual, this software is provided "as is", without any warranty. - - If you copy significant amounts of public domain code from XZ Utils - into your project, acknowledging this somewhere in your software is - polite (especially if it is proprietary, non-free software), but - naturally it is not legally required. Here is an example of a good - notice to put into "about box" or into documentation: - - This software includes code from XZ Utils . - - The following license texts are included in the following files: - - COPYING.LGPLv2.1: GNU Lesser General Public License version 2.1 - - COPYING.GPLv2: GNU General Public License version 2 - - COPYING.GPLv3: GNU General Public License version 3 - - Note that the toolchain (compiler, linker etc.) may add some code - pieces that are copyrighted. Thus, it is possible that e.g. liblzma - binary wouldn't actually be in the public domain in its entirety - even though it contains no copyrighted code from the XZ Utils source - package. - - If you have questions, don't hesitate to ask the author(s) for more - information. - - -BSD License - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ""AS IS"" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -9. libunwind - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/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. - -Provided for Informational Purposes Only - -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: - -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. - - - - ------------------------------------------------ END OF THIRD PARTY NOTICE ------------------------------------------ diff --git a/assets/AppxManifest.xml b/assets/AppxManifest.xml index 83df8c31b41..50a8c7af45d 100644 --- a/assets/AppxManifest.xml +++ b/assets/AppxManifest.xml @@ -20,7 +20,7 @@ - + @@ -45,5 +45,8 @@ + + + diff --git a/assets/MicrosoftUpdate/RegisterMicrosoftUpdate.ps1 b/assets/MicrosoftUpdate/RegisterMicrosoftUpdate.ps1 new file mode 100644 index 00000000000..db756063da4 --- /dev/null +++ b/assets/MicrosoftUpdate/RegisterMicrosoftUpdate.ps1 @@ -0,0 +1,70 @@ +# 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/additionalAttributions.txt b/assets/additionalAttributions.txt index d244bad6877..6676ca99cf5 100644 --- a/assets/additionalAttributions.txt +++ b/assets/additionalAttributions.txt @@ -1,46 +1,42 @@ -## Used to generate a new TPN -## Copy this into the additional attributions fields -## Copy everything below here, but do not include this line ---------------------------------------------- -File: PSReadLine ---------------------------------------------- +------------------------------------------------------------------- -https://github.com/lzybkr/PSReadLine +------------------------------------------------------------------- -Copyright (c) 2013, Jason Shirk +Additional - -All rights reserved. +------------------------------------------------- +Microsoft.PowerShell.Archive +------------------------------------------------- -BSD License +Copyright (c) 2016 Microsoft Corporation. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +The MIT License (MIT) -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. ----------------------------------------------- -File: Hashtables from ConvertFrom-json ----------------------------------------------- +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. -https://stackoverflow.com/questions/22002748/hashtables-from-convertfrom-json-have-different-type-from-powershells-built-in-h +------------------------------------------------- +Microsoft.Management.Infrastructure.Runtime.Unix +Microsoft.Management.Infrastructure +------------------------------------------------- -Copyright (c) 2015 Dave Wyatt. All rights reserved. +Copyright (c) Microsoft Corporation All rights reserved. @@ -52,6 +48,36 @@ The above copyright notice and this permission notice shall be included in all 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. +-------------------------------------------------------- +• NuGet.Common +• NuGet.Configuration +• NuGet.DependencyResolver.Core +• NuGet.Frameworks +• NuGet.LibraryModel +• NuGet.Packaging +• NuGet.Packaging.Core +• NuGet.Packaging.Core.Types +• NuGet.ProjectModel +• NuGet.Protocol.Core.Types +• NuGet.Protocol.Core.v3 +• NuGet.Repositories +• NuGet.RuntimeModel +• NuGet.Versioning +---------------------------------------------------------- + +Copyright (c) .NET Foundation. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +these files except in compliance with the License. You may obtain a copy of the +License at + +https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + ------------------------------------------------- PackageManagement ------------------------------------------------- @@ -79,32 +105,88 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -• NuGet.Common -• NuGet.Configuration -• NuGet.DependencyResolver.Core -• NuGet.Frameworks -• NuGet.LibraryModel -• NuGet.Packaging -• NuGet.Packaging.Core -• NuGet.Packaging.Core.Types -• NuGet.ProjectModel -• NuGet.Protocol.Core.Types -• NuGet.Protocol.Core.v3 -• NuGet.Repositories -• NuGet.RuntimeModel -• NuGet.Versioning ----------------------------------------------------------- +------------------------------------------------- +PowerShellGet +------------------------------------------------- -Copyright (c) .NET Foundation. All rights reserved. +Copyright (c) Microsoft Corporation -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -these files except in compliance with the License. You may obtain a copy of the -License at +All rights reserved. -https://www.apache.org/licenses/LICENSE-2.0 +The MIT License (MIT) -Unless required by applicable law or agreed to in writing, software distributed -under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -CONDITIONS OF ANY KIND, either express or implied. See the License for the -specific language governing permissions and limitations under the 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: + +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. + +--------------------------------------------- +File: PSReadLine +--------------------------------------------- + +https://github.com/PowerShell/PSReadLine + +Copyright (c) 2013, Jason Shirk + +All rights reserved. + +BSD License + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------- +ThreadJob +------------------------------------------------- + +Copyright (c) 2018 Paul Higinbotham + +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: + +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/manpage/pwsh.1 b/assets/manpage/pwsh.1 new file mode 100644 index 00000000000..14c191241a9 --- /dev/null +++ b/assets/manpage/pwsh.1 @@ -0,0 +1,10 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "PWSH" "1" "October 2023" "" "" +. +.SH "NAME" +\fBpwsh\fR \- PowerShell command\-line shell and \.NET REPL +. +.SH "SYNOPSIS" +\fBpwsh\fR [\fB\-Login\fR] [ [\fB\-File\fR] \fIfilePath\fR [args] ] [\fB\-Command\fR { \- | \fIscript\-block\fR [\fB\-args\fR \fIarg\-array\fR] | \fIstring\fR [\fICommandParameters\fR] } ] [\fB\-ConfigurationFile\fR \fIfilePath\fR] [\fB\-ConfigurationName\fR \fIstring\fR] [\fB\-CustomPipeName\fR \fIstring\fR] [\fB\-EncodedArguments\fR \fIBase64EncodedArguments\fR] [\fB\-EncodedCommand\fR \fIBase64EncodedCommand\fR] [\fB\-ExecutionPolicy\fR \fIExecutionPolicy\fR] [\fB\-Help\fR] [\fB\-InputFormat\fR {Text | XML}] [\fB\-Interactive\fR] [\fB\-MTA\fR] [\fB\-NoExit\fR] [\fB\-NoLogo\fR] [\fB\-NonInteractive\fR] [\fB\-NoProfile\fR] [\fB\-NoProfileLoadTime\fR] [\fB\-OutputFormat\fR {Text | XML}] [\fB\-SettingsFile\fR \fIfilePath\fR] [\fB\-SSHServerMode\fR] [\fB\-STA\fR] [\fB\-Version\fR] [\fB\-WindowStyle\fR diff --git a/assets/manpage/pwsh.1.ronn b/assets/manpage/pwsh.1.ronn new file mode 100644 index 00000000000..98320cc60c8 --- /dev/null +++ b/assets/manpage/pwsh.1.ronn @@ -0,0 +1,230 @@ +pwsh(1) -- PowerShell command-line shell and .NET REPL +================================================= + +## SYNOPSIS + +`pwsh` [`-Login`] [ [`-File`] [args] ] +[`-Command` { - | [`-args` ] | +[] } ] [`-ConfigurationFile` ] +[`-ConfigurationName` ] [`-CustomPipeName` ] +[`-EncodedArguments` ] +[`-EncodedCommand` ] +[`-ExecutionPolicy` ] [`-Help`] [`-InputFormat` {Text | XML}] +[`-Interactive`] [`-MTA`] [`-NoExit`] [`-NoLogo`] [`-NonInteractive`] +[`-NoProfile`] [`-NoProfileLoadTime`] [`-OutputFormat` {Text | XML}] +[`-SettingsFile` ] [`-SSHServerMode`] [`-STA`] [`-Version`] +[`-WindowStyle` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Page-1 + + + + + Start/End + User has idea! + + + + + + + + + + + + + + + + + + + User has idea! + + Dynamic connector + + + + Process + User files issue with rationale and use cases + + + + + + + + + + + + + + + + + + + User files issue with rationale and use cases + + Dynamic connector.9 + + + + Process.8 + Maintainers label issue with Area Label + + + + + + + + + + + + + + + + + + + Maintainers label issue with Area Label + + Dynamic connector.13 + No + + + + + No + + Process.12 + Users discuss idea “exhaustively” (TBD by WGs) + + + + + + + + + + + + + + + + + + + Users discuss idea exhaustively” (TBD by WGs) + + Dynamic connector.15 + + + + Decision + Do the WGs think the idea has potential / is worth pursuing? + + + + + + + + + + + + + + + + + + + Do the WGs think the idea has potential / is worth pursuing? + + Dynamic connector.23 + No + + + + + No + + Subprocess + Committee appeals process (TBD) + + + + + + + + + + + + + + + + + + + + + + + Committee appeals process (TBD) + + Dynamic connector.27 + Appeal unsuccessful + + + + + Appeal unsuccessful + + Start/End.26 + Issue is closed, idea not pursued within PS repo + + + + + + + + + + + + + + + + + + + + Issue is closed, idea not pursued within PS repo + + Dynamic connector.30 + Successful appeal + + + + + Successful appeal + + Process.32 + Implement and release the implementation outside of PS + + + + + + + + + + + + + + + + + + + Implement and release the implementation outside of PS + + Process.43 + WGs label “RFC required”; Both paths are (eventually) require... + + + + + + + + + + + + + + + + + + + + WGs label “RFC required”; Both paths are (eventually) required; author(s) can choose to do in any order + + Dynamic connector.46 + + + + Process.45 + Contributor writes and publishes RFC as draft PR in PowerShel... + + + + + + + + + + + + + + + + + + + + Contributor writes and publishes RFC as draft PR in PowerShell-RFC(add reference in original issue) + + Dynamic connector.48 + + + + Process.47 + Contributor publishes PR with WIP and/or prototype code as dr... + + + + + + + + + + + + + + + + + + + + Contributor publishes PR with WIP and/or prototype code as draft PR in PowerShell repo(add reference in original issue, Maintainers add `Proposal` label) + + Dynamic connector.50 + + + + Process.49 + WGs, contributors, and any others have discussion about RFC f... + + + + + + + + + + + + + + + + + + + WGs, contributors, and any others have discussion about RFC for >= 2 months + + Dynamic connector.52 + + + + Process.51 + Author marks RFC PR as non-draft + + + + + + + + + + + + + + + + + + + Author marks RFC PR as non-draft + + Dynamic connector.56 + + + + Decision.55 + Does the code PR meet WG and Maintainer standards? + + + + + + + + + + + + + + + + + + + + Does the code PR meet WG and Maintainer standards? + + Decision.65 + Is the RFC accepted and matching the implemen-tation? + + + + + + + + + + + + + + + + + + + + Is the RFC accepted and matching the implemen-tation? + + Dynamic connector.67 + No + + + + + No + + Dynamic connector.69 + + + + Start/End.68 + Merge as non-experimental code + + + + + + + + + + + + + + + + + + + Merge as non-experimental code + + Dynamic connector.71 + + + + Process.70 + Committee review + + + + + + + + + + + + + + + + + + + Committee review + + Decision.75 + Does the RFC contain all necessary info to merge as experimen... + + + + + + + + + + + + + + + + + + + + Does the RFC contain all necessary info to merge as experimental? + + Decision.77 + Committee votes to approve RFC + + + + + + + + + + + + + + + + + + + Committee votes to approve RFC + + Dynamic connector.79 + Reject + + + + + Reject + + Process.80 + RFC author adds additional info + + + + + + + + + + + + + + + + + + + RFC author adds additional info + + Dynamic connector.87 + Approve + + + + + Approve + + Decision.86 + Is the code ready to go? + + + + + + + + + + + + + + + + + + + Is the code ready to go? + + Dynamic connector.88 + Yes + + + + + Yes + + Dynamic connector.90 + No + + + + + No + + Decision.36 + Do you still think it needs to be in the PS package? + + + + + + + + + + + + + + + + + + + + Do you still think it needs to be in the PS package? + + Dynamic connector.1012 + Yes + + + + + Yes + + Decision.1006 + Can the idea be implemented outside of the PS code repo? + + + + + + + + + + + + + + + + + + + + Can the idea be implemented outside of the PS code repo? + + Dynamic connector.1007 + + + + Dynamic connector.1008 + Yes + + + + + Yes + + Dynamic connector.1001 + + + + Dynamic connector.1014 + + + + Decision.1015 + Do the WGs think an RFC is required? + + + + + + + + + + + + + + + + + + + Do the WGs think an RFC is required? + + Dynamic connector.1016 + Yes + + + + + Yes + + Dynamic connector.1017 + Yes + + + + + Yes + + Dynamic connector.1019 + No + + + + + No + + Process.1018 + WGs label “RFC not required”; Contributor opens a PR to be re... + + + + + + + + + + + + + + + + + + + + WGs label “RFC not required”; Contributor opens a PR to be reviewed by WGs and merged by maintainers + + Dynamic connector.1020 + Yes + + + + + Yes + + Process.1021 + Committee labels RFC PR with “Experimental - Approved” + + + + + + + + + + + + + + + + + + + Committee labels RFC PR with Experimental - Approved + + Dynamic connector.1023 + No + + + + + No + + Dynamic connector.1024 + + + + Dynamic connector.1026 + Yes + + + + + Yes + + Decision.1025 + Has the RFC been marked with “Experimental - Approved”? + + + + + + + + + + + + + + + + + + + + Has the RFC been marked with Experimental - Approved”? + + Dynamic connector.1028 + Yes + + + + + Yes + + Process.1027 + Merge code PR as Experimental Feature + + + + + + + + + + + + + + + + + + + Merge code PR as Experimental Feature + + Dynamic connector.1029 + No + + + + + No + + Dynamic connector.1031 + No + + + + + No + + Process.1030 + Contributor updates code PR based on feedback + + + + + + + + + + + + + + + + + + + Contributor updates code PR based on feedback + + Dynamic connector.1033 + + + + Decision.1032 + Has the code PR been merged as experimental? + + + + + + + + + + + + + + + + + + + + Has the code PR been merged as experimental? + + Dynamic connector.1034 + No + + + + + No + + Dynamic connector.1036 + Yes + + + + + Yes + + Decision.1035 + Does the Committee believe the experimental feature has had e... + + + + + + + + + + + + + + + + + + + + Does the Committee believe the experimental feature has had enough time to bake? + + Dynamic connector.1037 + Yes + + + + + Yes + + Dynamic connector.1038 + + + + Dynamic connector.1040 + + + + Decision.1039 + Does the Committee think the intent of the RFC is reasonable ... + + + + + + + + + + + + + + + + + + + + Does the Committee think the intent of the RFC is reasonable to pursue + + Dynamic connector.1041 + Yes + + + + + Yes + + Dynamic connector.1043 + No + + + + + No + + Start/End.1042 + Process stops + + + + + + + + + + + + + + + + + + + Process stops + + diff --git a/docs/community/process_diagram.vsdx b/docs/community/process_diagram.vsdx new file mode 100644 index 00000000000..014c28fa43d Binary files /dev/null and b/docs/community/process_diagram.vsdx differ diff --git a/docs/community/working-group-definitions.md b/docs/community/working-group-definitions.md new file mode 100644 index 00000000000..277fc37f789 --- /dev/null +++ b/docs/community/working-group-definitions.md @@ -0,0 +1,199 @@ +# Working Group Definitions + +This document maintains a list of the current PowerShell [Working Groups (WG)](working-group.md), +as well as their definitions, membership, and a non-exhaustive set of examples of topics that fall +within the purview of that WG. + +For an up-to-date list of the issue/PR labels associated with these WGs, +see [Issue Management](../maintainers/issue-management.md) + +## Desired State Configuration (DSC) + +The Desired State Configuration (DSC) WG manages all facets of DSC in PowerShell 7, +including language features (like the `Configuration` keyword) +and the `PSDesiredStateConfiguration` module. + +Today, DSC is integrated into the PowerShell language, and we need to manage it as such. + +### Members + +* @TravisEz13 +* @theJasonHelmick +* @anmenaga +* @gaelcolas +* @michaeltlombardi +* @SteveL-MSFT + +## Developer Experience + +The PowerShell developer experience includes the **development of modules** (in C#, PowerShell script, etc.), +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 Visual Studio Code). + +### Members + +* @JamesWTruher (PS Standard, module authoring) +* @adityapatwardhan (SDK) +* @michaeltlombardi +* @SeeminglyScience +* @bergmeister + +## Engine + +The PowerShell engine is one of the largest and most complex aspects of the codebase. +The Engine WG should be focused on the +**implementation and maintenance of core PowerShell engine code**. +This includes (but is not limited to): + +* The language parser +* The command and parameter binders +* The module and provider systems + * `*-Item` cmdlets + * Providers +* Performance +* Componentization +* AssemblyLoadContext + +It's worth noting that the Engine WG is not responsible for the definition of the PowerShell language. +This should be handled by the Language WG instead. +However, it's expected that many issues will require input from both WGs. + +### Members + +* @daxian-dbw +* @JamesWTruher +* @rkeithhill +* @vexx32 +* @SeeminglyScience +* @IISResetMe +* @powercode +* @kilasuit + +## Interactive UX + +While much of PowerShell can be used through both interactive and non-interactive means, +some of the PowerShell user experience is exclusively interactive. +These topics include (but are not limited to): + +* Console +* Help System +* Tab completion / IntelliSense +* Markdown rendering +* PSReadLine +* Debugging + +### Members + +* @theJasonHelmick +* @daxian-dbw (PSReadline / IntelliSense) +* @adityapatwardhan (Markdown / help system) +* @JamesWTruher (cmdlet design) +* @SeeminglyScience +* @sdwheeler +* @kilasuit +* @FriedrichWeinmann +* @StevenBucher98 + +## Language + +The Language WG is distinct from the Engine WG in that they deal with the abstract definition +of the PowerShell language itself. +While all WGs will be working closely with the PowerShell Committee (and may share members), +it's likely that the Language WG will work especially close with them, +particularly given the long-lasting effects of language decisions. + +### Members + +* @JamesWTruher +* @daxian-dbw +* @SeeminglyScience + +## Remoting + +The Remoting WG should focus on topics like the **PowerShell Remoting Protocol (PSRP)**, +the **protocols implemented under PSRP** (e.g. WinRM and SSH), +and **other protocols used for remoting** (e.g. "pure SSH" as opposed to SSH over PSRP). +Given the commonality of serialization boundaries, the Remoting WG should also focus on +**the PowerShell job system**. + +### Members + +* @anmenaga +* @TravisEz13 + +## Cmdlets and Modules + +The Cmdlet WG should focus on core/inbox modules whose source code lives within the +`PowerShell/PowerShell` repository, +including the proposal of new cmdlets and parameters, improvements and bugfixes to existing +cmdlets/parameters, and breaking changes. + +However, some modules that ship as part of the PowerShell package are managed in other source repositories. +These modules are owned by the maintainers of those individual repositories. +These modules include: + +* [`Microsoft.PowerShell.Archive`](https://github.com/PowerShell/Microsoft.PowerShell.Archive) +* [`PackageManagement` (formerly `OneGet`)](https://github.com/OneGet/oneget) +* [`PowerShellGet`](https://github.com/PowerShell/PowerShellGet) +* [`PSDesiredStateConfiguration`](https://github.com/PowerShell/xPSDesiredStateConfiguration) + (Note: this community repository maintains a slightly different version of this module on the Gallery, + but should be used for future development of `PSDesiredStateConfiguration`.) +* [`PSReadLine`](https://github.com/PowerShell/PSReadLine) +* [`ThreadJob`](https://github.com/PowerShell/Modules/tree/master/Modules/Microsoft.PowerShell.ThreadJob) + +### Members + +* @JamesWTruher +* @SteveL-MSFT +* @jdhitsolutions +* @TobiasPSP +* @doctordns +* @kilasuit + +## Security + +The Security WG should be brought into any issues or pull requests which may have security implications +in order to provide their expertise, concerns, and guidance. + +### Members + +* @TravisEz13 +* @SydneySmithReal +* @anamnavi +* @SteveL-MSFT + +## Explicitly not Working Groups + +Some areas of ownership in PowerShell specifically do not have Working Groups. +For the sake of completeness, these are listed below: + +### Build + +Build includes everything that is needed to build, compile, and package PowerShell. +This bucket is also not oriented a customer-facing deliverable and is already something handled by Maintainers, +so we don't need to address it as part of the WGs. + +* Build + * `build.psm1` + * `install-powershell.ps1` + * Build infrastructure and automation +* Packaging + * Scripts + * Infrastructure + +### Quality + +Similar to the topic of building PowerShell, quality +(including **test code**, **test infrastructure**, and **code coverage**) +should be managed by the PowerShell Maintainers. + +* Test code + * Pester unit tests + * xUnit unit tests +* Test infrastructure + * Nightlies + * CI +* Code coverage +* Pester diff --git a/docs/community/working-group.md b/docs/community/working-group.md new file mode 100644 index 00000000000..8b9caea2857 --- /dev/null +++ b/docs/community/working-group.md @@ -0,0 +1,197 @@ +# Working Groups + +Working Groups (WGs) are collections of contributors with knowledge of specific components or +technologies in the PowerShell domain. +They are responsible for issue triage/acceptance, code reviews, and providing their expertise to +others in issues, PRs, and RFC discussions. + +The list, description, and membership of the existing Working Groups is available +[here](working-group-definitions.md). + +## Terms + +* **Contributor** is used interchangeably within this doc as anyone participating in issues or + contributing code, RFCs, documentations, tests, bug reports, etc., + regardless of their status with the PowerShell project. +* **Repository Maintainers** are trusted stewards of the PowerShell repository responsible for + maintaining consistency and quality of PowerShell code. + One of their primary responsibilities is merging pull requests after all requirements have been fulfilled. + (Learn more about the Repository Maintainers [here](https://github.com/PowerShell/PowerShell/tree/master/docs/maintainers).) +* The **PowerShell Committee** is responsible for the design and governance of the PowerShell project, + primarily by voting to accept or reject review-for-comment (RFC) documents. + (Learn more about the PowerShell Committee [here](https://github.com/PowerShell/PowerShell/blob/master/docs/community/governance.md#powershell-committee).) +* A **Working Group** is a collection of people responsible for providing expertise on a specific + area of PowerShell in order to help establish consensus within the community and Committee. + The responsibilities of Working Groups are outlined below. + (Note: while some experts within Working Groups may have more specific expertise in a sub-topic + of the team, + the intent is that each team is holistically and collectively responsible for making decisions + within the larger topic space.) + +## Goals + +In designing the WG process, the Committee had a few goals: + +1. Increase the velocity of innovation without compromising the stability of PowerShell +1. Reduce the time spent by contributors on writing/reviewing PRs and RFCs that are not feasible +1. Increase the formal authority of subject matter experts (SMEs) inside and outside of Microsoft +1. Decrease the volume of required technical discussions held by the Committee + +## Process + +This process is represented within the [`process_diagram.vsdx` Visio diagram](process_diagram.vsdx): + +![process_diagram](process_diagram.svg) + +1. A contributor has an idea for PowerShell +1. The contributor files an issue informally describing the idea, + including some rationale as to why it should happen and a few use cases to show how it could work, + as well as to determine viability and value to the community. + This should include examples of expected input and output so that others understand how the feature would function in the real world. +1. The issue gets triaged into an [Area label](https://github.com/PowerShell/PowerShell/blob/master/docs/maintainers/issue-management.md#feature-areas) + by Maintainers. + This area label maps to one or more Working Groups. +1. If the Working Group determines that an idea can be prototyped or built outside of the PowerShell repo + (e.g. as a module), + contributors should start that idea outside of the PowerShell project. + Given that the issue is no longer directly relevant to the PowerShell project, it should be closed + (with a link to the new project, if available). + If the implementation turns out to be successful and particularly popular, + and a contributor believes that it would be overwhelmingly valuable to include in the primary PowerShell package, + they can restart the process in a new issue proposing that the functionality be + incorporated directly into the primary PowerShell package. +1. After the issue is filed, interested contributors should discuss the + feasibility and approach of the idea in the issue. + The Working Group that owns that Area is expected to contribute to this discussion. + Working groups may have their own criteria to consider in their areas. +1. After an appropriately exhaustive discussion/conversation + (i.e. the Working Group has determined that no new arguments are being made), + the Working Group makes a call on whether they believe the idea should continue through this process. + Note: this should be done via a best effort of consensus among the Working Group. + We don't currently have hard requirements for how this should be done, + but some ideas include: + + * a single member of the Working Group makes a proposal as a comment and other members should + "react" on GitHub with up/down thumbs + * Working Groups communicate privately through their own established channel to reach consensus + + It's worth noting that Working Group members who repeatedly speak on behalf of the Working Group without + consensus, they may be censured or removed from the team. + +### Working Groups reject the proposal + +If the Working Group says the idea should not pursued, the process stops. +Some reasons for rejection include (but are not limited to): + +* the idea can be implemented and validated for usefulness and popularity outside of the primary PowerShell repo/package +* the idea is difficult/impossible to implement +* the implementation would introduce undesirable (and possibly breaking) changes to PowerShell +* other reasons specific to individual Working Groups + +In the instance that the contributor feels they have compelling arguments showing that the +Working Group is incorrect in their rejection, +they can appeal to the PowerShell Committee by mentioning `@PowerShell/PowerShell-Committee`, +upon which a maintainer will add the `Review-Committee` label to the issue and reopen it. +Then, the PS Committee will discuss further with the Working Group and others to make a final call on +whether or not the issue should be pursued further. + +Be sure to enumerate your reasons for appeal, as unfounded appeals may be rejected for consideration +by the Committee until reasons are given. + +### Working groups believe the proposal has merit and/or potential + +If the idea passes the preliminary acceptance criteria for the Working Group, +the process proceeds on one of a few different paths: + +#### "RFC Not Required" + +In some cases, a proposed idea is determined by the Working Group to be small, uncontroversial, or simple, +such that an RFC is not required (to be determined by the Working Group). +In these circumstances, the Working Group should mark the issue as "RFC not required", +upon which it can move directly to the code PR phase to be reviewed by Working Groups and Maintainers. + +The Committee still holds the authority to require an RFC if they see an "RFC Not Required" issue +that they feel needs more exposition before merging. + +In cases of minor breaking changes, Maintainers or Working Groups can add the `Review - Committee` label to get +additional opinions from the Committee. + +#### RFC/Prototype Process + +If an idea has any significant design or ecosystem implications, +*cannot* be prototyped or built outside of the PowerShell repo, +and the community and Working Groups agree that the idea is worth pursuing, +a contributor (who may or may not be the original issue filer) must do two things: + +* Write an RFC as a [Draft PR](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests) + into `PowerShell/PowerShell-RFC` +* Prototype the implementation as a [Draft PR](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests) + into `PowerShell/PowerShell` + +In both cases, the intention is to provide Working Groups and other contributors who care about the +idea an opportunity to provide feedback on the design and implementation. + +Either of these two steps can be done first, but both are required in order to have code accepted +into the PowerShell repository, including experimental features. + +Note: When "Draft" is capitalized in this document, I'm referring to the +[Draft pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests) +feature on GitHub. +We intend to use this feature liberally to mark the lifecycle of an idea through to implementation. + +#### RFCs + +The existing RFC process uses folders as a way to move an RFC through a multi-stage process +(Draft -> Experimental -> Accepted/Final). +However, it was difficult to reconcile this process with the benefits of PR reviews. + +With the introduction of Draft PRs on GitHub, we are reorienting the acceptance process around the PR itself. +Going forward, an RFC will have three stages: + +1. A Draft PR, denoting that the RFC is still in the review period, openly soliciting comments, + and that it may continue to be significantly iterated upon with revisions, edits, and responses to + community feedback or concerns. +1. After a minimum of two months of discussion, the RFC/PR author marks the PR as + [ready for review](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/changing-the-stage-of-a-pull-request), + upon which the RFC will enter the Committee's review queue to discuss the RFC contents and comments, + and make a decision on whether it is reasonable to pursue. + If after this review, the Committee determines that the intent of the RFC is not reasonable + (e.g. there may be irreconcilable issues with the design, + or the intent may not fit with the principles of PowerShell), + they will reject the PR and the process terminates. +1. In most cases, the Committee will choose to wait for the code PR to be merged as experimental, + and leverage user feedback or telemetry to determine if the RFC PR should be merged. +1. Finally, the Committee will choose to either merge or close the RFC PR, + marking the RFC as either accepted or rejected, respectively. + +#### Experiments + +Often times, implementing an idea can demonstrate opportunities or challenges that were not well +understood when the idea was formulated. +Similarly, a "simple" code change can have far reaching effects that are not well understood +until you're able to experiment with an idea within working code. + +To that end, it's required that *some* implementation exist before the Committee will consider an +RFC for acceptance. +That way, contributors can compile the PR branch to play with a working iteration of the idea +as a way to understand whether the feature is working as expected and valuable. + +In most cases, as long as an RFC has already been written and published as a draft, +Working Groups or the Committee will approve the feature for incorporation as an experimental feature, +so that it can be trialed with greater usage as part of a preview release. +In addition to increasing the scope of those who can provide real-world feedback on the feature, +this enables us to use telemetry to understand if users are turning off the feature in large numbers. + +Note: today, this will be done on a case-by-case basis, but over time, the Committee will establish +firmer guidelines around when PRs should be merged as experimental. + +Experiments should be complete to the extent that they serve as reasonable indicators of the user experience. +In the case that breaking changes are required of the feature, the break should be made in the prototype +so that users can experiment with whether or not the break has a significant negative effect. + +#### Reporting Working Group members abuse + +Working group members are individuals and in many cases volunteers. +There may be situations where a working group member might exhibit behavior that is objectionable, +exceed the authority defined in this document or violate the [Code of Conduct](../../CODE_OF_CONDUCT.md). +We recommend to report such issues by using the [Report Content](https://docs.github.com/communities/maintaining-your-safety-on-github/reporting-abuse-or-spam) mechanism to bring it to the attention of the maintainers to review. diff --git a/docs/debugging/README.md b/docs/debugging/README.md index 1b13924b854..6e1acaaef59 100644 --- a/docs/debugging/README.md +++ b/docs/debugging/README.md @@ -41,7 +41,7 @@ process named `powershell`, and will attach to it. If you need more fine-grained control, replace `processName` with `processId` and provide a PID. (Please be careful not to commit such a change.) -[core-debug]: https://docs.microsoft.com/dotnet/core/tutorials/with-visual-studio-code#debug +[core-debug]: https://learn.microsoft.com/dotnet/core/tutorials/with-visual-studio-code#debug [vscode]: https://code.visualstudio.com/ [OmniSharp]: https://github.com/OmniSharp/omnisharp-vscode diff --git a/docs/dev-process/coding-guidelines.md b/docs/dev-process/coding-guidelines.md index 389981c3f7b..ef0f671c1a0 100644 --- a/docs/dev-process/coding-guidelines.md +++ b/docs/dev-process/coding-guidelines.md @@ -86,9 +86,15 @@ We also run the [.NET code formatter tool](https://github.com/dotnet/codeformatt * Make sure the added/updated comments are meaningful, accurate and easy to understand. -* Public members must use [doc comments](https://docs.microsoft.com/dotnet/csharp/programming-guide/xmldoc/). +### Documentation comments + +* Create documentation using [XML documentation comments](https://learn.microsoft.com/dotnet/csharp/language-reference/xmldoc/) so that Visual Studio and other IDEs can use IntelliSense to show quick information about types or members. + +* Publicly visible types and their members must be documented. Internal and private members may use doc comments but it is not required. +* Documentation text should be written using complete sentences ending with full stops. + ## Performance Considerations PowerShell has a lot of performance sensitive code as well as a lot of inefficient code. @@ -183,16 +189,16 @@ See [CODEOWNERS](../../.github/CODEOWNERS) for more information about the area e * Consider using the `Interlocked` class instead of the `lock` statement to atomically change simple states. The `Interlocked` class provides better performance for updates that must be atomic. * Here are some useful links for your reference: - * [Framework Design Guidelines](https://docs.microsoft.com/dotnet/standard/design-guidelines/index) - Naming, Design and Usage guidelines including: - * [Arrays](https://docs.microsoft.com/dotnet/standard/design-guidelines/arrays) - * [Collections](https://docs.microsoft.com/dotnet/standard/design-guidelines/guidelines-for-collections) - * [Exceptions](https://docs.microsoft.com/dotnet/standard/design-guidelines/exceptions) - * [Best Practices for Developing World-Ready Applications](https://docs.microsoft.com/dotnet/standard/globalization-localization/best-practices-for-developing-world-ready-apps) - Unicode, Culture, Encoding and Localization. - * [Best Practices for Exceptions](https://docs.microsoft.com/dotnet/standard/exceptions/best-practices-for-exceptions) - * [Best Practices for Using Strings in .NET](https://docs.microsoft.com/dotnet/standard/base-types/best-practices-strings) - * [Best Practices for Regular Expressions in .NET](https://docs.microsoft.com/dotnet/standard/base-types/best-practices) - * [Serialization Guidelines](https://docs.microsoft.com/dotnet/standard/serialization/serialization-guidelines) - * [Managed Threading Best Practices](https://docs.microsoft.com/dotnet/standard/threading/managed-threading-best-practices) + * [Framework Design Guidelines](https://learn.microsoft.com/dotnet/standard/design-guidelines/index) - Naming, Design and Usage guidelines including: + * [Arrays](https://learn.microsoft.com/dotnet/standard/design-guidelines/arrays) + * [Collections](https://learn.microsoft.com/dotnet/standard/design-guidelines/guidelines-for-collections) + * [Exceptions](https://learn.microsoft.com/dotnet/standard/design-guidelines/exceptions) + * [Best Practices for Developing World-Ready Applications](https://learn.microsoft.com/dotnet/core/extensions/best-practices-for-developing-world-ready-apps) - Unicode, Culture, Encoding and Localization. + * [Best Practices for Exceptions](https://learn.microsoft.com/dotnet/standard/exceptions/best-practices-for-exceptions) + * [Best Practices for Using Strings in .NET](https://learn.microsoft.com/dotnet/standard/base-types/best-practices-strings) + * [Best Practices for Regular Expressions in .NET](https://learn.microsoft.com/dotnet/standard/base-types/best-practices) + * [Serialization Guidelines](https://learn.microsoft.com/dotnet/standard/serialization/serialization-guidelines) + * [Managed Threading Best Practices](https://learn.microsoft.com/dotnet/standard/threading/managed-threading-best-practices) ## Portable Code diff --git a/docs/git/README.md b/docs/git/README.md index 817e4930f6c..46b5eee4c62 100644 --- a/docs/git/README.md +++ b/docs/git/README.md @@ -13,9 +13,9 @@ git clone https://github.com/PowerShell/PowerShell.git --branch=master * Checkout a new local branch from `master` for every change you want to make (bugfix, feature). * Use lowercase-with-dashes for naming. * Follow [Linus' recommendations][Linus] about history. - - "People can (and probably should) rebase their _private_ trees (their own work). That's a _cleanup_. But never other peoples code. That's a 'destroy history'... - You must never EVER destroy other peoples history. You must not rebase commits other people did. - Basically, if it doesn't have your sign-off on it, it's off limits: you can't rebase it, because it's not yours." + * "People can (and probably should) rebase their _private_ trees (their own work). That's a _cleanup_. But never other peoples code. That's a 'destroy history'... + You must never EVER destroy other peoples history. You must not rebase commits other people did. + Basically, if it doesn't have your sign-off on it, it's off limits: you can't rebase it, because it's not yours." ### Understand branches @@ -23,12 +23,12 @@ git clone https://github.com/PowerShell/PowerShell.git --branch=master It could be unstable. * Send your pull requests to **master**. -### Sync your local repo +### Sync your local repository Use **git rebase** instead of **git merge** and **git pull**, when you're updating your feature-branch. ```sh -# fetch updates all remote branch references in the repo +# fetch updates all remote branch references in the repository # --all : tells it to do it for all remotes (handy, when you use your fork) # -p : tells it to remove obsolete remote branch references (when they are removed from remote) git fetch --all -p @@ -42,11 +42,9 @@ git rebase origin/master Covering all possible git scenarios is behind the scope of the current document. Git has excellent documentation and lots of materials available online. -We are leaving few links here: +We are leaving a few links here: -[Git pretty flowchart](http://justinhileman.info/article/git-pretty/): what to do, when your local repo became a mess. - -[Linus]:https://wincent.com/wiki/git_rebase%3A_you're_doing_it_wrong +[Linus]:https://web.archive.org/web/20230522041845/https://wincent.com/wiki/git_rebase%3A_you're_doing_it_wrong ## Tags @@ -57,12 +55,12 @@ you will find it via **tags**. * Find the tag that corresponds to the release. * Use `git checkout ` to get this version. -**Note:** [checking out a tag][tag] will move the repo to a [DETACHED HEAD][HEAD] state. +**Note:** [checking out a tag][tag] will move the repository to a [DETACHED HEAD][HEAD] state. [tag]:https://git-scm.com/book/en/v2/Git-Basics-Tagging#Checking-out-Tags [HEAD]:https://www.git-tower.com/learn/git/faq/detached-head-when-checkout-commit -If you want to make changes, based on tag's version (i.e. a hotfix), +If you want to make changes, based on tag's version (i.e. a hotfix), checkout a new branch from this DETACHED HEAD state. ```sh diff --git a/docs/git/basics.md b/docs/git/basics.md index a8a9bacf046..aa7629cf746 100644 --- a/docs/git/basics.md +++ b/docs/git/basics.md @@ -40,11 +40,6 @@ changes, and issue a pull request. [Hello World]: https://guides.github.com/activities/hello-world/ -#### Katacoda - -Learn basic Git scenarios in the browser with interactive labs. -[Git lessons on katacoda](https://www.katacoda.com/courses/git/). - #### Githug [Githug](https://github.com/Gazler/githug) is a great gamified way to diff --git a/docs/host-powershell/README.md b/docs/host-powershell/README.md index 3d96855b413..174f986dfa4 100644 --- a/docs/host-powershell/README.md +++ b/docs/host-powershell/README.md @@ -41,7 +41,7 @@ There is a special hosting scenario for native hosts, where Trusted Platform Assemblies (TPA) do not include PowerShell assemblies, such as the in-box `powershell.exe` in Nano Server and the Azure DSC host. -For such hosting scenarios, the native host needs to bootstrap by calling [`PowerShellAssemblyLoadContextInitializer.SetPowerShellAssemblyLoadContext`](https://docs.microsoft.com/dotnet/api/system.management.automation.powershellassemblyloadcontextinitializer.setpowershellassemblyloadcontext). +For such hosting scenarios, the native host needs to bootstrap by calling [`PowerShellAssemblyLoadContextInitializer.SetPowerShellAssemblyLoadContext`](https://learn.microsoft.com/dotnet/api/system.management.automation.powershellassemblyloadcontextinitializer.setpowershellassemblyloadcontext). When using this API, the native host can pass in the path to the directory that contains PowerShell assemblies. A handler will then be registered to the [`Resolving`](https://github.com/dotnet/corefx/blob/d6678e9653defe3cdfff26b2ff62135b6b22c77f/src/System.Runtime.Loader/ref/System.Runtime.Loader.cs#L38) event of the default load context to deal with the loading of assemblies from that directory. diff --git a/docs/host-powershell/sample/NuGet.config b/docs/host-powershell/sample/NuGet.config.md similarity index 70% rename from docs/host-powershell/sample/NuGet.config rename to docs/host-powershell/sample/NuGet.config.md index b3ce3cb82a5..bf2b4c3f688 100644 --- a/docs/host-powershell/sample/NuGet.config +++ b/docs/host-powershell/sample/NuGet.config.md @@ -1,3 +1,8 @@ +# Nuget.config creation + +Create a filed called `nuget.config` at this location with this content: + +```xml @@ -8,3 +13,4 @@ +``` diff --git a/docs/learning-powershell/README.md b/docs/learning-powershell/README.md deleted file mode 100644 index 0dbbc5b8576..00000000000 --- a/docs/learning-powershell/README.md +++ /dev/null @@ -1,127 +0,0 @@ -# Learning PowerShell - -Whether you're a Developer, a DevOps or an IT Professional, this doc will help you getting started with PowerShell. -In this document we'll cover the following: -installing PowerShell, samples walkthrough, PowerShell editor, debugger, testing tools and a map book for experienced bash users to get started with PowerShell faster. - -The exercises in this document are intended to give you a solid foundation in how to use PowerShell. -You won't be a PowerShell guru at the end of reading this material but you will be well on your way with the right set of knowledge to start using PowerShell. - -If you have 30 minutes now, let’s try it. - -## Installing PowerShell - -First you need to set up your computer working environment if you have not done so. -Choose the platform below and follow the instructions. -At the end of this exercise, you should be able to launch the PowerShell session. - -- Get PowerShell by installing package - * [PowerShell on Linux][inst-linux] - * [PowerShell on macOS][inst-macos] - * [PowerShell on Windows][inst-win] - - For this tutorial, you do not need to install PowerShell if you are running on Windows. - You can launch PowerShell console by pressing Windows key, typing PowerShell, and clicking on Windows PowerShell. - However if you want to try out the latest PowerShell, follow the [PowerShell on Windows][inst-win]. - -- Alternatively you can get the PowerShell by [building it][build-powershell] - -[build-powershell]:../../README.md#building-the-repository -[inst-linux]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-linux -[inst-win]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-windows -[inst-macos]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-macos - -## Getting Started with PowerShell - -PowerShell commands follow a Verb-Noun semantic with a set of parameters. -It's easy to learn and use PowerShell. -For example, `Get-Process` will display all the running processes on your system. -Let's walk through with a few examples from the [PowerShell Beginner's Guide](powershell-beginners-guide.md). - -Now you have learned the basics of PowerShell. -Please continue reading if you want to do some development work in PowerShell. - -### PowerShell Editor - -In this section, you will create a PowerShell script using a text editor. -You can use your favorite editor to write scripts. -We use Visual Studio Code (VS Code) which works on Windows, Linux, and macOS. -Click on the following link to create your first PowerShell script. - -- [Using Visual Studio Code (VS Code)](https://docs.microsoft.com/powershell/scripting/dev-cross-plat/vscode/using-vscode) - -### PowerShell Debugger - -Debugging can help you find bugs and fix problems in your PowerShell scripts. -Click on the link below to learn more about debugging: - -- [Using Visual Studio Code (VS Code)](https://docs.microsoft.com/powershell/scripting/dev-cross-plat/vscode/using-vscode#debugging-with-visual-studio-code) -- [PowerShell Command-line Debugging][cli-debugging] - -[cli-debugging]:./debugging-from-commandline.md - -### PowerShell Testing - -We recommend using Pester testing tool which is initiated by the PowerShell Community for writing test cases. -To use the tool please read [Pester Guides](https://github.com/pester/Pester) and [Writing Pester Tests Guidelines](https://github.com/PowerShell/PowerShell/blob/master/docs/testing-guidelines/WritingPesterTests.md). - -### Map Book for Experienced Bash users - -The table below lists the usage of some basic commands to help you get started on PowerShell faster. -Note that all bash commands should continue working on PowerShell session. - -| Bash | PowerShell | Description -|:--------------------------------|:----------------------------------------|:--------------------- -| ls | dir, Get-ChildItem | List files and folders -| tree | dir -Recurse, Get-ChildItem -Recurse | List all files and folders -| cd | cd, Set-Location | Change directory -| pwd | pwd, $pwd, Get-Location | Show working directory -| clear, Ctrl+L, reset | cls, clear | Clear screen -| mkdir | New-Item -ItemType Directory | Create a new folder -| touch test.txt | New-Item -Path test.txt | Create a new empty file -| cat test1.txt test2.txt | Get-Content test1.txt, test2.txt | Display files contents -| cp ./source.txt ./dest/dest.txt | Copy-Item source.txt dest/dest.txt | Copy a file -| cp -r ./source ./dest | Copy-Item ./source ./dest -Recurse | Recursively copy from one folder to another -| mv ./source.txt ./dest/dest.txt | Move-Item ./source.txt ./dest/dest.txt | Move a file to other folder -| rm test.txt | Remove-Item test.txt | Delete a file -| rm -r <folderName> | Remove-Item <folderName> -Recurse | Delete a folder -| find -name build* | Get-ChildItem build* -Recurse | Find a file or folder starting with 'build' -| grep -Rin "sometext" --include="*.cs" |Get-ChildItem -Recurse -Filter *.cs
\| Select-String -Pattern "sometext" | Recursively case-insensitive search for text in files -| curl https://github.com | Invoke-RestMethod https://github.com | Transfer data to or from the web - -### Recommended Training and Reading - -- Microsoft Virtual Academy: [Getting Started with PowerShell][getstarted-with-powershell] -- [Why Learn PowerShell][why-learn-powershell] by Ed Wilson -- PowerShell Web Docs: [Basic cookbooks][basic-cookbooks] -- [The Guide to Learning PowerShell][ebook-from-Idera] by Tobias Weltner -- [PowerShell-related Videos][channel9-learn-powershell] on Channel 9 -- [PowerShell Quick Reference Guides][quick-reference] by PowerShellMagazine.com -- [Learn PowerShell Video Library][idera-learn-powershell] from Idera -- [PowerShell 5 How-To Videos][script-guy-how-to] by Ed Wilson -- [PowerShell Documentation](https://docs.microsoft.com/powershell) -- [Interactive learning with PSKoans](https://aka.ms/pskoans) - -### Commercial Resources - -- [Windows PowerShell in Action][in-action] by [Bruce Payette](https://github.com/brucepay) -- [Introduction to PowerShell][powershell-intro] from Pluralsight -- [PowerShell Training and Tutorials][lynda-training] from Lynda.com -- [Learn Windows PowerShell in a Month of Lunches][learn-win-powershell] by Don Jones and Jeffrey Hicks -- [Learn PowerShell in a Month of Lunches][learn-powershell] by Travis Plunk (@TravisEz13), - Tyler Leonhardt (@tylerleonhardt), Don Jones, and Jeffery Hicks - -[in-action]: https://www.amazon.com/Windows-PowerShell-Action-Second-Payette/dp/1935182137 -[powershell-intro]: https://www.pluralsight.com/courses/powershell-intro -[lynda-training]: https://www.lynda.com/PowerShell-training-tutorials/5779-0.html -[learn-win-powershell]: https://www.amazon.com/Learn-Windows-PowerShell-Month-Lunches/dp/1617294160 -[learn-powershell]: https://www.manning.com/books/learn-powershell-in-a-month-of-lunches - -[getstarted-with-powershell]: https://channel9.msdn.com/Series/GetStartedPowerShell3 -[why-learn-powershell]: https://blogs.technet.microsoft.com/heyscriptingguy/2014/10/18/weekend-scripter-why-learn-powershell/ -[ebook-from-Idera]:https://www.idera.com/resourcecentral/whitepapers/powershell-ebook -[channel9-learn-powershell]: https://channel9.msdn.com/Search?term=powershell#ch9Search -[idera-learn-powershell]: https://community.idera.com/database-tools/powershell/video_library/ -[quick-reference]: https://www.powershellmagazine.com/2014/04/24/windows-powershell-4-0-and-other-quick-reference-guides/ -[script-guy-how-to]:https://blogs.technet.microsoft.com/tommypatterson/2015/09/04/ed-wilsons-powershell5-videos-now-on-channel9-2/ -[basic-cookbooks]:https://docs.microsoft.com/powershell/scripting/samples/sample-scripts-for-administration diff --git a/docs/learning-powershell/create-powershell-scripts.md b/docs/learning-powershell/create-powershell-scripts.md deleted file mode 100644 index 5f93eb97ab3..00000000000 --- a/docs/learning-powershell/create-powershell-scripts.md +++ /dev/null @@ -1,65 +0,0 @@ -# How to Create and Run PowerShell Scripts - -You can combine a series of commands in a text file and save it with the file extension '.ps1', and the file will become a PowerShell script. -This would begin by opening your favorite text editor and pasting in the following example. - -```powershell -# Script to return current IPv4 addresses on a Linux or MacOS host -$ipInfo = ifconfig | Select-String 'inet' -$ipInfo = [regex]::matches($ipInfo,"addr:\b(?:\d{1,3}\.){3}\d{1,3}\b") | ForEach-Object value -foreach ($ip in $ipInfo) -{ - $ip.Replace('addr:','') -} -``` - -Then save the file to something memorable, such as .\NetIP.ps1. -In the future when you need to get the IP addresses for the node, you can simplify this task by executing the script. - -```powershell -.\NetIP.ps1 -10.0.0.1 -127.0.0.1 -``` - -You can accomplish this same task on Windows. - -```powershell -# One line script to return current IPv4 addresses on a Windows host -Get-NetIPAddress | Where-Object {$_.AddressFamily -eq 'IPv4'} | ForEach-Object IPAddress -``` - -As before, save the file as .\NetIP.ps1 and execute within a PowerShell environment. -Note: If you are using Windows, make sure you set the PowerShell's execution policy to "RemoteSigned" in this case. -See [Running PowerShell Scripts Is as Easy as 1-2-3][run-ps] for more details. - -```powershell -NetIP.ps1 -127.0.0.1 -10.0.0.1 -``` - -## Creating a script that can accomplish the same task on multiple operating systems - -If you would like to author one script that will return the IP address across Linux, MacOS, or Windows, you could accomplish this using an IF statement. - -```powershell -# Script to return current IPv4 addresses for Linux, MacOS, or Windows -$IP = if ($IsLinux -or $IsMacOS) -{ - $ipInfo = ifconfig | Select-String 'inet' - $ipInfo = [regex]::matches($ipInfo,"addr:\b(?:\d{1,3}\.){3}\d{1,3}\b") | ForEach-Object value - foreach ($ip in $ipInfo) { - $ip.Replace('addr:','') - } -} -else -{ - Get-NetIPAddress | Where-Object {$_.AddressFamily -eq 'IPv4'} | ForEach-Object IPAddress -} - -# Remove loopback address from output regardless of platform -$IP | Where-Object {$_ -ne '127.0.0.1'} -``` - -[run-ps]:https://www.itprotoday.com/powershell/running-powershell-scripts-easy-1-2-3 diff --git a/docs/learning-powershell/debugging-from-commandline.md b/docs/learning-powershell/debugging-from-commandline.md deleted file mode 100644 index 1aaab218256..00000000000 --- a/docs/learning-powershell/debugging-from-commandline.md +++ /dev/null @@ -1,173 +0,0 @@ -# Debugging in PowerShell Command-line - -As we know, we can debug PowerShell code via GUI tools like [Visual Studio Code](https://docs.microsoft.com/powershell/scripting/dev-cross-plat/vscode/using-vscode#debugging-with-visual-studio-code). In addition, we can -directly perform debugging within the PowerShell command-line session by using the PowerShell debugger cmdlets. This document demonstrates how to use the cmdlets for the PowerShell command-line debugging. We will cover the following topics: -setting a debug breakpoint on a line of code and on a variable. - -Let's use the following code snippet as our sample script. - -```powershell -# Convert Fahrenheit to Celsius -function ConvertFahrenheitToCelsius([double] $fahrenheit) -{ -$celsius = $fahrenheit - 32 -$celsius = $celsius / 1.8 -$celsius -} - -$fahrenheit = Read-Host 'Input a temperature in Fahrenheit' -$result =[int](ConvertFahrenheitToCelsius($fahrenheit)) -Write-Host "$result Celsius" -``` - -## Setting a Breakpoint on a Line - -- Open a [PowerShell editor](README.md#powershell-editor) -- Save the above code snippet to a file. For example, "test.ps1" -- Go to your command-line PowerShell -- Clear existing breakpoints if any - -```powershell - PS /home/jen/debug>Get-PSBreakpoint | Remove-PSBreakpoint -``` - -- Use **Set-PSBreakpoint** cmdlet to set a debug breakpoint. In this case, we will set it to line 5 - -```powershell -PS /home/jen/debug>Set-PSBreakpoint -Line 5 -Script ./test.ps1 - -ID Script Line Command Variable Action --- ------ ---- ------- -------- ------ - 0 test.ps1 5 -``` - -- Run the script "test.ps1". As we have set a breakpoint, it is expected the program will break into the debugger at the line 5. - -```powershell - -PS /home/jen/debug> ./test.ps1 -Input a temperature in Fahrenheit: 80 -Hit Line breakpoint on '/home/jen/debug/test.ps1:5' - -At /home/jen/debug/test.ps1:5 char:1 -+ $celsius = $celsius / 1.8 -+ ~~~~~~~~~~~~~~~~~~~~~~~~~ -[DBG]: PS /home/jen/debug>> -``` - -- The PowerShell prompt now has the prefix **[DBG]:** as you may have noticed. This means - we have entered into the debug mode. To watch the variables like $celsius, simply type **$celsius** as below. -- To exit from the debugging, type **q** -- To get help for the debugging commands, simply type **?**. The following is an example of debugging output. - -```PowerShell -[DBG]: PS /home/jen/debug>> $celsius -48 -[DBG]: PS /home/jen/debug>> $fahrenheit -80 -[DBG]: PS /home/jen/debug>> ? - - s, stepInto Single step (step into functions, scripts, etc.) - v, stepOver Step to next statement (step over functions, scripts, etc.) - o, stepOut Step out of the current function, script, etc. - - c, continue Continue operation - q, quit Stop operation and exit the debugger - d, detach Continue operation and detach the debugger. - - k, Get-PSCallStack Display call stack - - l, list List source code for the current script. - Use "list" to start from the current line, "list " - to start from line , and "list " to list - lines starting from line - - Repeat last command if it was stepInto, stepOver or list - - ?, h displays this help message. - - -For instructions about how to customize your debugger prompt, type "help about_prompt". - -[DBG]: PS /home/jen/debug>> s -At PS /home/jen/debug/test.ps1:6 char:1 -+ $celsius -+ ~~~~~~~~ -[DBG]: PS /home/jen/debug>> $celsius -26.6666666666667 -[DBG]: PS /home/jen/debug>> $fahrenheit -80 - -[DBG]: PS /home/jen/debug>> q -PS /home/jen/debug> - -``` - -## Setting a Breakpoint on a Variable -- Clear existing breakpoints if there are any - -```powershell - PS /home/jen/debug>Get-PSBreakpoint | Remove-PSBreakpoint - ``` - -- Use **Set-PSBreakpoint** cmdlet to set a debug breakpoint. In this case, we set it to line 5 - -```powershell - - PS /home/jen/debug>Set-PSBreakpoint -Variable "celsius" -Mode write -Script ./test.ps1 - -``` - -- Run the script "test.ps1" - - Once hit the debug breakpoint, we can type **l** to list the source code that debugger is currently executing. As we can see line 3 has an asterisk at the front, meaning that's the line the program is currently executing and broke into the debugger as illustrated below. -- Type **q** to exit from the debugging mode. The following is an example of debugging output. - -```powershell -./test.ps1 -Input a temperature in Fahrenheit: 80 -Hit Variable breakpoint on '/home/jen/debug/test.ps1:$celsius' (Write access) - -At /home/jen/debug/test.ps1:3 char:1 -+ $celsius = $fahrenheit - 32 -+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -[DBG]: PS /home/jen/debug>> l - - - 1: function ConvertFahrenheitToCelsius([double] $fahrenheit) - 2: { - 3:* $celsius = $fahrenheit - 32 - 4: $celsius = $celsius / 1.8 - 5: $celsius - 6: } - 7: - 8: $fahrenheit = Read-Host 'Input a temperature in Fahrenheit' - 9: $result =[int](ConvertFahrenheitToCelsius($fahrenheit)) - 10: Write-Host "$result Celsius" - - -[DBG]: PS /home/jen/debug>> $celsius -48 -[DBG]: PS /home/jen/debug>> v -At /home/jen/debug/test.ps1:4 char:1 -+ $celsius = $celsius / 1.8 -+ ~~~~~~~~~~~~~~~~~~~~~~~~~ -[DBG]: PS /home/jen/debug>> v -Hit Variable breakpoint on '/home/jen/debug/test.ps1:$celsius' (Write access) - -At /home/jen/debug/test.ps1:4 char:1 -+ $celsius = $celsius / 1.8 -+ ~~~~~~~~~~~~~~~~~~~~~~~~~ -[DBG]: PS /home/jen/debug>> $celsius -26.6666666666667 -[DBG]: PS /home/jen/debug>> q -PS /home/jen/debug> - -``` - -Now you know the basics of the PowerShell debugging from PowerShell command-line. For further learning, read the following articles. - -## More Reading - -- [about_Debuggers](https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_debuggers) -- [PowerShell Debugging](https://blogs.technet.microsoft.com/heyscriptingguy/tag/debugging/) diff --git a/docs/learning-powershell/powershell-beginners-guide.md b/docs/learning-powershell/powershell-beginners-guide.md deleted file mode 100644 index 2017b191066..00000000000 --- a/docs/learning-powershell/powershell-beginners-guide.md +++ /dev/null @@ -1,339 +0,0 @@ -# PowerShell Beginner’s Guide - -If you are new to PowerShell, this document will walk you through a few examples to give you some basic ideas of PowerShell. -We recommend that you open a PowerShell console/session and type along with the instructions in this document to get most out of this exercise. - -## Launch PowerShell Console/Session - -First you need to launch a PowerShell session by following the [Installing PowerShell Guide](./README.md#installing-powershell). - -## Getting Familiar with PowerShell Commands - -In this section, you will learn how to - -- create a file, delete a file and change file directory -- discover what version of PowerShell you are currently using -- exit a PowerShell session -- get help if you needed -- find syntax of PowerShell cmdlets -- and more - -As mentioned above, PowerShell commands are designed to have Verb-Noun structure, for instance `Get-Process`, `Set-Location`, `Clear-Host`, etc. -Let’s exercise some of the basic PowerShell commands, also known as **cmdlets**. - -Please note that we will use the PowerShell prompt sign **PS />** as it appears on Linux in the following examples. -It is shown as `PS C:\>` on Windows. - -1. `Get-Process`: Gets the processes that are running on the local computer or a remote computer. - - By default, you will get data back similar to the following: - - ```powershell - PS /> Get-Process - - Handles NPM(K) PM(K) WS(K) CPU(s) Id ProcessName - ------- ------ ----- ----- ------ -- ----------- - - - - 1 0.012 12 bash - - - - 21 20.220 449 powershell - - - - 11 61.630 8620 code - - - - 74 403.150 1209 firefox - - … - ``` - - Only interested in the instance of Firefox process that is running on your computer? - - Try this: - - ```powershell - PS /> Get-Process -Name firefox - - Handles NPM(K) PM(K) WS(K) CPU(s) Id ProcessName - ------- ------ ----- ----- ------ -- ----------- - - - - 74 403.150 1209 firefox - - ``` - - Want to get back more than one process? - Then just specify process names and separate them with commas. - - ```powershell - PS /> Get-Process -Name firefox, powershell - Handles NPM(K) PM(K) WS(K) CPU(s) Id ProcessName - ------- ------ ----- ----- ------ -- ----------- - - - - 74 403.150 1209 firefox - - - - 21 20.220 449 powershell - - ``` - -1. `Clear-Host`: Clears the display in the host program. - - ```powershell - PS /> Get-Process - PS /> Clear-Host - ``` - - Type too much just for clearing the screen? - - Here is how the alias can help. - -1. `Get-Alias`: Gets the aliases for the current session. - - ```powershell - Get-Alias - - CommandType Name - ----------- ---- - … - - Alias cd -> Set-Location - Alias cls -> Clear-Host - Alias clear -> Clear-Host - Alias copy -> Copy-Item - Alias dir -> Get-ChildItem - Alias gc -> Get-Content - Alias gmo -> Get-Module - Alias ri -> Remove-Item - Alias type -> Get-Content - … - ``` - - As you can see `cls` or `clear` is an alias of `Clear-Host`. - - Now try it: - - ```powershell - PS /> Get-Process - PS /> cls - ``` - -1. `cd -> Set-Location`: Sets the current working location to a specified location. - - ```powershell - PS /> Set-Location /home - PS /home> - ``` - -1. `dir -> Get-ChildItem`: Gets the items and child items in one or more specified locations. - - ```powershell - # Get all files under the current directory: - PS /> Get-ChildItem - - # Get all files under the current directory as well as its subdirectories: - PS /> cd $home - PS /home/jen> dir -Recurse - - # List all files with "txt" file extension. - PS /> cd $home - PS /home/jen> dir –Path *.txt -Recurse - ``` - -1. `New-Item`: Creates a new item. - - ```powershell - # An empty file is created if you type the following: - PS /home/jen> New-Item -Path ./test.txt - - - Directory: /home/jen - - - Mode LastWriteTime Length Name - ---- ------------- ------ ---- - -a---- 7/7/2016 7:17 PM 0 test.txt - ``` - - You can use the `-Value` parameter to add some data to your file. - - For example, the following command adds the phrase `Hello world!` as a file content to the `test.txt`. - - Because the test.txt file exists already, we use `-Force` parameter to replace the existing content. - - ```powershell - PS /home/jen> New-Item -Path ./test.txt -Value "Hello world!" -Force - - Directory: /home/jen - - - Mode LastWriteTime Length Name - ---- ------------- ------ ---- - -a---- 7/7/2016 7:19 PM 24 test.txt - - ``` - - There are other ways to add some data to a file. - - For example, you can use `Set-Content` to set the file contents: - - ```powershell - PS /home/jen>Set-Content -Path ./test.txt -Value "Hello world again!" - ``` - - Or simply use `>` as below: - - ```powershell - # create an empty file - "" > test.txt - - # set "Hello world!" as content of test.txt file - "Hello world!!!" > test.txt - - ``` - - The pound sign `#` above is used for comments in PowerShell. - -1. `type -> Get-Content`: Gets the content of the item at the specified location. - - ```powershell - PS /home/jen> Get-Content -Path ./test.txt - PS /home/jen> type -Path ./test.txt - - Hello world again! - ``` - -1. `del -> Remove-Item`: Deletes the specified items. - - This cmdlet will delete the file `/home/jen/test.txt`: - - ```powershell - PS /home/jen> Remove-Item ./test.txt - ``` - -1. `$PSVersionTable`: Displays the version of PowerShell you are currently using. - - Type `$PSVersionTable` in your PowerShell session, you will see something like below. - "PSVersion" indicates the PowerShell version that you are using. - - ```powershell - Name Value - ---- ----- - PSVersion 6.0.0-alpha - PSEdition Core - PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...} - BuildVersion 3.0.0.0 - GitCommitId v6.0.0-alpha.12 - CLRVersion - WSManStackVersion 3.0 - PSRemotingProtocolVersion 2.3 - SerializationVersion 1.1.0.1 - - ``` - -1. `Exit`: To exit the PowerShell session, type `exit`. - - ```powershell - exit - ``` - -## Need Help? - -The most important command in PowerShell is possibly the `Get-Help`, which allows you to quickly learn PowerShell without having to search around the internet. - -The `Get-Help` cmdlet also shows you how PowerShell commands work with examples. - -It shows the syntax and other technical information of the `Get-Process` cmdlet. - -```powershell -PS /> Get-Help -Name Get-Process -``` - -It displays the examples how to use the `Get-Process` cmdlet. - -```powershell -PS />Get-Help -Name Get-Process -Examples -``` - -If you use **-Full** parameter, for example, `Get-Help -Name Get-Process -Full`, it will display more technical information. - -## Discover Commands Available on Your System - -You want to discover what PowerShell cmdlets available on your system? Just run `Get-Command` as below: - -```powershell -PS /> Get-Command -``` - -If you want to know whether a particular cmdlet exists on your system, you can do something like below: - -```powershell -PS /> Get-Command Get-Process -``` - -If you want to know the syntax of `Get-Process` cmdlet, type: - -```powershell -PS /> Get-Command Get-Process -Syntax -``` - -If you want to know how to use the `Get-Process`, type: - -```powershell -PS /> Get-Help Get-Process -Example -``` - -## PowerShell Pipeline `|` - -Sometimes when you run Get-ChildItem or "dir", you want to get a list of files and folders in a descending order. -To achieve that, type: - -```powershell -PS /home/jen> dir | Sort-Object -Descending -``` - -Say you want to get the largest file in a directory - -```powershell -PS /home/jen> dir | Sort-Object -Property Length -Descending | Select-Object -First 1 - - - Directory: /home/jen - - -Mode LastWriteTime Length Name ----- ------------- ------ ---- --a---- 5/16/2016 1:15 PM 32972 test.log - -``` - -## How to Create and Run PowerShell scripts - -You can use Visual Studio Code or your favorite editor to create a PowerShell script and save it with a `.ps1` file extension. -For more details, see [Create and Run PowerShell Script Guide][create-run-script] - -## Recommended Training and Reading - -- Video: [Get Started with PowerShell][remoting] from Channel9 -- [eBooks from PowerShell.org](https://leanpub.com/u/devopscollective) -- [eBooks List][ebook-list] by Martin Schvartzman -- [Tutorial from MVP][tutorial] -- Script Guy blog: [The best way to Learn PowerShell][to-learn] -- [Understanding PowerShell Module][ps-module] -- [How and When to Create PowerShell Module][create-ps-module] by Adam Bertram -- Video: [PowerShell Remoting in Depth][in-depth] from Channel9 -- [PowerShell Basics: Remote Management][remote-mgmt] from ITPro -- [Running Remote Commands][remote-commands] from PowerShell Web Docs -- [Samples for Writing a PowerShell Script Module][examples-ps-module] -- [Writing a PowerShell module in C#][writing-ps-module] -- [Examples of Cmdlets Code][sample-code] - -## Commercial Resources - -- [Windows PowerShell in Action][in-action] by Bruce Payette -- [Windows PowerShell Cookbook][cookbook] by Lee Holmes - -[in-action]: https://www.amazon.com/Windows-PowerShell-Action-Bruce-Payette/dp/1633430294 -[cookbook]: http://shop.oreilly.com/product/9780596801519.do -[ebook-list]: https://martin77s.wordpress.com/2014/05/26/free-powershell-ebooks/ -[tutorial]: https://www.computerperformance.co.uk/powershell/index-13/ -[to-learn]:https://blogs.technet.microsoft.com/heyscriptingguy/2015/01/04/weekend-scripter-the-best-ways-to-learn-powershell/ -[ps-module]:https://docs.microsoft.com/powershell/scripting/developer/module/understanding-a-windows-powershell-module -[create-ps-module]:https://www.business.com/articles/powershell-modules/ -[remoting]:https://channel9.msdn.com/Series/GetStartedPowerShell3/06 -[in-depth]: https://channel9.msdn.com/events/MMS/2012/SV-B406 -[remote-mgmt]:https://www.itprotoday.com/powershell/powershell-basics-remote-management -[remote-commands]:https://docs.microsoft.com/powershell/scripting/learn/remoting/running-remote-commands -[examples-ps-module]:https://docs.microsoft.com/powershell/scripting/developer/module/how-to-write-a-powershell-script-module -[writing-ps-module]:https://www.powershellmagazine.com/2014/03/18/writing-a-powershell-module-in-c-part-1-the-basics/ -[sample-code]:https://docs.microsoft.com/powershell/scripting/developer/cmdlet/examples-of-cmdlet-code -[create-run-script]:./create-powershell-scripts.md diff --git a/docs/learning-powershell/working-with-powershell-objects.md b/docs/learning-powershell/working-with-powershell-objects.md deleted file mode 100644 index ab127483cfe..00000000000 --- a/docs/learning-powershell/working-with-powershell-objects.md +++ /dev/null @@ -1,125 +0,0 @@ -# Working with PowerShell Objects - -When cmdlets are executed in PowerShell, the output is an Object, as opposed to only returning text. -This provides the ability to store information as properties. -As a result, handling large amounts of data and getting only specific properties is a trivial task. - -As a simple example, the following function retrieves information about storage Devices on a Linux or MacOS operating system platform. -This is accomplished by parsing the output of an existing command, *parted -l* in administrative context, and creating an object from the raw text by using the *New-Object* cmdlet. - -```powershell -function Get-DiskInfo -{ - $disks = sudo parted -l | Select-String "Disk /dev/sd*" -Context 1,0 - $diskinfo = @() - foreach ($disk in $disks) { - $diskline1 = $disk.ToString().Split("`n")[0].ToString().Replace(' Model: ','') - $diskline2 = $disk.ToString().Split("`n")[1].ToString().Replace('> Disk ','') - $i = New-Object psobject -Property @{'Friendly Name' = $diskline1; Device=$diskline2.Split(': ')[0]; 'Total Size'=$diskline2.Split(':')[1]} - $diskinfo += $i - } - $diskinfo -} -``` - -Execute the function and store the results as a variable. -Now retrieve the value of the variable. -The results are formatted as a table with the default view. - -*Note: in this example, the disks are virtual disks in a Microsoft Azure virtual machine.* - -```powershell -PS /home/psuser> $d = Get-DiskInfo -[sudo] password for psuser: -PS /home/psuser> $d - -Friendly Name Total Size Device -------------- ---------- ------ -Msft Virtual Disk (scsi) 31.5GB /dev/sda -Msft Virtual Disk (scsi) 145GB /dev/sdb - -``` - -Passing the variable down the pipeline to *Get-Member* reveals available methods and properties. -This is because the value of *$d* is not just text output. -The value is actually an array of .Net objects with methods and properties. -The properties include Device, Friendly Name, and Total Size. - -```powershell -PS /home/psuser> $d | Get-Member - - - TypeName: System.Management.Automation.PSCustomObject - -Name MemberType Definition ----- ---------- ---------- -Equals Method bool Equals(System.Object obj) -GetHashCode Method int GetHashCode() -GetType Method type GetType() -ToString Method string ToString() -Device NoteProperty string Device=/dev/sda -Friendly Name NoteProperty string Friendly Name=Msft Virtual Disk (scsi) -Total Size NoteProperty string Total Size= 31.5GB -``` - -To confirm, we can call the GetType() method interactively from the console. - -```powershell -PS /home/psuser> $d.GetType() - -IsPublic IsSerial Name BaseType --------- -------- ---- -------- -True True Object[] System.Array -``` - -To index in to the array and return only specific objects, use the square brackets. - -```powershell -PS /home/psuser> $d[0] - -Friendly Name Total Size Device -------------- ---------- ------ -Msft Virtual Disk (scsi) 31.5GB /dev/sda - -PS /home/psuser> $d[0].GetType() - -IsPublic IsSerial Name BaseType --------- -------- ---- -------- -True False PSCustomObject System.Object -``` - -To return a specific property, the property name can be called interactively from the console. - -```powershell -PS /home/psuser> $d.Device -/dev/sda -/dev/sdb -``` - -To output a view of the information other than default, such as a view with only specific properties selected, pass the value to the *Select-Object* cmdlet. - -```powershell -PS /home/psuser> $d | Select-Object Device, 'Total Size' - -Device Total Size ------- ---------- -/dev/sda 31.5GB -/dev/sdb 145GB -``` - -Finally, the example below demonstrates use of the *ForEach-Object* cmdlet to iterate through the array and manipulate the value of a specific property of each object. -In this case the Total Size property, which was given in Gigabytes, is changed to Megabytes. -Alternatively, index in to a position in the array as shown below in the third example. - -```powershell -PS /home/psuser> $d | ForEach-Object 'Total Size' - 31.5GB - 145GB - -PS /home/psuser> $d | ForEach-Object {$_.'Total Size' / 1MB} -32256 -148480 - -PS /home/psuser> $d[1].'Total Size' / 1MB -148480 -``` diff --git a/docs/maintainers/README.md b/docs/maintainers/README.md index 0c28e2cc9de..ebba4b02258 100644 --- a/docs/maintainers/README.md +++ b/docs/maintainers/README.md @@ -6,8 +6,8 @@ One of their primary responsibilities is merging pull requests after all require They have [write access](https://docs.github.com/en/free-pro-team@latest/github/setting-up-and-managing-organizations-and-teams/repository-permission-levels-for-an-organization) to the PowerShell repositories which gives them the power to: 1. `git push` to the official PowerShell repository -1. Merge [pull requests](https://www.thinkful.com/learn/github-pull-request-tutorial/) -1. Assign labels, milestones, and people to [issues](https://guides.github.com/features/issues/) and [pull requests](https://www.thinkful.com/learn/github-pull-request-tutorial/) +1. Merge [pull requests](https://docs.github.com/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests) +1. Assign labels, milestones, and people to [issues](https://guides.github.com/features/issues/) and [pull requests](https://docs.github.com/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests) ## Table of Contents @@ -15,7 +15,6 @@ They have [write access](https://docs.github.com/en/free-pro-team@latest/github/ - [Repository Maintainer Responsibilities](#repository-maintainer-responsibilities) - [Issue Management Process](#issue-management-process) - [Pull Request Workflow](#pull-request-workflow) - - [Abandoned Pull Requests](#abandoned-pull-requests) - [Becoming a Repository Maintainer](#becoming-a-repository-maintainer) ## Current Repository Maintainers @@ -33,7 +32,7 @@ They have [write access](https://docs.github.com/en/free-pro-team@latest/github/ -- Andy Schwartzmeyer ([andschwa](https://github.com/andschwa)) +- Andy Jordan ([andyleejordan](https://github.com/andyleejordan)) - Jason Shirk ([lzybkr](https://github.com/lzybkr)) - Mike Richmond ([mirichmo](https://github.com/mirichmo)) - Sergei Vorobev ([vors](https://github.com/vors)) @@ -44,6 +43,7 @@ Repository Maintainers enable rapid contributions while maintaining a high level If you are a Repository Maintainer, you: +1. **MUST** abide by the [Code of Conduct](../../CODE_OF_CONDUCT.md) and report suspected violations to the [PowerShell Committee][ps-committee] 1. **MUST** ensure that each contributor has signed a valid Microsoft Contributor License Agreement (CLA) 1. **MUST** verify compliance with any third party code license terms (e.g., requiring attribution, etc.) if the contribution contains third party code. 1. **MUST** make sure that [any change requiring approval from the PowerShell Committee](../community/governance.md#changes-that-require-an-rfc) has gone through the proper [RFC][RFC-repo] or approval process @@ -96,10 +96,11 @@ At any point in time, the existing Repository Maintainers can unanimously nomina Nominations are brought to the PowerShell Committee to understand the reasons and justification. A simple majority of the PowerShell Committee is required to veto the nomination. When a nominee has been approved, a PR will be submitted by a current Maintainer to update this document to add the nominee's name to -the [Current Repository Maintainers](#Current-Repository-Maintainers) with justification as the description of the PR to serve as the public announcement. +the [Current Repository Maintainers](#current-repository-maintainers) with justification as the description of the PR to serve as the public announcement. [RFC-repo]: https://github.com/PowerShell/PowerShell-RFC [ci-system]: ../testing-guidelines/testing-guidelines.md#ci-system [issue-management]: issue-management.md [CONTRIBUTING]: ../../.github/CONTRIBUTING.md [best-practice]: best-practice.md +[ps-committee]: ../community/governance.md#powershell-committee diff --git a/docs/maintainers/issue-management.md b/docs/maintainers/issue-management.md index 4021d44c82a..0cc8eb00e37 100644 --- a/docs/maintainers/issue-management.md +++ b/docs/maintainers/issue-management.md @@ -7,6 +7,8 @@ first follow the [vulnerability issue reporting policy](../../.github/SECURITY.m ## Long-living issue labels +Issue labels for PowerShell/PowerShell can be found [here](https://github.com/powershell/powershell/labels). + ### Issue and PR Labels Issues are opened for many different reasons. @@ -37,32 +39,35 @@ When an issue is resolved, the following labels are used to describe the resolut ### Feature areas -These labels describe what feature area of PowerShell that an issue affects: +These labels describe what feature area of PowerShell that an issue affects. +Those labels denoted by `WG-*` are owned by a Working Group (WG) defined +[here](../community/working-group-definitions.md): -* `Area-Build`: build issues +* `Area-Maintainers-Build`: build issues * `Area-Cmdlets-Core`: cmdlets in the Microsoft.PowerShell.Core module * `Area-Cmdlets-Utility`: cmdlets in the Microsoft.PowerShell.Utility module * `Area-Cmdlets-Management`: cmdlets in the Microsoft.PowerShell.Management module -* `Area-Console`: the console experience -* `Area-Debugging`: debugging PowerShell script -* `Area-Demo`: a demo or sample * `Area-Documentation`: PowerShell *repo* documentation issues, general PowerShell doc issues go [here](https://github.com/PowerShell/PowerShell-Docs/issues) * `Area-DSC`: DSC related issues -* `Area-Engine`: core PowerShell engine, interpreter, runtime -* `Area-HelpSystem`: anything related to the help infrastructure and formatting of help -* `Area-Intellisense`: tab completion -* `Area-Language`: parser, language semantics -* `Area-OMI`: OMI -* `Area-PackageManagement`: PackageManagement related issues -* `Area-Performance`: a performance issue -* `Area-Portability`: anything affecting script portability * `Area-PowerShellGet`: PowerShellGet related issues -* `Area-Providers`: PowerShell providers such as FileSystem, Certificates, Registry, etc... -* `Area-PSReadline`: PSReadline related issues -* `Area-Remoting`: PSRP issues with any transport layer -* `Area-Security`: security related areas such as [JEA](https://github.com/powershell/JEA) * `Area-SideBySide`: side by side support -* `Area-Test`: issues in a test or in test infrastructure +* `WG-DevEx-Portability`: anything related to authoring cross-platform or cross-architecture + modules, cmdlets, and scripts +* `WG-DevEx-SDK`: anything related to hosting PowerShell as a runtime, PowerShell's APIs, + PowerShell Standard, or the development of modules and cmdlets +* `WG-Engine`: core PowerShell engine, interpreter, and runtime +* `WG-Engine-Performance`: core PowerShell engine, interpreter, and runtime performance +* `WG-Engine-Providers`: built-in PowerShell providers such as FileSystem, Certificates, + Registry, etc. (or anything returned by `Get-PSProvider`) +* `WG-Interactive-Console`: the console experience +* `WG-Interactive-Debugging`: debugging PowerShell script +* `WG-Interactive-HelpSystem`: anything related to the help infrastructure and formatting of help +* `WG-Interactive-IntelliSense`: tab completion +* `WG-Interactive-PSReadline`: PSReadline related issues +* `WG-Language`: parser, language semantics +* `WG-Quality-Test`: issues in a test or in test infrastructure +* `WG-Remoting`: PSRP issues with any transport layer +* `WG-Security`: security related areas such as [JEA](https://github.com/powershell/JEA) ### Operating Systems diff --git a/docs/maintainers/releasing.md b/docs/maintainers/releasing.md index e3ac1998f77..5aae87582c9 100644 --- a/docs/maintainers/releasing.md +++ b/docs/maintainers/releasing.md @@ -21,20 +21,15 @@ This is to help track the release preparation work. - Sign the MSI packages and DEB/RPM packages. - Install and verify the packages. 1. Update documentation, scripts and Dockerfiles - - Summarize the change log for the release. It should be reviewed by PM(s) to make it more user-friendly. - - Update [CHANGELOG.md](../../CHANGELOG.md) with the finalized change log draft. + - Summarize the changelog for the release. It should be reviewed by PM(s) to make it more user-friendly. + - Update [CHANGELOG.md](../../CHANGELOG.md) with the finalized changelog draft. - Update other documents and scripts to use the new package names and links. 1. Verify the release Dockerfiles. 1. [Create NuGet packages](#nuget-packages) and publish them to [powershell-core feed][ps-core-feed]. 1. [Create the release tag](#release-tag) and push the tag to `PowerShell/PowerShell` repository. -1. Create the draft and publish the release in Github. +1. Create the draft and publish the release in GitHub. 1. Merge the `release-` branch to `master` in `powershell/powershell` and delete the `release-` branch. 1. Publish Linux packages to Microsoft YUM/APT repositories. -1. Trigger the release docker builds for Linux and Windows container images. - - Linux: push a branch named `docker` to `powershell/powershell` repository to trigger the build at [powershell docker hub](https://hub.docker.com/r/microsoft/powershell/builds/). - Delete the `docker` branch once the builds succeed. - - Windows: queue a new build in `PowerShell Windows Docker Build` on VSTS. -1. Verify the generated docker container images. 1. [Update the homebrew formula](#homebrew) for the macOS package. This task usually will be taken care of by the community, so we can wait for one day or two and see if the homebrew formula has already been updated, @@ -104,7 +99,7 @@ this package will contain actual PowerShell bits (i.e. it is not a meta-package). These bits are installed to `/opt/microsoft/powershell/6.0.0-alpha.8/`, where the version will change with each update -(and is the pre-release version). +(and is the prerelease version). On macOS, the prefix is `/usr/local`, instead of `/opt/microsoft` because it is derived from BSD. @@ -173,7 +168,7 @@ Start-PSPackage -Type zip -ReleaseTag v6.0.0-beta.1 -WindowsRuntime 'win7-x64' ## NuGet Packages -The NuGet packages for hosting PowerShell for Windows and non-Windows are being built in our release build pipeline. +The NuGet packages for hosting PowerShell for Windows and non-Windows are being built-in our release build pipeline. The assemblies from the individual Windows and Linux builds are consumed and packed into NuGet packages. These are then released to [powershell-core feed][ps-core-feed]. @@ -190,7 +185,7 @@ we create an [annotated tag][tag] that names the release. An annotated tag has a message (like a commit), and is *not* the same as a lightweight tag. Create one with `git tag -a v6.0.0-alpha.7 -m `, -and use the release change logs as the message. +and use the release changelogs as the message. Our convention is to prepend the `v` to the semantic version. The summary (first line) of the annotated tag message should be the full release title, e.g. 'v6.0.0-alpha.7 release of PowerShellCore'. @@ -215,30 +210,10 @@ There are 2 homebrew formulas: main and preview. Update it on stable releases. -1. Make sure that you have [homebrew cask](https://caskroom.github.io/). -1. `brew update` -1. `cd /usr/local/Homebrew/Library/Taps/caskroom/homebrew-cask/Casks` -1. Edit `./powershell.rb`, reference [file history](https://github.com/vors/homebrew-cask/commits/master/Casks/powershell.rb) for the guidelines: - 1. Update `version` - 1. Update `sha256` to the checksum of produced `.pkg` (note lower-case string for the consistent style) - 1. Update `checkpoint` value. To do that run `brew cask _appcast_checkpoint --calculate 'https://github.com/PowerShell/PowerShell/releases.atom'` -1. `brew cask style --fix ./powershell.rb`, make sure there are no errors -1. `brew cask audit --download ./powershell.rb`, make sure there are no errors -1. `brew cask upgrade powershell`, make sure that powershell was updates successfully -1. Commit your changes, send a PR to [homebrew-cask](https://github.com/caskroom/homebrew-cask) +1. Wait for a PR to show up in https://github.com/powershell/homebrew-tap, review and merge it. ### Preview Update it on preview releases. -1. Add [homebrew cask versions](https://github.com/Homebrew/homebrew-cask-versions): `brew tap homebrew/cask-versions` -1. `brew update` -1. `cd /usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask-versions/Casks` -1. Edit `./powershell-preview.rb`: - 1. Update `version` - 1. Update `sha256` to the checksum of produced `.pkg` (note lower-case string for the consistent style) - 1. Update `checkpoint` value. To do that run `brew cask _appcast_checkpoint --calculate 'https://github.com/PowerShell/PowerShell/releases.atom'` -1. `brew cask style --fix ./powershell-preview.rb`, make sure there are no errors -1. `brew cask audit --download ./powershell-preview.rb`, make sure there are no errors -1. `brew cask upgrade powershell-preview`, make sure that powershell was updates successfully -1. Commit your changes, send a PR to [homebrew-cask-versions](https://github.com/Homebrew/homebrew-cask-versions) +1. Wait for a PR to show up in https://github.com/powershell/homebrew-tap, review and merge it. diff --git a/docs/testing-guidelines/CodeCoverageAnalysis.md b/docs/testing-guidelines/CodeCoverageAnalysis.md deleted file mode 100644 index bee669ea747..00000000000 --- a/docs/testing-guidelines/CodeCoverageAnalysis.md +++ /dev/null @@ -1,109 +0,0 @@ -# Code coverage analysis for commit [de5f69c](https://codecov.io/gh/PowerShell/PowerShell/tree/de5f69cf942a85839c907f11a29cf9c09f9de8b4/src) - -Code coverage runs are enabled on daily Windows builds for PowerShell Core 6. -The results of the latest build are available at [codecov.io](https://codecov.io/gh/PowerShell/PowerShell) - -The goal of this analysis is to find the hot spots of missing coverage. -The metrics used for selection of these hot spots were: # missing lines and likelihood of code path usage. - -## Coverage Status - -The following table shows the status for the above commit, dated 2018-11-28 - -| Assembly | Hit % | -| -------- |:-----:| -| Microsoft.Management.Infrastructure.CimCmdlets | 48.18% | -| Microsoft.PowerShell.Commands.Diagnostics | 47.58% | -| Microsoft.PowerShell.Commands.Management | 61.06% | -| Microsoft.PowerShell.Commands.Utility | 70.76% | -| Microsoft.PowerShell.ConsoleHost | 46.39% | -| Microsoft.PowerShell.CoreCLR.Eventing | 37.84% | -| Microsoft.PowerShell.MarkdownRender | 70.68% | -| Microsoft.PowerShell.Security | 49.36% | -| Microsoft.WSMan.Management | 62.36% | -| System.Management.Automation | 63.35% | -| Microsoft.WSMan.Runtime/WSManSessionOption.cs | 100.00% | -| powershell/Program.cs | 100.00% | - -## Hot Spots with missing coverage - -### Microsoft.PowerShell.Commands.Management - -- [ ] Add tests for *-Item cmdlets. Especially for literal paths and error cases. [#4148](https://github.com/PowerShell/PowerShell/issues/4148) -- [ ] Lots of resource strings not covered. Will probably get covered when coverage is added for error cases. [#4148](https://github.com/PowerShell/PowerShell/issues/4148) - -### Microsoft.PowerShell.Commands.Utility - -- [ ] Add tests for Debug-Runspace [#4153](https://github.com/PowerShell/PowerShell/issues/4153) - -### Microsoft.PowerShell.ConsoleHost - -- [ ] Various options, DebugHandler and hosting modes like server, namedpipe etc. [#4155](https://github.com/PowerShell/PowerShell/issues/4155) - -### Microsoft.PowerShell.CoreCLR.Eventing - -- [ ] Add tests for ETW events. [#4156](https://github.com/PowerShell/PowerShell/issues/4156) - -### Microsoft.PowerShell.Security - -- [ ] Add tests for *-Acl cmdlets. [#4157](https://github.com/PowerShell/PowerShell/issues/4157) -- [ ] Add tests for *-AuthenticodeSignature cmdlets. [#4157](https://github.com/PowerShell/PowerShell/issues/4157) -- [ ] Add coverage to various utility methods under src/Microsoft.PowerShell.Security/security/Utils.cs [#4157](https://github.com/PowerShell/PowerShell/issues/4157) - -### Microsoft.WSMan.Management - -- [ ] Add tests for WSMan provider [#4158](https://github.com/PowerShell/PowerShell/issues/4158) -- [ ] Add tests for WSMan cmdlets [#4158](https://github.com/PowerShell/PowerShell/issues/4158) -- [ ] Add tests for CredSSP [#4158](https://github.com/PowerShell/PowerShell/issues/4158) - -### System.Management.Automation - -#### CoreCLR - -- [ ] Lots of non-windows code can be ifdef'ed out. [#3565](https://github.com/PowerShell/PowerShell/issues/3565) - -#### Engine - -- [ ] Add tests for Tab Completion of various types of input. [#4160](https://github.com/PowerShell/PowerShell/issues/4160) -- [ ] Add tests for debugging PS Jobs. [#4153](https://github.com/PowerShell/PowerShell/issues/4153) -- [ ] Remove Snapin code from CommandDiscovery. [#4118](https://github.com/PowerShell/PowerShell/issues/4118) -- [ ] Add tests SessionStateItem, SessionStateContainer error cases, dynamic parameters. Coverage possibly added by *-Item, *-ChildItem error case tests. [#4148](https://github.com/PowerShell/PowerShell/issues/4148) -- [ ] Add more tests using PSCredential [#4165](https://github.com/PowerShell/PowerShell/issues/4165) - -#### Remoting - -- [ ] Can PSProxyJobs be removed as it is for Workflows? -- [ ] Add more tests for PS Jobs. [#4166](https://github.com/PowerShell/PowerShell/issues/4166) -- [ ] Add more tests using -ThrottleLimit [#4166](https://github.com/PowerShell/PowerShell/issues/4166) -- [ ] Add tests for Register-PSSessionConfiguration [#4166](https://github.com/PowerShell/PowerShell/issues/4166) -- [ ] Add tests for Connect/Disconnect session [#4166](https://github.com/PowerShell/PowerShell/issues/4166) -- [ ] Add more tests for Start-Job's various options [#4166](https://github.com/PowerShell/PowerShell/issues/4166) - -#### Security - -- [ ] Add more tests under various ExecutionPolicy modes. [#4168](https://github.com/PowerShell/PowerShell/issues/4168) - -#### Utils - -- [ ] Add more error case test to improve coverage of src/System.Management.Automation/utils [#4169](https://github.com/PowerShell/PowerShell/issues/4169) - -#### Providers - -##### FileSystemProvider - -- [ ] Add tests for Mapped Network Drive [#4148](https://github.com/PowerShell/PowerShell/issues/4148) -- [ ] Add tests for *-Item alternate stream [#4148](https://github.com/PowerShell/PowerShell/issues/4148) -- [ ] Add tests for Get-ChildItem -path "file" [#4148](https://github.com/PowerShell/PowerShell/issues/4148) -- [ ] Add tests for Rename-Item for a directory [#4148](https://github.com/PowerShell/PowerShell/issues/4148) -- [ ] Add tests for Copy-Item over remote session [#4148](https://github.com/PowerShell/PowerShell/issues/4148) -- [ ] Add tests for various error conditions [#4148](https://github.com/PowerShell/PowerShell/issues/4148) - -##### RegistryProvider - -- [ ] Add tests for *-Item [#4148](https://github.com/PowerShell/PowerShell/issues/4148) -- [ ] Add tests for *-Acl [#4157](https://github.com/PowerShell/PowerShell/issues/4157) -- [ ] Add tests for error conditions [#4148](https://github.com/PowerShell/PowerShell/issues/4148) - -##### FunctionProvider - -- [ ] Add *-Item tests [#4148](https://github.com/PowerShell/PowerShell/issues/4148) diff --git a/experimental-feature-linux.json b/experimental-feature-linux.json new file mode 100644 index 00000000000..ca5b49878a4 --- /dev/null +++ b/experimental-feature-linux.json @@ -0,0 +1,8 @@ +[ + "PSFeedbackProvider", + "PSLoadAssemblyFromNativeCode", + "PSNativeWindowsTildeExpansion", + "PSSerializeJSONLongEnumAsNumber", + "PSRedirectToVariable", + "PSSubsystemPluginModel" +] diff --git a/experimental-feature-windows.json b/experimental-feature-windows.json new file mode 100644 index 00000000000..ca5b49878a4 --- /dev/null +++ b/experimental-feature-windows.json @@ -0,0 +1,8 @@ +[ + "PSFeedbackProvider", + "PSLoadAssemblyFromNativeCode", + "PSNativeWindowsTildeExpansion", + "PSSerializeJSONLongEnumAsNumber", + "PSRedirectToVariable", + "PSSubsystemPluginModel" +] diff --git a/global.json b/global.json index 2c34319a232..4d8816105f0 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "6.0.100-preview.2.21155.3" + "version": "10.0.100-preview.4.25258.110" } } diff --git a/nuget.config b/nuget.config index a8ed27bf99e..388a65572dd 100644 --- a/nuget.config +++ b/nuget.config @@ -2,8 +2,7 @@ - - + diff --git a/src/GlobalTools/PowerShell.Windows.x64/PowerShell.Windows.x64.csproj b/src/GlobalTools/PowerShell.Windows.x64/PowerShell.Windows.x64.csproj new file mode 100644 index 00000000000..49d607ebfed --- /dev/null +++ b/src/GlobalTools/PowerShell.Windows.x64/PowerShell.Windows.x64.csproj @@ -0,0 +1,33 @@ + + + + Exe + net10.0 + enable + enable + true + win-x64 + pwsh + $(PackageVersion) + true + ../../signing/visualstudiopublic.snk + + + + + + Modules\%(RecursiveDir)\%(FileName)%(Extension) + PreserveNewest + PreserveNewest + + + + + + + + + + + + diff --git a/src/GlobalTools/PowerShell.Windows.x64/Powershell_64.png b/src/GlobalTools/PowerShell.Windows.x64/Powershell_64.png new file mode 100644 index 00000000000..2a656ffc3c8 Binary files /dev/null and b/src/GlobalTools/PowerShell.Windows.x64/Powershell_64.png differ diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimAsyncOperation.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimAsyncOperation.cs index 49f39c8c019..023bf652b1d 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimAsyncOperation.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimAsyncOperation.cs @@ -206,10 +206,7 @@ protected void AddCimSessionProxy(CimSessionProxy sessionproxy) { lock (cimSessionProxyCacheLock) { - if (this.cimSessionProxyCache == null) - { - this.cimSessionProxyCache = new List(); - } + this.cimSessionProxyCache ??= new List(); if (!this.cimSessionProxyCache.Contains(sessionproxy)) { @@ -347,16 +344,14 @@ protected virtual void SubscribeToCimSessionProxyEvent(CimSessionProxy proxy) /// protected object GetBaseObject(object value) { - PSObject psObject = value as PSObject; - if (psObject == null) + if (value is not PSObject psObject) { return value; } else { object baseObject = psObject.BaseObject; - var arrayObject = baseObject as object[]; - if (arrayObject == null) + if (baseObject is not object[] arrayObject) { return baseObject; } @@ -383,8 +378,7 @@ protected object GetBaseObject(object value) /// The object. protected object GetReferenceOrReferenceArrayObject(object value, ref CimType referenceType) { - PSReference cimReference = value as PSReference; - if (cimReference != null) + if (value is PSReference cimReference) { object baseObject = GetBaseObject(cimReference.Value); if (!(baseObject is CimInstance cimInstance)) @@ -397,8 +391,7 @@ protected object GetReferenceOrReferenceArrayObject(object value, ref CimType re } else { - object[] cimReferenceArray = value as object[]; - if (cimReferenceArray == null) + if (value is not object[] cimReferenceArray) { return null; } @@ -531,10 +524,7 @@ private void Cleanup() } this.moreActionEvent.Dispose(); - if (this.ackedEvent != null) - { - this.ackedEvent.Dispose(); - } + this.ackedEvent?.Dispose(); DebugHelper.WriteLog("Cleanup complete.", 2); } diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimBaseAction.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimBaseAction.cs index e3e487a9533..4702e47e2f2 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimBaseAction.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimBaseAction.cs @@ -168,10 +168,7 @@ protected virtual void Dispose(bool disposing) if (disposing) { // Dispose managed resources. - if (this.completeEvent != null) - { - this.completeEvent.Dispose(); - } + this.completeEvent?.Dispose(); } // Call the appropriate methods to clean up diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimCommandBase.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimCommandBase.cs index 059f58a97b2..89f7478b513 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimCommandBase.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimCommandBase.cs @@ -57,7 +57,7 @@ internal class ParameterSetEntry /// Initializes a new instance of the class. /// /// - internal ParameterSetEntry(UInt32 mandatoryParameterCount) + internal ParameterSetEntry(uint mandatoryParameterCount) { this.MandatoryParameterCount = mandatoryParameterCount; this.IsDefaultParameterSet = false; @@ -80,7 +80,7 @@ internal ParameterSetEntry(ParameterSetEntry toClone) /// /// /// - internal ParameterSetEntry(UInt32 mandatoryParameterCount, bool isDefault) + internal ParameterSetEntry(uint mandatoryParameterCount, bool isDefault) { this.MandatoryParameterCount = mandatoryParameterCount; this.IsDefaultParameterSet = isDefault; @@ -104,7 +104,7 @@ internal void reset() /// /// Property MandatoryParameterCount /// - internal UInt32 MandatoryParameterCount { get; } = 0; + internal uint MandatoryParameterCount { get; } = 0; /// /// Property IsValueSet @@ -119,12 +119,12 @@ internal void reset() /// /// Property SetMandatoryParameterCount /// - internal UInt32 SetMandatoryParameterCount { get; set; } = 0; + internal uint SetMandatoryParameterCount { get; set; } = 0; /// /// Property SetMandatoryParameterCountAtBeginProcess /// - internal UInt32 SetMandatoryParameterCountAtBeginProcess { get; set; } = 0; + internal uint SetMandatoryParameterCountAtBeginProcess { get; set; } = 0; } /// @@ -390,10 +390,7 @@ internal string GetParameterSet() } // Looking for default parameter set - if (boundParameterSetName == null) - { - boundParameterSetName = defaultParameterSetName; - } + boundParameterSetName ??= defaultParameterSetName; // throw if still can not find the parameter set name if (boundParameterSetName == null) @@ -473,10 +470,7 @@ internal void SetParameter(object value, string parameterName) return; } - if (this.parameterBinder != null) - { - this.parameterBinder.SetParameter(parameterName, this.AtBeginProcess); - } + this.parameterBinder?.SetParameter(parameterName, this.AtBeginProcess); } #endregion @@ -579,10 +573,7 @@ protected void Dispose(bool disposing) protected virtual void DisposeInternal() { // Dispose managed resources. - if (this.operation != null) - { - this.operation.Dispose(); - } + this.operation?.Dispose(); } #endregion @@ -672,6 +663,7 @@ internal virtual CmdletOperationBase CmdletOperation /// Throw terminating error /// /// + [System.Diagnostics.CodeAnalysis.DoesNotReturn] internal void ThrowTerminatingError(Exception exception, string operation) { ErrorRecord errorRecord = new(exception, operation, ErrorCategory.InvalidOperation, this); diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetCimClass.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetCimClass.cs index 386482f11c5..c775325094b 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetCimClass.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetCimClass.cs @@ -97,7 +97,7 @@ public void GetCimClass(GetCimClassCommand cmdlet) { List proxys = new(); string nameSpace = ConstValue.GetNamespace(cmdlet.Namespace); - string className = (cmdlet.ClassName == null) ? @"*" : cmdlet.ClassName; + string className = cmdlet.ClassName ?? @"*"; CimGetCimClassContext context = new( cmdlet.ClassName, cmdlet.MethodName, @@ -165,6 +165,7 @@ private static void SetSessionProxyProperties( GetCimClassCommand cmdlet) { proxy.OperationTimeout = cmdlet.OperationTimeoutSec; + proxy.Amended = cmdlet.Amended; } /// diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetInstance.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetInstance.cs index 25400514401..371b06d9356 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetInstance.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetInstance.cs @@ -284,8 +284,7 @@ protected static string GetQuery(CimBaseCommand cmdlet) internal static bool IsClassNameQuerySet(CimBaseCommand cmdlet) { DebugHelper.WriteLogEx(); - GetCimInstanceCommand cmd = cmdlet as GetCimInstanceCommand; - if (cmd != null) + if (cmdlet is GetCimInstanceCommand cmd) { if (cmd.QueryDialect != null || cmd.SelectProperties != null || cmd.Filter != null) { @@ -299,8 +298,7 @@ internal static bool IsClassNameQuerySet(CimBaseCommand cmdlet) protected static string CreateQuery(CimBaseCommand cmdlet) { DebugHelper.WriteLogEx(); - GetCimInstanceCommand cmd = cmdlet as GetCimInstanceCommand; - if (cmd != null) + if (cmdlet is GetCimInstanceCommand cmd) { StringBuilder propertyList = new(); if (cmd.SelectProperties == null) diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimIndicationWatcher.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimIndicationWatcher.cs index 0c847265cfe..899e67495cc 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimIndicationWatcher.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimIndicationWatcher.cs @@ -153,7 +153,7 @@ public CimIndicationWatcher( string theNamespace, string queryDialect, string queryExpression, - UInt32 operationTimeout) + uint operationTimeout) { ValidationHelper.ValidateNoNullorWhiteSpaceArgument(queryExpression, queryExpressionParameterName); computerName = ConstValue.GetComputerName(computerName); @@ -173,7 +173,7 @@ public CimIndicationWatcher( string theNamespace, string queryDialect, string queryExpression, - UInt32 operationTimeout) + uint operationTimeout) { ValidationHelper.ValidateNoNullorWhiteSpaceArgument(queryExpression, queryExpressionParameterName); ValidationHelper.ValidateNoNullArgument(cimSession, cimSessionParameterName); @@ -192,7 +192,7 @@ private void Initialize( string theNameSpace, string theQueryDialect, string theQueryExpression, - UInt32 theOperationTimeout) + uint theOperationTimeout) { enableRaisingEvents = false; status = Status.Default; @@ -221,14 +221,11 @@ private void NewSubscriptionResultHandler(object src, CimSubscriptionEventArgs a if (temp != null) { // raise the event - CimSubscriptionResultEventArgs resultArgs = args as CimSubscriptionResultEventArgs; - if (resultArgs != null) + if (args is CimSubscriptionResultEventArgs resultArgs) temp(this, new CimIndicationEventInstanceEventArgs(resultArgs.Result)); - else + else if (args is CimSubscriptionExceptionEventArgs exceptionArgs) { - CimSubscriptionExceptionEventArgs exceptionArgs = args as CimSubscriptionExceptionEventArgs; - if (exceptionArgs != null) - temp(this, new CimIndicationEventExceptionEventArgs(exceptionArgs.Exception)); + temp(this, new CimIndicationEventExceptionEventArgs(exceptionArgs.Exception)); } } } @@ -242,7 +239,7 @@ private void NewSubscriptionResultHandler(object src, CimSubscriptionEventArgs a /// If set EnableRaisingEvents to false, which will be ignored /// /// - [BrowsableAttribute(false)] + [Browsable(false)] public bool EnableRaisingEvents { get @@ -378,7 +375,7 @@ internal void SetCmdlet(Cmdlet cmdlet) private string nameSpace; private string queryDialect; private string queryExpression; - private UInt32 operationTimeout; + private uint operationTimeout; #endregion #endregion } diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimNewCimInstance.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimNewCimInstance.cs index d7436fc0ec8..beb3e551d33 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimNewCimInstance.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimNewCimInstance.cs @@ -179,15 +179,14 @@ internal void GetCimInstance(CimInstance cimInstance, XOperationContextBase cont { DebugHelper.WriteLogEx(); - CimNewCimInstanceContext newCimInstanceContext = context as CimNewCimInstanceContext; - if (newCimInstanceContext == null) + if (context is not CimNewCimInstanceContext newCimInstanceContext) { DebugHelper.WriteLog("Invalid (null) CimNewCimInstanceContext", 1); return; } CimSessionProxy proxy = CreateCimSessionProxy(newCimInstanceContext.Proxy); - string nameSpace = (cimInstance.CimSystemProperties.Namespace == null) ? newCimInstanceContext.Namespace : cimInstance.CimSystemProperties.Namespace; + string nameSpace = cimInstance.CimSystemProperties.Namespace ?? newCimInstanceContext.Namespace; proxy.GetInstanceAsync(nameSpace, cimInstance); } @@ -295,8 +294,7 @@ private CimInstance CreateCimInstance( DebugHelper.WriteLog("Create and add new property to ciminstance: name = {0}; value = {1}; flags = {2}", 5, propertyName, propertyValue, flag); - PSReference cimReference = propertyValue as PSReference; - if (cimReference != null) + if (propertyValue is PSReference cimReference) { CimProperty newProperty = CimProperty.Create(propertyName, GetBaseObject(cimReference.Value), CimType.Reference, flag); cimInstance.CimInstanceProperties.Add(newProperty); diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRegisterCimIndication.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRegisterCimIndication.cs index e0fbb1aa095..692a5f123e8 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRegisterCimIndication.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRegisterCimIndication.cs @@ -123,7 +123,7 @@ public void RegisterCimIndication( string nameSpace, string queryDialect, string queryExpression, - UInt32 operationTimeout) + uint operationTimeout) { DebugHelper.WriteLogEx("queryDialect = '{0}'; queryExpression = '{1}'", 0, queryDialect, queryExpression); this.TargetComputerName = computerName; @@ -146,13 +146,11 @@ public void RegisterCimIndication( string nameSpace, string queryDialect, string queryExpression, - UInt32 operationTimeout) + uint operationTimeout) { DebugHelper.WriteLogEx("queryDialect = '{0}'; queryExpression = '{1}'", 0, queryDialect, queryExpression); - if (cimSession == null) - { - throw new ArgumentNullException(string.Format(CultureInfo.CurrentUICulture, CimCmdletStrings.NullArgument, @"cimSession")); - } + + ArgumentNullException.ThrowIfNull(cimSession, string.Format(CultureInfo.CurrentUICulture, CimCmdletStrings.NullArgument, nameof(cimSession))); this.TargetComputerName = cimSession.ComputerName; CimSessionProxy proxy = CreateSessionProxy(cimSession, operationTimeout); @@ -198,8 +196,7 @@ private void CimIndicationHandler(object cimSession, CmdletActionEventArgs actio } // NOTES: should move after this.Disposed, but need to log the exception - CimWriteError cimWriteError = actionArgs.Action as CimWriteError; - if (cimWriteError != null) + if (actionArgs.Action is CimWriteError cimWriteError) { this.Exception = cimWriteError.Exception; if (!this.ackedEvent.IsSet) @@ -221,11 +218,9 @@ private void CimIndicationHandler(object cimSession, CmdletActionEventArgs actio DebugHelper.WriteLog("Got an exception: {0}", 2, Exception); } - CimWriteResultObject cimWriteResultObject = actionArgs.Action as CimWriteResultObject; - if (cimWriteResultObject != null) + if (actionArgs.Action is CimWriteResultObject cimWriteResultObject) { - CimSubscriptionResult result = cimWriteResultObject.Result as CimSubscriptionResult; - if (result != null) + if (cimWriteResultObject.Result is CimSubscriptionResult result) { EventHandler temp = this.OnNewSubscriptionResult; if (temp != null) @@ -317,7 +312,7 @@ internal string TargetComputerName /// private CimSessionProxy CreateSessionProxy( string computerName, - UInt32 timeout) + uint timeout) { CimSessionProxy proxy = CreateCimSessionProxy(computerName); proxy.OperationTimeout = timeout; @@ -332,7 +327,7 @@ private CimSessionProxy CreateSessionProxy( /// private CimSessionProxy CreateSessionProxy( CimSession session, - UInt32 timeout) + uint timeout) { CimSessionProxy proxy = CreateCimSessionProxy(session); proxy.OperationTimeout = timeout; diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimResultObserver.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimResultObserver.cs index c27c7493ddb..389c45c8314 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimResultObserver.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimResultObserver.cs @@ -419,8 +419,7 @@ public override void OnNext(CimMethodResultBase value) string resultObjectPSType = null; PSObject resultObject = null; - CimMethodResult methodResult = value as CimMethodResult; - if (methodResult != null) + if (value is CimMethodResult methodResult) { resultObjectPSType = PSTypeCimMethodResult; resultObject = new PSObject(); @@ -431,8 +430,7 @@ public override void OnNext(CimMethodResultBase value) } else { - CimMethodStreamedResult methodStreamedResult = value as CimMethodStreamedResult; - if (methodStreamedResult != null) + if (value is CimMethodStreamedResult methodStreamedResult) { resultObjectPSType = PSTypeCimMethodStreamedResult; resultObject = new PSObject(); diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionOperations.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionOperations.cs index 28d530ed6a3..b8ebc9adaef 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionOperations.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionOperations.cs @@ -535,9 +535,8 @@ internal IEnumerable QuerySession( { if (this.curCimSessionsById.ContainsKey(id)) { - if (!sessionIds.Contains(id)) + if (sessionIds.Add(id)) { - sessionIds.Add(id); sessions.Add(this.curCimSessionsById[id].GetPSObject()); } } @@ -951,7 +950,7 @@ internal void AddSessionToCache(CimSession cimSession, XOperationContextBase con CimTestCimSessionContext testCimSessionContext = context as CimTestCimSessionContext; uint sessionId = this.sessionState.GenerateSessionId(); string originalSessionName = testCimSessionContext.CimSessionWrapper.Name; - string sessionName = (originalSessionName != null) ? originalSessionName : string.Format(CultureInfo.CurrentUICulture, @"{0}{1}", CimSessionState.CimSessionClassName, sessionId); + string sessionName = originalSessionName ?? string.Create(CultureInfo.CurrentUICulture, $"{CimSessionState.CimSessionClassName}{sessionId}"); // detach CimSession from the proxy object CimSession createdCimSession = testCimSessionContext.Proxy.Detach(); diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs index dfbfe090ddc..630bb34dfb1 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs @@ -539,14 +539,34 @@ private void CreateSetSession( #region set operation options + /// + /// Gets or sets a value indicating whether to retrieve localized information for the CIM class. + /// + public bool Amended + { + get => OperationOptions.Flags.HasFlag(CimOperationFlags.LocalizedQualifiers); + + set + { + if (value) + { + OperationOptions.Flags |= CimOperationFlags.LocalizedQualifiers; + } + else + { + OperationOptions.Flags &= ~CimOperationFlags.LocalizedQualifiers; + } + } + } + /// /// Set timeout value (seconds) of the operation. /// - public UInt32 OperationTimeout + public uint OperationTimeout { get { - return (UInt32)this.OperationOptions.Timeout.TotalSeconds; + return (uint)this.OperationOptions.Timeout.TotalSeconds; } set @@ -666,9 +686,9 @@ private void InitOption(CimOperationOptions operOptions) { this.OperationOptions = new CimOperationOptions(operOptions); } - else if (this.OperationOptions == null) + else { - this.OperationOptions = new CimOperationOptions(); + this.OperationOptions ??= new CimOperationOptions(); } this.EnableMethodResultStreaming = true; @@ -821,7 +841,7 @@ private void FireOperationDeletedEvent( /// /// /// - internal void WriteMessage(UInt32 channel, string message) + internal void WriteMessage(uint channel, string message) { DebugHelper.WriteLogEx("Channel = {0} message = {1}", 0, channel, message); try @@ -855,7 +875,7 @@ internal void WriteOperationStartMessage(string operation, Hashtable parameterLi parameters.Append(','); } - parameters.Append(string.Format(CultureInfo.CurrentUICulture, @"'{0}' = {1}", key, parameterList[key])); + parameters.Append(CultureInfo.CurrentUICulture, $@"'{key}' = {parameterList[key]}"); } } @@ -863,7 +883,7 @@ internal void WriteOperationStartMessage(string operation, Hashtable parameterLi CimCmdletStrings.CimOperationStart, operation, (parameters.Length == 0) ? "null" : parameters.ToString()); - WriteMessage((UInt32)CimWriteMessageChannel.Verbose, operationStartMessage); + WriteMessage((uint)CimWriteMessageChannel.Verbose, operationStartMessage); } /// @@ -878,7 +898,7 @@ internal void WriteOperationCompleteMessage(string operation) string operationCompleteMessage = string.Format(CultureInfo.CurrentUICulture, CimCmdletStrings.CimOperationCompleted, operation); - WriteMessage((UInt32)CimWriteMessageChannel.Verbose, operationCompleteMessage); + WriteMessage((uint)CimWriteMessageChannel.Verbose, operationCompleteMessage); } /// @@ -894,8 +914,8 @@ internal void WriteOperationCompleteMessage(string operation) public void WriteProgress(string activity, string currentOperation, string statusDescription, - UInt32 percentageCompleted, - UInt32 secondsRemaining) + uint percentageCompleted, + uint secondsRemaining) { DebugHelper.WriteLogEx("activity:{0}; currentOperation:{1}; percentageCompleted:{2}; secondsRemaining:{3}", 0, activity, currentOperation, percentageCompleted, secondsRemaining); @@ -1212,7 +1232,7 @@ public void EnumerateInstancesAsync(string namespaceName, string className) this.operationParameters.Add(@"className", className); this.WriteOperationStartMessage(this.operationName, this.operationParameters); CimAsyncMultipleResults asyncResult = this.CimSession.EnumerateInstancesAsync(namespaceName, className, this.OperationOptions); - string errorSource = string.Format(CultureInfo.CurrentUICulture, "{0}:{1}", namespaceName, className); + string errorSource = string.Create(CultureInfo.CurrentUICulture, $"{namespaceName}:{className}"); ConsumeCimInstanceAsync(asyncResult, new CimResultContext(errorSource)); } @@ -1294,7 +1314,7 @@ public void EnumerateClassesAsync(string namespaceName, string className) this.operationParameters.Add(@"className", className); this.WriteOperationStartMessage(this.operationName, this.operationParameters); CimAsyncMultipleResults asyncResult = this.CimSession.EnumerateClassesAsync(namespaceName, className, this.OperationOptions); - string errorSource = string.Format(CultureInfo.CurrentUICulture, "{0}:{1}", namespaceName, className); + string errorSource = string.Create(CultureInfo.CurrentUICulture, $"{namespaceName}:{className}"); ConsumeCimClassAsync(asyncResult, new CimResultContext(errorSource)); } @@ -1314,7 +1334,7 @@ public void GetClassAsync(string namespaceName, string className) this.operationParameters.Add(@"className", className); this.WriteOperationStartMessage(this.operationName, this.operationParameters); CimAsyncResult asyncResult = this.CimSession.GetClassAsync(namespaceName, className, this.OperationOptions); - string errorSource = string.Format(CultureInfo.CurrentUICulture, "{0}:{1}", namespaceName, className); + string errorSource = string.Create(CultureInfo.CurrentUICulture, $"{namespaceName}:{className}"); ConsumeCimClassAsync(asyncResult, new CimResultContext(errorSource)); } @@ -1368,7 +1388,7 @@ public void InvokeMethodAsync( this.operationParameters.Add(@"methodName", methodName); this.WriteOperationStartMessage(this.operationName, this.operationParameters); CimAsyncMultipleResults asyncResult = this.CimSession.InvokeMethodAsync(namespaceName, className, methodName, methodParameters, this.OperationOptions); - string errorSource = string.Format(CultureInfo.CurrentUICulture, "{0}:{1}", namespaceName, className); + string errorSource = string.Create(CultureInfo.CurrentUICulture, $"{namespaceName}:{className}"); ConsumeCimInvokeMethodResultAsync(asyncResult, className, methodName, new CimResultContext(errorSource)); } @@ -1473,7 +1493,7 @@ protected virtual void PostOperationDeleteEvent(OperationEventArgs args) /// The CimSession object managed by this proxy object, /// which is either created by constructor OR passed in by caller. /// The session will be closed while disposing this proxy object - /// if it is created by constuctor. + /// if it is created by constructor. /// internal CimSession CimSession { get; private set; } @@ -1879,7 +1899,7 @@ private CimSession CreateCimSessionByComputerName(string computerName) /// /// internal static CimSessionOptions CreateCimSessionOption(string computerName, - UInt32 timeout, CimCredential credential) + uint timeout, CimCredential credential) { DebugHelper.WriteLogEx(); @@ -2134,12 +2154,11 @@ internal class CimSessionProxyNewCimInstance : CimSessionProxy /// /// Initializes a new instance of the class. - /// + /// /// /// Create by given computer name. /// Then create wrapper object. /// - /// public CimSessionProxyNewCimInstance(string computerName, CimNewCimInstance operation) : base(computerName) { @@ -2149,6 +2168,7 @@ public CimSessionProxyNewCimInstance(string computerName, CimNewCimInstance oper /// /// Initializes a new instance of the class. /// + /// /// /// Create by given computer name /// and session options. diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteError.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteError.cs index d3ae3419fa4..f1ebe911603 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteError.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteError.cs @@ -39,14 +39,12 @@ internal static ErrorRecord ErrorRecordFromAnyException( { Debug.Assert(inner != null, "Caller should verify inner != null"); - CimException cimException = inner as CimException; - if (cimException != null) + if (inner is CimException cimException) { return CreateFromCimException(context, cimException, cimResultContext); } - var containsErrorRecord = inner as IContainsErrorRecord; - if (containsErrorRecord != null) + if (inner is IContainsErrorRecord containsErrorRecord) { return InitializeErrorRecord(context, exception: inner, diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteMessage.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteMessage.cs index cf927617235..f4b244b97f9 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteMessage.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteMessage.cs @@ -26,7 +26,7 @@ internal sealed class CimWriteMessage : CimBaseAction #region Properties - internal UInt32 Channel { get; } + internal uint Channel { get; } internal string Message { get; } @@ -35,7 +35,7 @@ internal sealed class CimWriteMessage : CimBaseAction /// /// Initializes a new instance of the class. /// - public CimWriteMessage(UInt32 channel, + public CimWriteMessage(uint channel, string message) { this.Channel = channel; diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteProgress.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteProgress.cs index 8e5632215a0..b99407ccc81 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteProgress.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteProgress.cs @@ -40,8 +40,8 @@ public CimWriteProgress( int theActivityID, string theCurrentOperation, string theStatusDescription, - UInt32 thePercentageCompleted, - UInt32 theSecondsRemaining) + uint thePercentageCompleted, + uint theSecondsRemaining) { this.Activity = theActivity; this.ActivityID = theActivityID; @@ -112,12 +112,12 @@ public override void Execute(CmdletOperationBase cmdlet) /// /// Gets the percentage completed of the given activity. /// - internal UInt32 PercentageCompleted { get; } + internal uint PercentageCompleted { get; } /// /// Gets the number of seconds remaining for the given activity. /// - internal UInt32 SecondsRemaining { get; } + internal uint SecondsRemaining { get; } #endregion } diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CmdletOperation.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CmdletOperation.cs index 3c49b1dcbb8..bd1a2751622 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CmdletOperation.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CmdletOperation.cs @@ -65,6 +65,7 @@ public virtual bool ShouldProcess(string verboseDescription, string verboseWarni return cmdlet.ShouldProcess(verboseDescription, verboseWarning, caption, out shouldProcessReason); } + [System.Diagnostics.CodeAnalysis.DoesNotReturn] public virtual void ThrowTerminatingError(ErrorRecord errorRecord) { cmdlet.ThrowTerminatingError(errorRecord); @@ -115,6 +116,7 @@ public virtual void WriteWarning(string text) /// Throw terminating error /// /// + [System.Diagnostics.CodeAnalysis.DoesNotReturn] internal void ThrowTerminatingError(Exception exception, string operation) { ErrorRecord errorRecord = new(exception, operation, ErrorCategory.InvalidOperation, this); @@ -230,8 +232,7 @@ public override void WriteObject(object sendToPipeline, XOperationContextBase co if (sendToPipeline is CimInstance) { - CimSetCimInstanceContext setContext = context as CimSetCimInstanceContext; - if (setContext != null) + if (context is CimSetCimInstanceContext setContext) { if (string.Equals(setContext.ParameterSetName, CimBaseCommand.QueryComputerSet, StringComparison.OrdinalIgnoreCase) || string.Equals(setContext.ParameterSetName, CimBaseCommand.QuerySessionSet, StringComparison.OrdinalIgnoreCase)) diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimAssociatedInstanceCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimAssociatedInstanceCommand.cs index ea592b40b90..6c4d0c94d2b 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimAssociatedInstanceCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimAssociatedInstanceCommand.cs @@ -110,7 +110,7 @@ public CimInstance InputObject /// [Alias(AliasOT)] [Parameter(ValueFromPipelineByPropertyName = true)] - public UInt32 OperationTimeoutSec { get; set; } + public uint OperationTimeoutSec { get; set; } /// /// @@ -232,8 +232,7 @@ protected override void ProcessRecord() protected override void EndProcessing() { CimGetAssociatedInstance operation = this.GetOperationAgent(); - if (operation != null) - operation.ProcessRemainActions(this.CmdletOperation); + operation?.ProcessRemainActions(this.CmdletOperation); } #endregion diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimClassCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimClassCommand.cs index d4ab679f0a7..1bededc485f 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimClassCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimClassCommand.cs @@ -43,6 +43,12 @@ public GetCimClassCommand() #region parameters + /// + /// Gets or sets flag to retrieve a localized data for WMI class. + /// + [Parameter] + public SwitchParameter Amended { get; set; } + /// /// /// The following is the definition of the input parameter "ClassName". @@ -79,7 +85,7 @@ public GetCimClassCommand() /// [Alias(AliasOT)] [Parameter(ValueFromPipelineByPropertyName = true)] - public UInt32 OperationTimeoutSec { get; set; } + public uint OperationTimeoutSec { get; set; } /// /// The following is the definition of the input parameter "Session". @@ -196,10 +202,7 @@ protected override void ProcessRecord() protected override void EndProcessing() { CimGetCimClass cimGetCimClass = this.GetOperationAgent(); - if (cimGetCimClass != null) - { - cimGetCimClass.ProcessRemainActions(this.CmdletOperation); - } + cimGetCimClass?.ProcessRemainActions(this.CmdletOperation); } #endregion diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimInstanceCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimInstanceCommand.cs index cc4de5e84e9..65eeae8e450 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimInstanceCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimInstanceCommand.cs @@ -260,7 +260,7 @@ public string Namespace /// [Alias(AliasOT)] [Parameter] - public UInt32 OperationTimeoutSec { get; set; } + public uint OperationTimeoutSec { get; set; } /// /// The following is the definition of the input parameter "InputObject". @@ -491,10 +491,7 @@ protected override void ProcessRecord() protected override void EndProcessing() { CimGetInstance cimGetInstance = this.GetOperationAgent(); - if (cimGetInstance != null) - { - cimGetInstance.ProcessRemainActions(this.CmdletOperation); - } + cimGetInstance?.ProcessRemainActions(this.CmdletOperation); } #endregion diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimSessionCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimSessionCommand.cs index a402d58d0d3..3289b6c86c0 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimSessionCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimSessionCommand.cs @@ -81,7 +81,7 @@ public string[] ComputerName ValueFromPipelineByPropertyName = true, ParameterSetName = SessionIdSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public UInt32[] Id + public uint[] Id { get { @@ -95,7 +95,7 @@ public UInt32[] Id } } - private UInt32[] id; + private uint[] id; /// /// The following is the definition of the input parameter "InstanceID". diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/InvokeCimMethodCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/InvokeCimMethodCommand.cs index f920094ae15..e3bc6f293b6 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/InvokeCimMethodCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/InvokeCimMethodCommand.cs @@ -373,7 +373,7 @@ public string Namespace /// [Alias(AliasOT)] [Parameter] - public UInt32 OperationTimeoutSec { get; set; } + public uint OperationTimeoutSec { get; set; } #endregion @@ -408,10 +408,7 @@ protected override void ProcessRecord() protected override void EndProcessing() { CimInvokeCimMethod cimInvokeMethod = this.GetOperationAgent(); - if (cimInvokeMethod != null) - { - cimInvokeMethod.ProcessRemainActions(this.CmdletOperation); - } + cimInvokeMethod?.ProcessRemainActions(this.CmdletOperation); } #endregion diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimInstanceCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimInstanceCommand.cs index b43b1e275d7..5843f25a26b 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimInstanceCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimInstanceCommand.cs @@ -223,7 +223,7 @@ public string Namespace /// [Alias(AliasOT)] [Parameter] - public UInt32 OperationTimeoutSec { get; set; } + public uint OperationTimeoutSec { get; set; } /// /// @@ -377,10 +377,7 @@ protected override void ProcessRecord() protected override void EndProcessing() { CimNewCimInstance cimNewCimInstance = this.GetOperationAgent(); - if (cimNewCimInstance != null) - { - cimNewCimInstance.ProcessRemainActions(this.CmdletOperation); - } + cimNewCimInstance?.ProcessRemainActions(this.CmdletOperation); } #endregion diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionCommand.cs index 2d1a91f5c0e..8b8e36cf829 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionCommand.cs @@ -54,7 +54,7 @@ public PasswordAuthenticationMechanism Authentication /// The default is the current user. /// [Parameter(Position = 1, ParameterSetName = CredentialParameterSet)] - [Credential()] + [Credential] public PSCredential Credential { get; set; } /// @@ -104,7 +104,7 @@ public PasswordAuthenticationMechanism Authentication /// [Alias(AliasOT)] [Parameter(ValueFromPipelineByPropertyName = true)] - public UInt32 OperationTimeoutSec + public uint OperationTimeoutSec { get { @@ -118,7 +118,7 @@ public UInt32 OperationTimeoutSec } } - private UInt32 operationTimeout; + private uint operationTimeout; internal bool operationTimeoutSet = false; /// @@ -136,7 +136,7 @@ public UInt32 OperationTimeoutSec /// This is specificly for wsman protocol. /// [Parameter(ValueFromPipelineByPropertyName = true)] - public UInt32 Port + public uint Port { get { @@ -150,7 +150,7 @@ public UInt32 Port } } - private UInt32 port; + private uint port; private bool portSet = false; /// @@ -234,8 +234,7 @@ internal void BuildSessionOptions(out CimSessionOptions outputOptions, out CimCr outputCredential = null; if (options != null) { - DComSessionOptions dcomOptions = options as DComSessionOptions; - if (dcomOptions != null) + if (options is DComSessionOptions dcomOptions) { bool conflict = false; string parameterName = string.Empty; @@ -334,10 +333,7 @@ protected override void DisposeInternal() base.DisposeInternal(); // Dispose managed resources. - if (this.cimNewSession != null) - { - this.cimNewSession.Dispose(); - } + this.cimNewSession?.Dispose(); } #endregion } diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionOptionCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionOptionCommand.cs index 0b62b79d96c..54956a9805a 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionOptionCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionOptionCommand.cs @@ -231,7 +231,7 @@ public Uri HttpPrefix /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = WSManParameterSet)] - public UInt32 MaxEnvelopeSizeKB + public uint MaxEnvelopeSizeKB { get { @@ -246,7 +246,7 @@ public UInt32 MaxEnvelopeSizeKB } } - private UInt32 maxenvelopesizekb; + private uint maxenvelopesizekb; private bool maxenvelopesizekbSet = false; /// @@ -299,7 +299,7 @@ public string ProxyCertificateThumbprint /// Ps Credential used by the proxy server when required by the server. /// [Parameter(ParameterSetName = WSManParameterSet)] - [Credential()] + [Credential] public PSCredential ProxyCredential { get diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/RegisterCimIndicationCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/RegisterCimIndicationCommand.cs index 246a95f37e3..b314691e41f 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/RegisterCimIndicationCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/RegisterCimIndicationCommand.cs @@ -124,7 +124,7 @@ public string QueryDialect /// [Alias(CimBaseCommand.AliasOT)] [Parameter] - public UInt32 OperationTimeoutSec { get; set; } + public uint OperationTimeoutSec { get; set; } /// /// The following is the definition of the input parameter "Session". @@ -205,7 +205,7 @@ protected override object GetSourceObject() case CimBaseCommand.ClassNameComputerSet: // validate the classname this.CheckArgument(); - tempQueryExpression = string.Format(CultureInfo.CurrentCulture, "Select * from {0}", this.ClassName); + tempQueryExpression = string.Create(CultureInfo.CurrentCulture, $"Select * from {this.ClassName}"); break; } @@ -227,10 +227,7 @@ protected override object GetSourceObject() break; } - if (watcher != null) - { - watcher.SetCmdlet(this); - } + watcher?.SetCmdlet(this); return watcher; } @@ -275,10 +272,7 @@ private static void newSubscriber_Unsubscribed( DebugHelper.WriteLogEx(); CimIndicationWatcher watcher = sender as CimIndicationWatcher; - if (watcher != null) - { - watcher.Stop(); - } + watcher?.Stop(); } #region private members diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimInstanceCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimInstanceCommand.cs index c3f1230020c..5ac8d129367 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimInstanceCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimInstanceCommand.cs @@ -154,7 +154,7 @@ public string Namespace /// [Alias(AliasOT)] [Parameter] - public UInt32 OperationTimeoutSec { get; set; } + public uint OperationTimeoutSec { get; set; } /// /// The following is the definition of the input parameter "InputObject". @@ -276,10 +276,7 @@ protected override void ProcessRecord() protected override void EndProcessing() { CimRemoveCimInstance cimRemoveInstance = this.GetOperationAgent(); - if (cimRemoveInstance != null) - { - cimRemoveInstance.ProcessRemainActions(this.CmdletOperation); - } + cimRemoveInstance?.ProcessRemainActions(this.CmdletOperation); } #endregion diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimSessionCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimSessionCommand.cs index 53937e57354..2f1a5ad026e 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimSessionCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimSessionCommand.cs @@ -108,7 +108,7 @@ public string[] ComputerName ValueFromPipelineByPropertyName = true, ParameterSetName = SessionIdSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public UInt32[] Id + public uint[] Id { get { @@ -122,7 +122,7 @@ public UInt32[] Id } } - private UInt32[] id; + private uint[] id; /// /// The following is the definition of the input parameter "InstanceId". diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/SetCimInstanceCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/SetCimInstanceCommand.cs index 25b3113e452..d190e5fafba 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/SetCimInstanceCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/SetCimInstanceCommand.cs @@ -151,7 +151,7 @@ public string Namespace /// [Alias(AliasOT)] [Parameter] - public UInt32 OperationTimeoutSec { get; set; } + public uint OperationTimeoutSec { get; set; } /// /// The following is the definition of the input parameter "InputObject". @@ -325,10 +325,7 @@ protected override void ProcessRecord() protected override void EndProcessing() { CimSetCimInstance cimSetCimInstance = this.GetOperationAgent(); - if (cimSetCimInstance != null) - { - cimSetCimInstance.ProcessRemainActions(this.CmdletOperation); - } + cimSetCimInstance?.ProcessRemainActions(this.CmdletOperation); } #endregion diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/Utils.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/Utils.cs index bab5c092894..adcab254231 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/Utils.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/Utils.cs @@ -88,7 +88,7 @@ internal static bool IsDefaultComputerName(string computerName) /// internal static IEnumerable GetComputerNames(IEnumerable computerNames) { - return (computerNames == null) ? NullComputerNames : computerNames; + return computerNames ?? NullComputerNames; } /// @@ -110,7 +110,7 @@ internal static string GetComputerName(string computerName) /// internal static string GetNamespace(string nameSpace) { - return (nameSpace == null) ? DefaultNameSpace : nameSpace; + return nameSpace ?? DefaultNameSpace; } /// @@ -122,7 +122,7 @@ internal static string GetNamespace(string nameSpace) /// internal static string GetQueryDialectWithDefault(string queryDialect) { - return (queryDialect == null) ? DefaultQueryDialect : queryDialect; + return queryDialect ?? DefaultQueryDialect; } } @@ -199,18 +199,8 @@ internal static string GetSourceCodeInformation(bool withFileName, int depth) { StackTrace trace = new(); StackFrame frame = trace.GetFrame(depth); - // if (withFileName) - // { - // return string.Format(CultureInfo.CurrentUICulture, "{0}#{1}:{2}:", frame.GetFileName()., frame.GetFileLineNumber(), frame.GetMethod().Name); - // } - // else - // { - // return string.Format(CultureInfo.CurrentUICulture, "{0}:", frame.GetMethod()); - // } - - return string.Format(CultureInfo.CurrentUICulture, "{0}::{1} ", - frame.GetMethod().DeclaringType.Name, - frame.GetMethod().Name); + + return string.Create(CultureInfo.CurrentUICulture, $"{frame.GetMethod().DeclaringType.Name}::{frame.GetMethod().Name} "); } #endregion @@ -371,10 +361,7 @@ internal static class ValidationHelper /// public static void ValidateNoNullArgument(object obj, string argumentName) { - if (obj == null) - { - throw new ArgumentNullException(argumentName); - } + ArgumentNullException.ThrowIfNull(obj, argumentName); } /// diff --git a/src/Microsoft.Management.UI.Internal/.globalconfig b/src/Microsoft.Management.UI.Internal/.globalconfig deleted file mode 100644 index fdb956e8529..00000000000 --- a/src/Microsoft.Management.UI.Internal/.globalconfig +++ /dev/null @@ -1,3 +0,0 @@ -is_global = true - -dotnet_analyzer_diagnostic.severity = none diff --git a/src/Microsoft.Management.UI.Internal/HelpWindow/HelpParagraphBuilder.cs b/src/Microsoft.Management.UI.Internal/HelpWindow/HelpParagraphBuilder.cs index 1d4bcacd893..fdb81a6d416 100644 --- a/src/Microsoft.Management.UI.Internal/HelpWindow/HelpParagraphBuilder.cs +++ b/src/Microsoft.Management.UI.Internal/HelpWindow/HelpParagraphBuilder.cs @@ -147,7 +147,7 @@ private static PSPropertyInfo GetProperty(PSObject psObj, string propertyName) /// /// PSObject that contains another PSObject as a property. /// Property name that contains the PSObject. - /// Property name in thye inner PSObject. + /// Property name in the inner PSObject. /// The string from the inner psObject property or null if it could not be retrieved. private static string GetInnerPSObjectPropertyString(PSObject psObj, string psObjectName, string propertyName) { @@ -276,7 +276,7 @@ private static string AddIndent(string str, string indentString) foreach (string line in lines) { // Indentation is not localized - returnValue.AppendFormat("{0}{1}\r\n", indentString, line); + returnValue.Append($"{indentString}{line}\r\n"); } if (returnValue.Length > 2) @@ -369,7 +369,7 @@ private void AddSyntax(bool setting, string sectionTitle) continue; } - string commandStart = string.Format(CultureInfo.CurrentCulture, "{0} ", commandName); + string commandStart = string.Create(CultureInfo.CurrentCulture, $"{commandName} "); this.AddText(HelpParagraphBuilder.AddIndent(commandStart), false); foreach (object parameterObj in parameterObjs) @@ -389,7 +389,7 @@ private void AddSyntax(bool setting, string sectionTitle) continue; } - string parameterType = parameterValue == null ? string.Empty : string.Format(CultureInfo.CurrentCulture, "<{0}>", parameterValue); + string parameterType = parameterValue == null ? string.Empty : string.Create(CultureInfo.CurrentCulture, $"<{parameterValue}>"); string parameterOptionalOpenBrace, parameterOptionalCloseBrace; @@ -607,7 +607,7 @@ private void AddMembers(bool setting, string sectionTitle) description = GetPropertyString(propertyTypeObject, "description"); } - memberText = string.Format(CultureInfo.CurrentCulture, " [{0}] {1}\r\n", propertyType, name); + memberText = string.Create(CultureInfo.CurrentCulture, $" [{propertyType}] {name}\r\n"); } } else if (string.Equals("method", type, StringComparison.OrdinalIgnoreCase)) @@ -690,14 +690,14 @@ private static void FormatMethodData(PSObject member, string name, out string me { parameterType = GetPropertyString(parameterTypeData, "name"); - // If there is no type for the paramter, we expect it is System.Object + // If there is no type for the parameter, we expect it is System.Object if (string.IsNullOrEmpty(parameterType)) { parameterType = "object"; } } - string paramString = string.Format(CultureInfo.CurrentCulture, "[{0}] ${1},", parameterType, parameterName); + string paramString = string.Create(CultureInfo.CurrentCulture, $"[{parameterType}] ${parameterName},"); parameterText.Append(paramString); } @@ -709,7 +709,7 @@ private static void FormatMethodData(PSObject member, string name, out string me } } - memberText = string.Format(CultureInfo.CurrentCulture, " [{0}] {1}({2})\r\n", returnType, name, parameterText); + memberText = string.Create(CultureInfo.CurrentCulture, $" [{returnType}] {name}({parameterText})\r\n"); } /// diff --git a/src/Microsoft.Management.UI.Internal/HelpWindow/HelpViewModel.cs b/src/Microsoft.Management.UI.Internal/HelpWindow/HelpViewModel.cs index 7cbe4b0c74e..7ab8681fef4 100644 --- a/src/Microsoft.Management.UI.Internal/HelpWindow/HelpViewModel.cs +++ b/src/Microsoft.Management.UI.Internal/HelpWindow/HelpViewModel.cs @@ -268,7 +268,7 @@ private void SetMatchesLabel() } /// - /// Called internally to notify when a proiperty changed. + /// Called internally to notify when a property changed. /// /// Property name. private void OnNotifyPropertyChanged(string propertyName) diff --git a/src/Microsoft.Management.UI.Internal/HelpWindow/ParagraphBuilder.cs b/src/Microsoft.Management.UI.Internal/HelpWindow/ParagraphBuilder.cs index 822e4c05026..77b1b5c966f 100644 --- a/src/Microsoft.Management.UI.Internal/HelpWindow/ParagraphBuilder.cs +++ b/src/Microsoft.Management.UI.Internal/HelpWindow/ParagraphBuilder.cs @@ -12,8 +12,8 @@ namespace Microsoft.Management.UI.Internal { /// /// Builds a paragraph based on Text + Bold + Highlight information. - /// Bold are the segments of thexct that should be bold, and Highlight are - /// the segments of thext that should be highlighted (like search results). + /// Bold are the segments of the text that should be bold, and Highlight are + /// the segments of the text that should be highlighted (like search results). /// internal class ParagraphBuilder : INotifyPropertyChanged { @@ -43,10 +43,7 @@ internal class ParagraphBuilder : INotifyPropertyChanged /// Paragraph we will be adding lines to in BuildParagraph. internal ParagraphBuilder(Paragraph paragraph) { - if (paragraph == null) - { - throw new ArgumentNullException("paragraph"); - } + ArgumentNullException.ThrowIfNull(paragraph); this.paragraph = paragraph; this.boldSpans = new List(); @@ -80,12 +77,12 @@ internal Paragraph Paragraph /// /// Called after all the AddText calls have been made to build the paragraph /// based on the current text. - /// This method goes over 3 collections simultaneouslly: + /// This method goes over 3 collections simultaneously: /// 1) characters in this.textBuilder /// 2) spans in this.boldSpans /// 3) spans in this.highlightedSpans /// And adds the minimal number of Inlines to the paragraph so that all - /// characters that should be bold and/or highlighed are. + /// characters that should be bold and/or highlighted are. /// internal void BuildParagraph() { @@ -128,7 +125,7 @@ internal void BuildParagraph() } /// - /// Highlights all ocurrences of . + /// Highlights all occurrences of . /// This is called after all calls to AddText have been made. /// /// Search string. @@ -185,10 +182,7 @@ internal void HighlightAllInstancesOf(string search, bool caseSensitive, bool wh /// True if the text should be bold. internal void AddText(string str, bool bold) { - if (str == null) - { - throw new ArgumentNullException("str"); - } + ArgumentNullException.ThrowIfNull(str); if (str.Length == 0) { @@ -240,16 +234,16 @@ private static void AddInline(Paragraph currentParagraph, bool currentBold, bool } /// - /// This is an auxiliar method in BuildParagraph to move the current bold or highlighed spans + /// This is an auxiliar method in BuildParagraph to move the current bold or highlighted spans /// according to the - /// The current bold and higlighed span should be ending ahead of the current position. + /// The current bold and highlighted span should be ending ahead of the current position. /// Moves and to the - /// propper span in according to the + /// proper span in according to the /// This is an auxiliar method in BuildParagraph. /// /// Current index within . /// Current span within . - /// Caracter position. This comes from a position within this.textBuilder. + /// Character position. This comes from a position within this.textBuilder. /// The collection of spans. This is either this.boldSpans or this.highlightedSpans. private static void MoveSpanToPosition(ref int currentSpanIndex, ref TextSpan? currentSpan, int caracterPosition, List allSpans) { @@ -270,7 +264,7 @@ private static void MoveSpanToPosition(ref int currentSpanIndex, ref TextSpan? c } // there is no span ending ahead of current position, so - // we set the current span to null to prevent unecessary comparisons against the currentSpan + // we set the current span to null to prevent unnecessary comparisons against the currentSpan currentSpan = null; } @@ -282,21 +276,14 @@ private static void MoveSpanToPosition(ref int currentSpanIndex, ref TextSpan? c /// Highlight length. private void AddHighlight(int start, int length) { - if (start < 0) - { - throw new ArgumentOutOfRangeException("start"); - } - - if (start + length > this.textBuilder.Length) - { - throw new ArgumentOutOfRangeException("length"); - } + ArgumentOutOfRangeException.ThrowIfNegative(start); + ArgumentOutOfRangeException.ThrowIfGreaterThan(start + length, this.textBuilder.Length, nameof(length)); this.highlightedSpans.Add(new TextSpan(start, length)); } /// - /// Called internally to notify when a proiperty changed. + /// Called internally to notify when a property changed. /// /// Property name. private void OnNotifyPropertyChanged(string propertyName) @@ -309,7 +296,7 @@ private void OnNotifyPropertyChanged(string propertyName) } /// - /// A text span used to mark bold and highlighed segments. + /// A text span used to mark bold and highlighted segments. /// internal struct TextSpan { @@ -330,15 +317,8 @@ internal struct TextSpan /// Index of the last character in the span. internal TextSpan(int start, int length) { - if (start < 0) - { - throw new ArgumentOutOfRangeException("start"); - } - - if (length < 1) - { - throw new ArgumentOutOfRangeException("length"); - } + ArgumentOutOfRangeException.ThrowIfNegative(start); + ArgumentOutOfRangeException.ThrowIfLessThan(length, 1); this.start = start; this.end = start + length - 1; diff --git a/src/Microsoft.Management.UI.Internal/HelpWindow/ParagraphSearcher.cs b/src/Microsoft.Management.UI.Internal/HelpWindow/ParagraphSearcher.cs index e71a27bac8b..c8b9907751d 100644 --- a/src/Microsoft.Management.UI.Internal/HelpWindow/ParagraphSearcher.cs +++ b/src/Microsoft.Management.UI.Internal/HelpWindow/ParagraphSearcher.cs @@ -43,8 +43,8 @@ internal ParagraphSearcher() /// The next highlight starting at the . internal Run MoveAndHighlightNextNextMatch(bool forward, TextPointer caretPosition) { - Debug.Assert(caretPosition != null, "a caret position is allways valid"); - Debug.Assert(caretPosition.Parent != null && caretPosition.Parent is Run, "a caret PArent is allways a valid Run"); + Debug.Assert(caretPosition != null, "a caret position is always valid"); + Debug.Assert(caretPosition.Parent != null && caretPosition.Parent is Run, "a caret Parent is always a valid Run"); Run caretRun = (Run)caretPosition.Parent; Run currentRun; @@ -56,10 +56,10 @@ internal Run MoveAndHighlightNextNextMatch(bool forward, TextPointer caretPositi } // If the caret is in the end of a highlight we move to the adjacent run - // It has to be in the end because if there is a match at the begining of the file + // It has to be in the end because if there is a match at the beginning of the file // and the caret has not been touched (so it is in the beginning of the file too) // we want to highlight this first match. - // Considering the caller allways set the caret to the end of the highlight + // Considering the caller always set the caret to the end of the highlight // The condition below works well for successive searchs // We also need to move to the adjacent run if the caret is at the first run and we // are moving backwards so that a search backwards when the first run is highlighted @@ -78,7 +78,7 @@ internal Run MoveAndHighlightNextNextMatch(bool forward, TextPointer caretPositi if (currentRun == null) { - // if we could not find a next highlight wrap arround + // if we could not find a next highlight wraparound currentRun = ParagraphSearcher.GetFirstOrLastRun(caretRun, forward); currentRun = ParagraphSearcher.GetNextMatch(currentRun, forward); } @@ -86,7 +86,7 @@ internal Run MoveAndHighlightNextNextMatch(bool forward, TextPointer caretPositi this.currentHighlightedMatch = currentRun; if (this.currentHighlightedMatch != null) { - // restore the curent highligthed background to current highlighted + // restore the curent highlighted background to current highlighted this.currentHighlightedMatch.Background = ParagraphSearcher.CurrentHighlightBrush; } @@ -202,10 +202,10 @@ private static Paragraph GetParagraph(Run run) } /// - /// Returns true if the run is the fiorst run of the paragraph. + /// Returns true if the run is the first run of the paragraph. /// /// Run to check. - /// True if the run is the fiorst run of the paragraph. + /// True if the run is the first run of the paragraph. private static bool IsFirstRun(Run run) { Paragraph paragraph = GetParagraph(run); @@ -221,7 +221,7 @@ private static bool IsFirstRun(Run run) /// The first or last run in the paragraph containing . private static Run GetFirstOrLastRun(Run caretRun, bool forward) { - Debug.Assert(caretRun != null, "a caret run is allways valid"); + Debug.Assert(caretRun != null, "a caret run is always valid"); Paragraph paragraph = GetParagraph(caretRun); diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/DataRoutedEventArgs.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/DataRoutedEventArgs.cs index cf65462c3b4..f75555b1da4 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/Common/DataRoutedEventArgs.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/DataRoutedEventArgs.cs @@ -11,7 +11,7 @@ namespace Microsoft.Management.UI.Internal { /// /// Routed event args which provide the ability to attach an - /// arbitrary peice of data. + /// arbitrary piece of data. /// /// There are no restrictions on type T. [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/DismissiblePopup.Generated.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/DismissiblePopup.Generated.cs index 76e3206f1f5..3a2768b17b0 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/Common/DismissiblePopup.Generated.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/DismissiblePopup.Generated.cs @@ -11,7 +11,7 @@ namespace Microsoft.Management.UI.Internal { /// - /// A popup which child controls can signal to be dimissed. + /// A popup which child controls can signal to be dismissed. /// /// /// If a control wants to dismiss the popup then they should execute the DismissPopupCommand on a target in the popup window. diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/IntegralConverter.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/IntegralConverter.cs index dff537f00bb..27c45ef288b 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/Common/IntegralConverter.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/IntegralConverter.cs @@ -31,10 +31,7 @@ public class IntegralConverter : IMultiValueConverter /// public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { - if (values == null) - { - throw new ArgumentNullException("values"); - } + ArgumentNullException.ThrowIfNull(values); if (values.Length != 2) { diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/InverseBooleanConverter.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/InverseBooleanConverter.cs index 55b57d76a3f..efefb08bae9 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/Common/InverseBooleanConverter.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/InverseBooleanConverter.cs @@ -22,10 +22,7 @@ public class InverseBooleanConverter : IValueConverter /// The inverted boolean value. public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { - if (value == null) - { - throw new ArgumentNullException("value"); - } + ArgumentNullException.ThrowIfNull(value); var boolValue = (bool)value; diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/IsEqualConverter.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/IsEqualConverter.cs index 83cd762198f..dbd806a64d6 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/Common/IsEqualConverter.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/IsEqualConverter.cs @@ -31,10 +31,7 @@ public class IsEqualConverter : IMultiValueConverter /// public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { - if (values == null) - { - throw new ArgumentNullException("values"); - } + ArgumentNullException.ThrowIfNull(values); if (values.Length != 2) { diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/KeyboardHelp.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/KeyboardHelp.cs index 90684f3f6cc..386b33996c1 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/Common/KeyboardHelp.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/KeyboardHelp.cs @@ -106,10 +106,10 @@ public static FocusNavigationDirection GetNavigationDirection(DependencyObject e /// /// Determines if the control key is pressed. /// - /// True if a control is is pressed. + /// True if a control is pressed. public static bool IsControlPressed() { - if (ModifierKeys.Control == (Keyboard.Modifiers & ModifierKeys.Control)) + if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control) { return true; } diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/ListOrganizerItem.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/ListOrganizerItem.cs index 469ed5aec77..24ff69bc35b 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/Common/ListOrganizerItem.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/ListOrganizerItem.cs @@ -153,7 +153,7 @@ private void RevertTextAndChangeFromEditToDisplayMode() private void ChangeFromEditToDisplayMode() { // NOTE : This is to resolve a race condition where clicking - // on the rename button causes the the edit box to change and + // on the rename button causes the edit box to change and // then have re-toggle. DependencyObject d = Mouse.DirectlyOver as DependencyObject; if (d == null || !(this.renameButton.IsAncestorOf(d) && Mouse.LeftButton == MouseButtonState.Pressed)) diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/ReadOnlyObservableAsyncCollection.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/ReadOnlyObservableAsyncCollection.cs index 7734bc4903c..99f08931aa4 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/Common/ReadOnlyObservableAsyncCollection.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/ReadOnlyObservableAsyncCollection.cs @@ -44,7 +44,7 @@ public ReadOnlyObservableAsyncCollection(IList list) /// Occurs when the collection changes, either by adding or removing an item. /// /// - /// see + /// see /// public event NotifyCollectionChangedEventHandler CollectionChanged; @@ -52,7 +52,7 @@ public ReadOnlyObservableAsyncCollection(IList list) /// Occurs when a property changes. /// /// - /// see + /// see /// public event PropertyChangedEventHandler PropertyChanged; #endregion Events diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/ScalableImage.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/ScalableImage.cs index b994bb1a29a..ea5f91adc87 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/Common/ScalableImage.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/ScalableImage.cs @@ -75,7 +75,7 @@ protected override void OnRender(DrawingContext drawingContext) } /// - /// Override of . + /// Override of . /// Make this control to respect the ClipToBounds attribute value. /// /// An instance of used for calculating an additional clip. diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/StateDescriptor.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/StateDescriptor.cs index be8cc841757..b1a82c0d079 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/Common/StateDescriptor.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/StateDescriptor.cs @@ -12,7 +12,6 @@ namespace Microsoft.Management.UI.Internal /// Base proxy class for other classes which wish to have save and restore functionality. /// /// There are no restrictions on T. - [Serializable] [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public abstract class StateDescriptor { diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/StringFormatConverter.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/StringFormatConverter.cs index a2e2b144ad6..d8cf0b253aa 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/Common/StringFormatConverter.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/StringFormatConverter.cs @@ -23,10 +23,7 @@ public class StringFormatConverter : IValueConverter /// The formatted string. public object Convert(object value, Type targetType, Object parameter, CultureInfo culture) { - if (parameter == null) - { - throw new ArgumentNullException("parameter"); - } + ArgumentNullException.ThrowIfNull(parameter); string str = (string)value; string formatString = (string)parameter; diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/Utilities.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/Utilities.cs index c6bcfcb1737..9cc60c8411c 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/Common/Utilities.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/Utilities.cs @@ -83,10 +83,7 @@ public static class Utilities /// The specified value is a null reference. public static bool AreAllItemsOfType(IEnumerable items) { - if (items == null) - { - throw new ArgumentNullException("items"); - } + ArgumentNullException.ThrowIfNull(items); foreach (object item in items) { @@ -108,10 +105,7 @@ public static bool AreAllItemsOfType(IEnumerable items) /// The specified value is a null reference. public static T Find(this IEnumerable items) { - if (items == null) - { - throw new ArgumentNullException("items"); - } + ArgumentNullException.ThrowIfNull(items); foreach (object item in items) { diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/VisualToAncestorDataConverter.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/VisualToAncestorDataConverter.cs index 85a7d00a61f..868e9a9b25b 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/Common/VisualToAncestorDataConverter.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/VisualToAncestorDataConverter.cs @@ -27,15 +27,9 @@ public class VisualToAncestorDataConverter : IValueConverter /// The specified value is a null reference. public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - if (value == null) - { - throw new ArgumentNullException("value"); - } + ArgumentNullException.ThrowIfNull(value); - if (parameter == null) - { - throw new ArgumentNullException("parameter"); - } + ArgumentNullException.ThrowIfNull(parameter); Type dataType = (Type)parameter; diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/WeakEventListener.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/WeakEventListener.cs index cc18509092f..d005dd909ee 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/Common/WeakEventListener.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/WeakEventListener.cs @@ -20,10 +20,7 @@ internal class WeakEventListener : IWeakEventListener where TEventAr /// The handler for the event. public WeakEventListener(EventHandler handler) { - if (handler == null) - { - throw new ArgumentNullException("handler"); - } + ArgumentNullException.ThrowIfNull(handler); this.realHander = handler; } diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/WpfHelp.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/WpfHelp.cs index 23ff80d9974..628723b089a 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/Common/WpfHelp.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/WpfHelp.cs @@ -153,10 +153,7 @@ public bool IsEmpty /// The specified value does not have a parent that supports removal. public static void RemoveFromParent(FrameworkElement element) { - if (element == null) - { - throw new ArgumentNullException("element"); - } + ArgumentNullException.ThrowIfNull(element); // If the element has already been detached, do nothing \\ if (element.Parent == null) @@ -215,15 +212,9 @@ public static void RemoveFromParent(FrameworkElement element) /// The specified value does not have a parent that supports removal. public static void AddChild(FrameworkElement parent, FrameworkElement element) { - if (element == null) - { - throw new ArgumentNullException("element"); - } + ArgumentNullException.ThrowIfNull(element); - if (parent == null) - { - throw new ArgumentNullException("element"); - } + ArgumentNullException.ThrowIfNull(parent, nameof(element)); ContentControl parentContentControl = parent as ContentControl; @@ -310,10 +301,8 @@ public static List FindVisualChildren(DependencyObject obj) where T : DependencyObject { Debug.Assert(obj != null, "obj is null"); - if (obj == null) - { - throw new ArgumentNullException("obj"); - } + + ArgumentNullException.ThrowIfNull(obj); List childrenOfType = new List(); @@ -348,10 +337,7 @@ public static List FindVisualChildren(DependencyObject obj) public static T FindVisualAncestorData(this DependencyObject obj) where T : class { - if (obj == null) - { - throw new ArgumentNullException("obj"); - } + ArgumentNullException.ThrowIfNull(obj); FrameworkElement parent = obj.FindVisualAncestor(); @@ -381,10 +367,7 @@ public static T FindVisualAncestorData(this DependencyObject obj) /// The specified value is a null reference. public static T FindVisualAncestor(this DependencyObject @object) where T : class { - if (@object == null) - { - throw new ArgumentNullException("object"); - } + ArgumentNullException.ThrowIfNull(@object, nameof(@object)); DependencyObject parent = VisualTreeHelper.GetParent(@object); @@ -413,10 +396,7 @@ public static T FindVisualAncestor(this DependencyObject @object) where T : c /// The specified value is a null reference. public static bool TryExecute(this RoutedCommand command, object parameter, IInputElement target) { - if (command == null) - { - throw new ArgumentNullException("command"); - } + ArgumentNullException.ThrowIfNull(command); if (command.CanExecute(parameter, target)) { @@ -437,15 +417,8 @@ public static bool TryExecute(this RoutedCommand command, object parameter, IInp /// The reference to the child, or null if the template part wasn't found. public static T GetOptionalTemplateChild(Control templateParent, string childName) where T : FrameworkElement { - if (templateParent == null) - { - throw new ArgumentNullException("templateParent"); - } - - if (string.IsNullOrEmpty(childName)) - { - throw new ArgumentNullException("childName"); - } + ArgumentNullException.ThrowIfNull(templateParent); + ArgumentException.ThrowIfNullOrEmpty(childName); object templatePart = templateParent.Template.FindName(childName, templateParent); T item = templatePart as T; @@ -566,10 +539,7 @@ public static RoutedPropertyChangedEventArgs CreateRoutedPropertyChangedEvent /// The specified index is not valid for the specified collection. public static void ChangeIndex(ItemCollection items, object item, int newIndex) { - if (items == null) - { - throw new ArgumentNullException("items"); - } + ArgumentNullException.ThrowIfNull(items); if (!items.Contains(item)) { diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/CommonControls/ResizerGripThicknessConverter.cs b/src/Microsoft.Management.UI.Internal/ManagementList/CommonControls/ResizerGripThicknessConverter.cs index 4cdf51f63f6..c371a6391b5 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/CommonControls/ResizerGripThicknessConverter.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/CommonControls/ResizerGripThicknessConverter.cs @@ -38,10 +38,7 @@ public ResizerGripThicknessConverter() /// A converted value. If the method returns nullNothingnullptra null reference (Nothing in Visual Basic), the valid null value is used. public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { - if (values == null) - { - throw new ArgumentNullException("values"); - } + ArgumentNullException.ThrowIfNull(values); if (object.ReferenceEquals(values[0], DependencyProperty.UnsetValue) || object.ReferenceEquals(values[1], DependencyProperty.UnsetValue)) diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/DefaultFilterRuleCustomizationFactory.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/DefaultFilterRuleCustomizationFactory.cs index bd5faf32d63..cd5e40a8bd9 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/DefaultFilterRuleCustomizationFactory.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/DefaultFilterRuleCustomizationFactory.cs @@ -36,10 +36,7 @@ public override IPropertyValueGetter PropertyValueGetter set { - if (value == null) - { - throw new ArgumentNullException("value"); - } + ArgumentNullException.ThrowIfNull(value); this.propertyValueGetter = value; } @@ -106,15 +103,9 @@ public override ICollection CreateDefaultFilterRulesForPropertyValue /// public override void TransferValues(FilterRule oldRule, FilterRule newRule) { - if (oldRule == null) - { - throw new ArgumentNullException("oldRule"); - } + ArgumentNullException.ThrowIfNull(oldRule); - if (newRule == null) - { - throw new ArgumentNullException("newRule"); - } + ArgumentNullException.ThrowIfNull(newRule); if (this.TryTransferValuesAsSingleValueComparableValueFilterRule(oldRule, newRule)) { @@ -130,10 +121,7 @@ public override void TransferValues(FilterRule oldRule, FilterRule newRule) /// public override void ClearValues(FilterRule rule) { - if (rule == null) - { - throw new ArgumentNullException("rule"); - } + ArgumentNullException.ThrowIfNull(rule); if (this.TryClearValueFromSingleValueComparableValueFilterRule(rule)) { @@ -163,10 +151,7 @@ public override void ClearValues(FilterRule rule) /// public override string GetErrorMessageForInvalidValue(string value, Type typeToParseTo) { - if (typeToParseTo == null) - { - throw new ArgumentNullException("typeToParseTo"); - } + ArgumentNullException.ThrowIfNull(typeToParseTo); bool isNumericType = typeToParseTo == typeof(byte) || typeToParseTo == typeof(sbyte) @@ -222,10 +207,7 @@ private object GetValueFromValidatingValue(FilterRule rule, string propertyName) Debug.Assert(rule != null && !string.IsNullOrEmpty(propertyName), "rule and propertyname are not null"); // NOTE: This isn't needed but OACR is complaining - if (rule == null) - { - throw new ArgumentNullException("rule"); - } + ArgumentNullException.ThrowIfNull(rule); Type ruleType = rule.GetType(); @@ -241,10 +223,7 @@ private void SetValueOnValidatingValue(FilterRule rule, string propertyName, obj Debug.Assert(rule != null && !string.IsNullOrEmpty(propertyName), "rule and propertyname are not null"); // NOTE: This isn't needed but OACR is complaining - if (rule == null) - { - throw new ArgumentNullException("rule"); - } + ArgumentNullException.ThrowIfNull(rule); Type ruleType = rule.GetType(); diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterEvaluator.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterEvaluator.cs index 2b33153a983..cf885d813c5 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterEvaluator.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterEvaluator.cs @@ -9,7 +9,7 @@ namespace Microsoft.Management.UI.Internal { /// - /// The FilterEvaluator class is responsible for allowing the registeration of + /// The FilterEvaluator class is responsible for allowing the registration of /// the FilterExpressionProviders and producing a FilterExpression composed of /// the FilterExpression returned from the providers. /// @@ -145,10 +145,7 @@ public FilterExpressionNode FilterExpression /// public void AddFilterExpressionProvider(IFilterExpressionProvider provider) { - if (provider == null) - { - throw new ArgumentNullException("provider"); - } + ArgumentNullException.ThrowIfNull(provider); this.filterExpressionProviders.Add(provider); provider.FilterExpressionChanged += this.FilterProvider_FilterExpressionChanged; @@ -162,10 +159,7 @@ public void AddFilterExpressionProvider(IFilterExpressionProvider provider) /// public void RemoveFilterExpressionProvider(IFilterExpressionProvider provider) { - if (provider == null) - { - throw new ArgumentNullException("provider"); - } + ArgumentNullException.ThrowIfNull(provider); this.filterExpressionProviders.Remove(provider); provider.FilterExpressionChanged -= this.FilterProvider_FilterExpressionChanged; diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterExceptionEventArgs.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterExceptionEventArgs.cs index b7a26757b21..77460f61fc9 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterExceptionEventArgs.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterExceptionEventArgs.cs @@ -32,10 +32,7 @@ public Exception Exception /// public FilterExceptionEventArgs(Exception exception) { - if (exception == null) - { - throw new ArgumentNullException("exception"); - } + ArgumentNullException.ThrowIfNull(exception); this.Exception = exception; } diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterExpressionNodes/FilterExpressionAndOperatorNode.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterExpressionNodes/FilterExpressionAndOperatorNode.cs index f255973dce8..0227362bf28 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterExpressionNodes/FilterExpressionAndOperatorNode.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterExpressionNodes/FilterExpressionAndOperatorNode.cs @@ -52,10 +52,7 @@ public FilterExpressionAndOperatorNode() /// public FilterExpressionAndOperatorNode(IEnumerable children) { - if (children == null) - { - throw new ArgumentNullException("children"); - } + ArgumentNullException.ThrowIfNull(children); this.children.AddRange(children); } diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterExpressionNodes/FilterExpressionOperandNode.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterExpressionNodes/FilterExpressionOperandNode.cs index f6bfd17377b..3161dc30283 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterExpressionNodes/FilterExpressionOperandNode.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterExpressionNodes/FilterExpressionOperandNode.cs @@ -38,10 +38,7 @@ public FilterRule Rule /// public FilterExpressionOperandNode(FilterRule rule) { - if (rule == null) - { - throw new ArgumentNullException("rule"); - } + ArgumentNullException.ThrowIfNull(rule); this.Rule = rule; } diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterExpressionNodes/FilterExpressionOrOperatorNode.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterExpressionNodes/FilterExpressionOrOperatorNode.cs index 201316a433e..ff92e42cf2d 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterExpressionNodes/FilterExpressionOrOperatorNode.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterExpressionNodes/FilterExpressionOrOperatorNode.cs @@ -52,10 +52,7 @@ public FilterExpressionOrOperatorNode() /// public FilterExpressionOrOperatorNode(IEnumerable children) { - if (children == null) - { - throw new ArgumentNullException("children"); - } + ArgumentNullException.ThrowIfNull(children); this.children.AddRange(children); } diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRuleCustomizationFactory.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRuleCustomizationFactory.cs index 75019cdbf5d..b61c9933aef 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRuleCustomizationFactory.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRuleCustomizationFactory.cs @@ -32,10 +32,7 @@ public static FilterRuleCustomizationFactory FactoryInstance set { - if (value == null) - { - throw new ArgumentNullException("value"); - } + ArgumentNullException.ThrowIfNull(value); factoryInstance = value; } 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 e75cd59f17a..8362a035156 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/ComparableValueFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/ComparableValueFilterRule.cs @@ -12,10 +12,26 @@ namespace Microsoft.Management.UI.Internal /// /// The generic parameter. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public abstract class ComparableValueFilterRule : FilterRule where T : IComparable { + /// + /// Initializes a new instance of the class. + /// + protected ComparableValueFilterRule() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + protected ComparableValueFilterRule(ComparableValueFilterRule source) + : base(source) + { + this.DefaultNullValueEvaluation = source.DefaultNullValueEvaluation; + } + #region Properties /// 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 ea74ee062f9..c5d4f36fe55 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/DoesNotEqualFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/DoesNotEqualFilterRule.cs @@ -12,12 +12,11 @@ namespace Microsoft.Management.UI.Internal /// /// The generic parameter. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class DoesNotEqualFilterRule : EqualsFilterRule where T : IComparable { /// - /// Initializes a new instance of the DoesNotEqualFilterRule class. + /// Initializes a new instance of the class. /// public DoesNotEqualFilterRule() { @@ -25,6 +24,15 @@ public DoesNotEqualFilterRule() this.DefaultNullValueEvaluation = true; } + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + public DoesNotEqualFilterRule(DoesNotEqualFilterRule source) + : base(source) + { + } + /// /// Determines if item is not equal to Value. /// 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 5f21f57292b..34a1ecb722d 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/EqualsFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/EqualsFilterRule.cs @@ -13,18 +13,26 @@ namespace Microsoft.Management.UI.Internal /// /// The generic parameter. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class EqualsFilterRule : SingleValueComparableValueFilterRule where T : IComparable { /// - /// Initializes a new instance of the EqualsFilterRule class. + /// Initializes a new instance of the class. /// public EqualsFilterRule() { this.DisplayName = UICultureResources.FilterRule_Equals; } + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + public EqualsFilterRule(EqualsFilterRule source) + : base(source) + { + } + /// /// Determines if item is equal to Value. /// 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 1c2fc523e86..f18c89addf9 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/FilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/FilterRule.cs @@ -8,9 +8,8 @@ namespace Microsoft.Management.UI.Internal /// /// The base class for all filtering rules. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - public abstract class FilterRule : IEvaluate + public abstract class FilterRule : IEvaluate, IDeepCloneable { /// /// Gets a value indicating whether the FilterRule can be @@ -34,15 +33,26 @@ public string DisplayName } /// - /// Initializes a new instance of the FilterRule class. + /// Initializes a new instance of the class. /// protected FilterRule() { - // HACK : Is there a way to statically enforce this? No... not ISerializable... - if (!this.GetType().IsSerializable) - { - throw new InvalidOperationException("FilterRules must be serializable."); - } + } + + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + protected FilterRule(FilterRule source) + { + ArgumentNullException.ThrowIfNull(source); + this.DisplayName = source.DisplayName; + } + + /// + public object DeepClone() + { + return Activator.CreateInstance(this.GetType(), new object[] { this }); } /// @@ -58,7 +68,6 @@ protected FilterRule() /// /// Occurs when the values of this rule changes. /// - [field: NonSerialized] public event EventHandler EvaluationResultInvalidated; /// 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 1ccc3d1d227..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 { @@ -27,34 +23,8 @@ public static class FilterRuleExtensions /// public static FilterRule DeepCopy(this FilterRule rule) { - if (rule == null) - { - throw new ArgumentNullException("rule"); - } - - Debug.Assert(rule.GetType().IsSerializable, "rule is serializable"); - - BinaryFormatter formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone)); - MemoryStream ms = new MemoryStream(); - - FilterRule copy = null; - try - { -#pragma warning disable SYSLIB0011 - formatter.Serialize(ms, rule); -#pragma warning restore SYSLIB0011 - - ms.Position = 0; -#pragma warning disable SYSLIB0011 - copy = (FilterRule)formatter.Deserialize(ms); -#pragma warning restore SYSLIB0011 - } - finally - { - ms.Close(); - } - - return copy; + ArgumentNullException.ThrowIfNull(rule); + 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 b54508157a6..f51093510ec 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsBetweenFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsBetweenFilterRule.cs @@ -15,7 +15,6 @@ namespace Microsoft.Management.UI.Internal /// /// The generic parameter. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class IsBetweenFilterRule : ComparableValueFilterRule where T : IComparable { @@ -56,7 +55,7 @@ public ValidatingValue EndValue #region Ctor /// - /// Initializes a new instance of the IsBetweenFilterRule class. + /// Initializes a new instance of the class. /// public IsBetweenFilterRule() { @@ -69,6 +68,20 @@ public IsBetweenFilterRule() this.EndValue.PropertyChanged += this.Value_PropertyChanged; } + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + public IsBetweenFilterRule(IsBetweenFilterRule source) + : base(source) + { + this.StartValue = (ValidatingValue)source.StartValue.DeepClone(); + this.StartValue.PropertyChanged += this.Value_PropertyChanged; + + this.EndValue = (ValidatingValue)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 8e8b91087ef..71bb7e23e7c 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsEmptyFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsEmptyFilterRule.cs @@ -9,18 +9,26 @@ namespace Microsoft.Management.UI.Internal /// The IsEmptyFilterRule evaluates an item to determine whether it /// is empty or not. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class IsEmptyFilterRule : FilterRule { /// - /// Initializes a new instance of the IsEmptyFilterRule class. + /// Initializes a new instance of the class. /// public IsEmptyFilterRule() { this.DisplayName = UICultureResources.FilterRule_IsEmpty; } + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + public IsEmptyFilterRule(IsEmptyFilterRule source) + : base(source) + { + } + /// /// Gets a values indicating whether the supplied item is empty. /// 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 bd9af169e82..6c7d16f312a 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsGreaterThanFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsGreaterThanFilterRule.cs @@ -13,18 +13,26 @@ namespace Microsoft.Management.UI.Internal /// /// The generic parameter. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class IsGreaterThanFilterRule : SingleValueComparableValueFilterRule where T : IComparable { /// - /// Initializes a new instance of the IsGreaterThanFilterRule class. + /// Initializes a new instance of the class. /// public IsGreaterThanFilterRule() { this.DisplayName = UICultureResources.FilterRule_GreaterThanOrEqual; } + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + public IsGreaterThanFilterRule(IsGreaterThanFilterRule source) + : base(source) + { + } + /// /// Determines if item is greater than Value. /// 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 db3bc01f810..e1dc3268cc5 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsLessThanFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsLessThanFilterRule.cs @@ -13,18 +13,26 @@ namespace Microsoft.Management.UI.Internal /// /// The generic parameter. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class IsLessThanFilterRule : SingleValueComparableValueFilterRule where T : IComparable { /// - /// Initializes a new instance of the IsLessThanFilterRule class. + /// Initializes a new instance of the class. /// public IsLessThanFilterRule() { this.DisplayName = UICultureResources.FilterRule_LessThanOrEqual; } + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + public IsLessThanFilterRule(IsLessThanFilterRule source) + : base(source) + { + } + /// /// Determines if item is less than Value. /// 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 c9bfc7519a0..711caee9874 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsNotEmptyFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsNotEmptyFilterRule.cs @@ -9,18 +9,26 @@ namespace Microsoft.Management.UI.Internal /// The IsNotEmptyFilterRule evaluates an item to determine whether it /// is empty or not. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class IsNotEmptyFilterRule : IsEmptyFilterRule { /// - /// Initializes a new instance of the IsNotEmptyFilterRule class. + /// Initializes a new instance of the class. /// public IsNotEmptyFilterRule() { this.DisplayName = UICultureResources.FilterRule_IsNotEmpty; } + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + public IsNotEmptyFilterRule(IsNotEmptyFilterRule source) + : base(source) + { + } + /// /// Gets a values indicating whether the supplied item is not empty. /// 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 924ffc02af8..cb6eacaaff3 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsNotEmptyValidationRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsNotEmptyValidationRule.cs @@ -8,7 +8,6 @@ namespace Microsoft.Management.UI.Internal /// /// The IsNotEmptyValidationRule checks a value to see if a value is not empty. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class IsNotEmptyValidationRule : DataErrorInfoValidationRule { @@ -51,6 +50,14 @@ public override DataErrorInfoValidationResult Validate(object value, System.Glob } } + /// + 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 c29715419ce..8c32530be8c 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/PropertiesTextContainsFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/PropertiesTextContainsFilterRule.cs @@ -11,7 +11,6 @@ namespace Microsoft.Management.UI.Internal /// /// Represents a filter rule that searches for text within properties on an object. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class PropertiesTextContainsFilterRule : TextFilterRule { @@ -29,6 +28,17 @@ public PropertiesTextContainsFilterRule() this.EvaluationResultInvalidated += this.PropertiesTextContainsFilterRule_EvaluationResultInvalidated; } + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + public PropertiesTextContainsFilterRule(PropertiesTextContainsFilterRule source) + : base(source) + { + this.PropertyNames = new List(source.PropertyNames); + this.EvaluationResultInvalidated += this.PropertiesTextContainsFilterRule_EvaluationResultInvalidated; + } + /// /// Gets a collection of the names of properties to search in. /// @@ -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 e8927c74826..09c732970b0 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/PropertyValueSelectorFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/PropertyValueSelectorFilterRule.cs @@ -15,7 +15,6 @@ namespace Microsoft.Management.UI.Internal /// /// The generic parameter. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class PropertyValueSelectorFilterRule : SelectorFilterRule where T : IComparable { @@ -66,21 +65,6 @@ public PropertyValueSelectorFilterRule(string propertyName, string propertyDispl /// public PropertyValueSelectorFilterRule(string propertyName, string propertyDisplayName, IEnumerable rules) { - if (string.IsNullOrEmpty(propertyName)) - { - throw new ArgumentNullException("propertyName"); - } - - if (string.IsNullOrEmpty(propertyDisplayName)) - { - throw new ArgumentNullException("propertyDisplayName"); - } - - if (rules == null) - { - throw new ArgumentNullException("rules"); - } - this.PropertyName = propertyName; this.DisplayName = propertyDisplayName; @@ -97,6 +81,17 @@ public PropertyValueSelectorFilterRule(string propertyName, string propertyDispl this.AvailableRules.DisplayNameConverter = new FilterRuleToDisplayNameConverter(); } + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + public PropertyValueSelectorFilterRule(PropertyValueSelectorFilterRule 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 c67b1c993e5..d1627ee2281 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/SelectorFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/SelectorFilterRule.cs @@ -9,7 +9,6 @@ namespace Microsoft.Management.UI.Internal /// /// The SelectorFilterRule represents a rule composed of other rules. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class SelectorFilterRule : FilterRule { @@ -40,7 +39,7 @@ public ValidatingSelectorValue AvailableRules #region Ctor /// - /// Creates a new SelectorFilterRule instance. + /// Initializes a new instance of the class. /// public SelectorFilterRule() { @@ -48,6 +47,18 @@ public SelectorFilterRule() this.AvailableRules.SelectedValueChanged += this.AvailableRules_SelectedValueChanged; } + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + public SelectorFilterRule(SelectorFilterRule source) + : base(source) + { + this.AvailableRules = (ValidatingSelectorValue)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 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 5aaabe58bfb..b26531943fc 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/SingleValueComparableValueFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/SingleValueComparableValueFilterRule.cs @@ -12,7 +12,6 @@ namespace Microsoft.Management.UI.Internal /// that take a single input and evaluate against IComparable values. /// /// The generic parameter. - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public abstract class SingleValueComparableValueFilterRule : ComparableValueFilterRule where T : IComparable { @@ -44,7 +43,7 @@ public override bool IsValid #region Ctor /// - /// Initializes a new instance of the SingleValueComparableValueFilterRule class. + /// Initializes a new instance of the class. /// protected SingleValueComparableValueFilterRule() { @@ -52,6 +51,17 @@ protected SingleValueComparableValueFilterRule() this.Value.PropertyChanged += this.Value_PropertyChanged; } + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + protected SingleValueComparableValueFilterRule(SingleValueComparableValueFilterRule source) + : base(source) + { + this.Value = (ValidatingValue)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 9186827c5f5..beb4a29d23f 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextContainsFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextContainsFilterRule.cs @@ -10,7 +10,6 @@ namespace Microsoft.Management.UI.Internal /// The TextContainsFilterRule class evaluates a string item to /// check if it is contains the rule's value within it. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class TextContainsFilterRule : TextFilterRule { @@ -18,13 +17,22 @@ public class TextContainsFilterRule : TextFilterRule private static readonly string TextContainsWordsRegexPattern = WordBoundaryRegexPattern + TextContainsCharactersRegexPattern + WordBoundaryRegexPattern; /// - /// Initializes a new instance of the TextContainsFilterRule class. + /// Initializes a new instance of the class. /// public TextContainsFilterRule() { this.DisplayName = UICultureResources.FilterRule_Contains; } + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + public TextContainsFilterRule(TextContainsFilterRule source) + : base(source) + { + } + /// /// Determines if Value is contained within data. /// 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 dcfeabff4c4..2cdbf1efcef 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextDoesNotContainFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextDoesNotContainFilterRule.cs @@ -9,12 +9,11 @@ namespace Microsoft.Management.UI.Internal /// The TextDoesNotContainFilterRule class evaluates a string item to /// check if it is does not contain the rule's value within it. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class TextDoesNotContainFilterRule : TextContainsFilterRule { /// - /// Initializes a new instance of the TextDoesNotContainFilterRule class. + /// Initializes a new instance of the class. /// public TextDoesNotContainFilterRule() { @@ -22,6 +21,15 @@ public TextDoesNotContainFilterRule() this.DefaultNullValueEvaluation = true; } + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + public TextDoesNotContainFilterRule(TextDoesNotContainFilterRule source) + : base(source) + { + } + /// /// Determines if Value is not contained within data. /// 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 3666b17c2de..e74b371a7a6 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextDoesNotEqualFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextDoesNotEqualFilterRule.cs @@ -9,12 +9,11 @@ namespace Microsoft.Management.UI.Internal /// The TextDoesNotEqualFilterRule class evaluates a string item to /// check if it is not equal to the rule's value. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class TextDoesNotEqualFilterRule : TextEqualsFilterRule { /// - /// Initializes a new instance of the TextDoesNotEqualFilterRule class. + /// Initializes a new instance of the class. /// public TextDoesNotEqualFilterRule() { @@ -22,6 +21,15 @@ public TextDoesNotEqualFilterRule() this.DefaultNullValueEvaluation = true; } + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + public TextDoesNotEqualFilterRule(TextDoesNotEqualFilterRule source) + : base(source) + { + } + /// /// Determines if data is not equal to Value. /// 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 45a9dd85386..d7f7e05c4b8 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextEndsWithFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextEndsWithFilterRule.cs @@ -10,7 +10,6 @@ namespace Microsoft.Management.UI.Internal /// The TextEndsWithFilterRule class evaluates a string item to /// check if it ends with the rule's value. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class TextEndsWithFilterRule : TextFilterRule { @@ -18,13 +17,22 @@ public class TextEndsWithFilterRule : TextFilterRule private static readonly string TextEndsWithWordsRegexPattern = WordBoundaryRegexPattern + TextEndsWithCharactersRegexPattern; /// - /// Initializes a new instance of the TextEndsWithFilterRule class. + /// Initializes a new instance of the class. /// public TextEndsWithFilterRule() { this.DisplayName = UICultureResources.FilterRule_TextEndsWith; } + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + public TextEndsWithFilterRule(TextEndsWithFilterRule source) + : base(source) + { + } + /// /// Determines if data ends with Value. /// 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 6401506bf1d..a357575c6ab 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextEqualsFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextEqualsFilterRule.cs @@ -10,20 +10,28 @@ namespace Microsoft.Management.UI.Internal /// The TextEqualsFilterRule class evaluates a string item to /// check if it is equal to the rule's value. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class TextEqualsFilterRule : TextFilterRule { private static readonly string TextEqualsCharactersRegexPattern = "^{0}$"; /// - /// Initializes a new instance of the TextEqualsFilterRule class. + /// Initializes a new instance of the class. /// public TextEqualsFilterRule() { this.DisplayName = UICultureResources.FilterRule_Equals; } + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + public TextEqualsFilterRule(TextEqualsFilterRule source) + : base(source) + { + } + /// /// Determines if data is equal to Value. /// 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 3440935889f..eacbcb8d256 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextFilterRule.cs @@ -13,7 +13,6 @@ namespace Microsoft.Management.UI.Internal /// The TextFilterRule class supports derived rules by offering services for /// evaluating string operations. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public abstract class TextFilterRule : SingleValueComparableValueFilterRule { @@ -62,7 +61,7 @@ public bool CultureInvariant } /// - /// Initializes a new instance of the TextFilterRule class. + /// Initializes a new instance of the class. /// protected TextFilterRule() { @@ -70,6 +69,17 @@ protected TextFilterRule() this.CultureInvariant = false; } + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + protected TextFilterRule(TextFilterRule source) + : base(source) + { + this.IgnoreCase = source.IgnoreCase; + this.CultureInvariant = source.CultureInvariant; + } + /// /// Gets the current value and determines whether it should be evaluated as an exact match. /// @@ -101,15 +111,9 @@ protected internal string GetParsedValue(out bool evaluateAsExactMatch) /// The specified value is a null reference. protected internal string GetRegexPattern(string pattern, string exactMatchPattern) { - if (pattern == null) - { - throw new ArgumentNullException("pattern"); - } + ArgumentNullException.ThrowIfNull(pattern); - if (exactMatchPattern == null) - { - throw new ArgumentNullException("exactMatchPattern"); - } + ArgumentNullException.ThrowIfNull(exactMatchPattern); Debug.Assert(this.IsValid, "is valid"); 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 8cfdc7960d8..98eac2b9a41 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextStartsWithFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextStartsWithFilterRule.cs @@ -10,7 +10,6 @@ namespace Microsoft.Management.UI.Internal /// The TextStartsWithFilterRule class evaluates a string item to /// check if it starts with the rule's value. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class TextStartsWithFilterRule : TextFilterRule { @@ -18,13 +17,22 @@ public class TextStartsWithFilterRule : TextFilterRule private static readonly string TextStartsWithWordsRegexPattern = TextStartsWithCharactersRegexPattern + WordBoundaryRegexPattern; /// - /// Initializes a new instance of the TextStartsWithFilterRule class. + /// Initializes a new instance of the class. /// public TextStartsWithFilterRule() { this.DisplayName = UICultureResources.FilterRule_TextStartsWith; } + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + public TextStartsWithFilterRule(TextStartsWithFilterRule source) + : base(source) + { + } + /// /// Determines if data starts with Value. /// 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 +{ + /// + /// Defines a generalized method for creating a deep copy of an instance. + /// + internal interface IDeepCloneable + { + /// + /// Creates a deep copy of the current instance. + /// + /// A new object that is a deep copy of the current instance. + object DeepClone(); + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/IEvaluate.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/IEvaluate.cs index f83f6b377aa..161f14d4537 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/IEvaluate.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/IEvaluate.cs @@ -13,7 +13,7 @@ public interface IEvaluate { /// /// Gets a values indicating whether the supplied item has meet the - /// criteria rule specificed by the rule. + /// criteria rule specified by the rule. /// /// /// The item to evaluate. diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingSelectorValue.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingSelectorValue.cs index ed5389668e6..0fed0c42e65 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingSelectorValue.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingSelectorValue.cs @@ -15,10 +15,40 @@ namespace Microsoft.Management.UI.Internal /// /// The generic parameter. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class ValidatingSelectorValue : ValidatingValueBase { + /// + /// Initializes a new instance of the class. + /// + public ValidatingSelectorValue() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + public ValidatingSelectorValue(ValidatingSelectorValue 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 @@ -130,11 +160,6 @@ public IValueConverter DisplayNameConverter set { - if (value != null && !value.GetType().IsSerializable) - { - throw new ArgumentException("The DisplayNameConverter must be serializable.", "value"); - } - this.displayNameConverter = value; } } @@ -148,13 +173,18 @@ public IValueConverter DisplayNameConverter /// /// Notifies listeners that the selected value has changed. /// - [field: NonSerialized] public event EventHandler> SelectedValueChanged; #endregion Events #region Public Methods + /// + public override object DeepClone() + { + return new ValidatingSelectorValue(this); + } + #region Validate /// @@ -187,7 +217,7 @@ protected override DataErrorInfoValidationResult Validate(string columnName) { if (!columnName.Equals(SelectedIndexPropertyName, StringComparison.CurrentCulture)) { - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "{0} is not a valid column name.", columnName), "columnName"); + throw new ArgumentException(string.Create(CultureInfo.CurrentCulture, $"{columnName} is not a valid column name."), "columnName"); } if (!this.IsIndexWithinBounds(this.SelectedIndex)) diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingValue.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingValue.cs index cf9c553f6b4..437cb3be50e 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingValue.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingValue.cs @@ -14,10 +14,26 @@ namespace Microsoft.Management.UI.Internal /// /// The generic parameter. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class ValidatingValue : ValidatingValueBase { + /// + /// Initializes a new instance of the class. + /// + public ValidatingValue() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + public ValidatingValue(ValidatingValue 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 + /// + public override object DeepClone() + { + return new ValidatingValue(this); + } + /// /// Gets the raw value cast/transformed into /// type T. @@ -165,10 +187,7 @@ private bool TryGetCastValue(object rawValue, out T castValue) { castValue = default(T); - if (rawValue == null) - { - throw new ArgumentNullException("rawValue"); - } + ArgumentNullException.ThrowIfNull(rawValue); if (typeof(T).IsEnum) { diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingValueBase.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingValueBase.cs index f3959685349..a4ffb1af77c 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingValueBase.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingValueBase.cs @@ -14,10 +14,30 @@ namespace Microsoft.Management.UI.Internal /// The ValidatingValueBase class provides basic services for base /// classes to support validation via the IDataErrorInfo interface. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - public abstract class ValidatingValueBase : IDataErrorInfo, INotifyPropertyChanged + public abstract class ValidatingValueBase : IDataErrorInfo, INotifyPropertyChanged, IDeepCloneable { + /// + /// Initializes a new instance of the class. + /// + protected ValidatingValueBase() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The source to initialize from. + 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 readonlyValidationRules; private bool isValidationRulesCollectionDirty = true; - [field: NonSerialized] private DataErrorInfoValidationResult cachedValidationResult; /// @@ -82,10 +101,7 @@ public string this[string columnName] { get { - if (string.IsNullOrEmpty(columnName)) - { - throw new ArgumentNullException("columnName"); - } + ArgumentException.ThrowIfNullOrEmpty(columnName); this.UpdateValidationResult(columnName); return this.GetValidationResult().ErrorMessage; @@ -123,7 +139,6 @@ public string Error /// /// The listeners attached to this event are not serialized. /// - [field: NonSerialized] public event PropertyChangedEventHandler PropertyChanged; #endregion PropertyChanged @@ -132,6 +147,9 @@ public string Error #region Public Methods + /// + public abstract object DeepClone(); + #region AddValidationRule /// @@ -140,10 +158,7 @@ public string Error /// The validation rule to add. public void AddValidationRule(DataErrorInfoValidationRule rule) { - if (rule == null) - { - throw new ArgumentNullException("rule"); - } + ArgumentNullException.ThrowIfNull(rule); this.validationRules.Add(rule); @@ -161,10 +176,7 @@ public void AddValidationRule(DataErrorInfoValidationRule rule) /// The rule to remove. public void RemoveValidationRule(DataErrorInfoValidationRule rule) { - if (rule == null) - { - throw new ArgumentNullException("rule"); - } + ArgumentNullException.ThrowIfNull(rule); this.validationRules.Remove(rule); @@ -223,7 +235,7 @@ internal DataErrorInfoValidationResult EvaluateValidationRules(object value, Sys DataErrorInfoValidationResult result = rule.Validate(value, cultureInfo); if (result == null) { - throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "DataErrorInfoValidationResult not returned by ValidationRule: {0}", rule.ToString())); + throw new InvalidOperationException(string.Create(CultureInfo.CurrentCulture, $"DataErrorInfoValidationResult not returned by ValidationRule: {rule}")); } if (!result.IsValid) 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 9b4a2b23d0d..a92916c0717 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidationRules/DataErrorInfoValidationRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidationRules/DataErrorInfoValidationRule.cs @@ -8,9 +8,8 @@ namespace Microsoft.Management.UI.Internal /// /// Provides a way to create a custom rule in order to check the validity of user input. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - public abstract class DataErrorInfoValidationRule + public abstract class DataErrorInfoValidationRule : IDeepCloneable { /// /// When overridden in a derived class, performs validation checks on a value. @@ -25,5 +24,8 @@ public abstract class DataErrorInfoValidationRule /// A DataErrorInfoValidationResult object. /// public abstract DataErrorInfoValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo); + + /// + public abstract object DeepClone(); } } diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRulePanel.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRulePanel.cs index bcc9a01a92c..c3bb4042d53 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRulePanel.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRulePanel.cs @@ -154,15 +154,9 @@ public FilterRulePanel() /// public void AddFilterRulePanelItemContentTemplate(Type type, DataTemplate dataTemplate) { - if (type == null) - { - throw new ArgumentNullException("type"); - } + ArgumentNullException.ThrowIfNull(type); - if (dataTemplate == null) - { - throw new ArgumentNullException("dataTemplate"); - } + ArgumentNullException.ThrowIfNull(dataTemplate); this.filterRuleTemplateSelector.TemplateDictionary.Add(new KeyValuePair(type, dataTemplate)); } @@ -176,10 +170,7 @@ public void AddFilterRulePanelItemContentTemplate(Type type, DataTemplate dataTe /// public void RemoveFilterRulePanelItemContentTemplate(Type type) { - if (type == null) - { - throw new ArgumentNullException("type"); - } + ArgumentNullException.ThrowIfNull(type); this.filterRuleTemplateSelector.TemplateDictionary.Remove(type); } diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRulePanelController.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRulePanelController.cs index dcec5022d98..68c22de5af9 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRulePanelController.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRulePanelController.cs @@ -88,10 +88,7 @@ public FilterRulePanelController() /// public void AddFilterRulePanelItem(FilterRulePanelItem item) { - if (item == null) - { - throw new ArgumentNullException("item"); - } + ArgumentNullException.ThrowIfNull(item); int insertionIndex = this.GetInsertionIndex(item); this.filterRulePanelItems.Insert(insertionIndex, item); @@ -116,10 +113,7 @@ private void Rule_EvaluationResultInvalidated(object sender, EventArgs e) /// public void RemoveFilterRulePanelItem(FilterRulePanelItem item) { - if (item == null) - { - throw new ArgumentNullException("item"); - } + ArgumentNullException.ThrowIfNull(item); item.Rule.EvaluationResultInvalidated -= this.Rule_EvaluationResultInvalidated; diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRulePanelItem.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRulePanelItem.cs index a1a873cdbc7..ee6b124562c 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRulePanelItem.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRulePanelItem.cs @@ -25,7 +25,7 @@ public FilterRule Rule } /// - /// Gets a string that indentifies which group this + /// Gets a string that identifies which group this /// item belongs to. /// public string GroupId @@ -79,15 +79,8 @@ protected internal set /// public FilterRulePanelItem(FilterRule rule, string groupId) { - if (rule == null) - { - throw new ArgumentNullException("rule"); - } - - if (string.IsNullOrEmpty(groupId)) - { - throw new ArgumentNullException("groupId"); - } + ArgumentNullException.ThrowIfNull(rule); + ArgumentException.ThrowIfNullOrEmpty(groupId); this.Rule = rule; this.GroupId = groupId; diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRuleTemplateSelector.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRuleTemplateSelector.cs index fc40c3c5768..0381ef0e63c 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRuleTemplateSelector.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRuleTemplateSelector.cs @@ -26,7 +26,7 @@ public IDictionary TemplateDictionary } /// - /// Selects a template based upon the type of the item and and the + /// Selects a template based upon the type of the item and the /// corresponding template that is registered in the TemplateDictionary. /// /// diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRuleToDisplayNameConverter.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRuleToDisplayNameConverter.cs index 07a4ed58f8b..972c19080e0 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRuleToDisplayNameConverter.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRuleToDisplayNameConverter.cs @@ -11,7 +11,6 @@ namespace Microsoft.Management.UI.Internal /// The FilterRuleToDisplayNameConverter is responsible for converting /// a FilterRule value to its DisplayName. /// - [Serializable] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class FilterRuleToDisplayNameConverter : IValueConverter { diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/InputFieldBackgroundTextConverter.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/InputFieldBackgroundTextConverter.cs index 0017f2a4340..1295b933d5b 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/InputFieldBackgroundTextConverter.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/InputFieldBackgroundTextConverter.cs @@ -11,7 +11,7 @@ namespace Microsoft.Management.UI.Internal { /// - /// The InputFieldBackgroundTextConverter is responsible for determing the + /// The InputFieldBackgroundTextConverter is responsible for determining the /// correct background text to display for a particular type of data. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] @@ -40,10 +40,7 @@ public class InputFieldBackgroundTextConverter : IValueConverter /// public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - if (value == null) - { - throw new ArgumentNullException("value"); - } + ArgumentNullException.ThrowIfNull(value); Type inputType = null; if (this.IsOfTypeValidatingValue(value)) diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/SearchBox.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/SearchBox.cs index 25830150939..b3345a10cf2 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/SearchBox.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/SearchBox.cs @@ -86,10 +86,7 @@ public SearchTextParser Parser set { - if (value == null) - { - throw new ArgumentNullException("value"); - } + ArgumentNullException.ThrowIfNull(value); this.parser = value; } @@ -118,10 +115,7 @@ partial void OnClearTextExecutedImplementation(ExecutedRoutedEventArgs e) /// The specified value is a null reference. protected static FilterExpressionNode ConvertToFilterExpression(ICollection searchBoxItems) { - if (searchBoxItems == null) - { - throw new ArgumentNullException("searchBoxItems"); - } + ArgumentNullException.ThrowIfNull(searchBoxItems); if (searchBoxItems.Count == 0) { diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/SearchTextParseResult.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/SearchTextParseResult.cs index 5fa5e3e7703..10843087587 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/SearchTextParseResult.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/SearchTextParseResult.cs @@ -18,10 +18,7 @@ public class SearchTextParseResult /// The specified value is a null reference. public SearchTextParseResult(FilterRule rule) { - if (rule == null) - { - throw new ArgumentNullException("rule"); - } + ArgumentNullException.ThrowIfNull(rule); this.FilterRule = rule; } diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/SearchTextParser.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/SearchTextParser.cs index 04ba32ece6d..2e0ac74e076 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/SearchTextParser.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/SearchTextParser.cs @@ -45,10 +45,7 @@ public TextFilterRule FullTextRule public bool TryAddSearchableRule(SelectorFilterRule selectorRule) where T : TextFilterRule { - if (selectorRule == null) - { - throw new ArgumentNullException("selectorRule"); - } + ArgumentNullException.ThrowIfNull(selectorRule); T textRule = selectorRule.AvailableRules.AvailableValues.Find(); @@ -193,25 +190,21 @@ protected class SearchableRule /// The specified value is a null reference. public SearchableRule(string uniqueId, SelectorFilterRule selectorFilterRule, TextFilterRule childRule) { - if (uniqueId == null) - { - throw new ArgumentNullException("uniqueId"); - } + ArgumentNullException.ThrowIfNull(uniqueId); - if (selectorFilterRule == null) - { - throw new ArgumentNullException("selectorFilterRule"); - } + ArgumentNullException.ThrowIfNull(selectorFilterRule); - if (childRule == null) - { - throw new ArgumentNullException("childRule"); - } + ArgumentNullException.ThrowIfNull(childRule); this.UniqueId = uniqueId; this.selectorFilterRule = selectorFilterRule; this.childRule = childRule; - this.Pattern = string.Format(CultureInfo.InvariantCulture, "(?<{0}>){1}\\s*:\\s*{2}", uniqueId, Regex.Escape(selectorFilterRule.DisplayName), SearchTextParser.ValuePattern); + this.Pattern = string.Format( + CultureInfo.InvariantCulture, + "(?<{0}>){1}\\s*:\\s*{2}", + uniqueId, + Regex.Escape(selectorFilterRule.DisplayName), + SearchTextParser.ValuePattern); } /// @@ -240,10 +233,7 @@ public string Pattern /// The specified value is a null reference. public SelectorFilterRule GetRuleWithValueSet(string value) { - if (value == null) - { - throw new ArgumentNullException("value"); - } + ArgumentNullException.ThrowIfNull(value); SelectorFilterRule selectorRule = (SelectorFilterRule)this.selectorFilterRule.DeepCopy(); selectorRule.AvailableRules.SelectedIndex = this.selectorFilterRule.AvailableRules.AvailableValues.IndexOf(this.childRule); diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/ValidatingSelectorValueToDisplayNameConverter.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/ValidatingSelectorValueToDisplayNameConverter.cs index e8708b92a15..010fbbeef75 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/ValidatingSelectorValueToDisplayNameConverter.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/ValidatingSelectorValueToDisplayNameConverter.cs @@ -38,10 +38,7 @@ public class ValidatingSelectorValueToDisplayNameConverter : IMultiValueConverte /// public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { - if (values == null) - { - throw new ArgumentNullException("values"); - } + ArgumentNullException.ThrowIfNull(values); if (values.Length != 2) { 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 470a7670860..8a919959968 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ColumnPicker.xaml.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ColumnPicker.xaml.cs @@ -59,15 +59,9 @@ internal ColumnPicker( ICollection availableColumns) : this() { - if (columns == null) - { - throw new ArgumentNullException("columns"); - } - - if (availableColumns == null) - { - throw new ArgumentNullException("availableColumns"); - } + ArgumentNullException.ThrowIfNull(columns); + + ArgumentNullException.ThrowIfNull(availableColumns); // Add visible columns to Selected list, preserving order // Note that availableColumns is not necessarily in the order diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/DefaultStringConverter.cs b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/DefaultStringConverter.cs index cf85d79ae36..2d590904097 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/DefaultStringConverter.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/DefaultStringConverter.cs @@ -62,7 +62,9 @@ public string DefaultValue /// public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { - if (values == null || values.Length != 1) + ArgumentNullException.ThrowIfNull(values); + + if (values.Length != 1) { throw new ArgumentNullException("values"); } diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/InnerListGridView.cs b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/InnerListGridView.cs index 73222cae639..2761dcf36da 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/InnerListGridView.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/InnerListGridView.cs @@ -45,10 +45,7 @@ public InnerListGridView() /// The specified value is a null reference. internal InnerListGridView(ObservableCollection availableColumns) { - if (availableColumns == null) - { - throw new ArgumentNullException("availableColumns"); - } + ArgumentNullException.ThrowIfNull(availableColumns); // Setting the AvailableColumns property won't trigger CollectionChanged, so we have to do it manually \\ this.AvailableColumns = availableColumns; diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/Innerlist.cs b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/Innerlist.cs index 03c7acdf008..aa945a1d90c 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/Innerlist.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/Innerlist.cs @@ -93,7 +93,7 @@ public InnerList() /// /// Gets ItemsSource instead. - /// Does not support adding to Items. + /// Does not support adding to Items. /// [Browsable(false)] public new ItemCollection Items @@ -191,10 +191,7 @@ public void RefreshColumns() /// The specified value is a null reference. public void ApplySort(InnerListColumn column, bool shouldScrollIntoView) { - if (column == null) - { - throw new ArgumentNullException("column"); - } + ArgumentNullException.ThrowIfNull(column); // NOTE : By setting the column here, it will be used // later to set the sorted column when the UI state @@ -296,7 +293,7 @@ protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldV this.itemsSourceIsEmpty = this.ItemsSource != null && this.ItemsSource.GetEnumerator().MoveNext() == false; - // A view can be created if there is data to auto-generate columns, or columns are added programatically \\ + // A view can be created if there is data to auto-generate columns, or columns are added programmatically \\ bool canCreateView = (this.ItemsSource != null) && (this.itemsSourceIsEmpty == false || this.AutoGenerateColumns == false); @@ -355,7 +352,7 @@ protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); - if ((Key.Left == e.Key || Key.Right == e.Key) && + if ((e.Key == Key.Left || e.Key == Key.Right) && Keyboard.Modifiers == ModifierKeys.None) { // If pressing Left or Right on a column header, move the focus \\ @@ -388,8 +385,8 @@ private static void InnerList_OnViewChanged(DependencyObject obj, DependencyProp throw new NotSupportedException(string.Format( CultureInfo.InvariantCulture, InvariantResources.ViewSetWithType, - typeof(GridView).Name, - typeof(InnerListGridView).Name)); + nameof(GridView), + nameof(InnerListGridView))); } ((InnerList)obj).innerGrid = innerGrid; @@ -405,7 +402,7 @@ private static NotSupportedException GetItemsException() string.Format( CultureInfo.InvariantCulture, InvariantResources.NotSupportAddingToItems, - typeof(InnerList).Name, + nameof(InnerList), ItemsControl.ItemsSourceProperty.Name)); } #endregion static private methods @@ -599,7 +596,7 @@ private string GetClipboardTextLineForSelectedItem(object value) propertyValue = string.Empty; } - entryText.AppendFormat(CultureInfo.CurrentCulture, "{0}\t", propertyValue); + entryText.Append(CultureInfo.CurrentCulture, $"{propertyValue}\t"); } return entryText.ToString(); diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ManagementListStateDescriptor.cs b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ManagementListStateDescriptor.cs index 841175c97da..bbfd3d8603c 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ManagementListStateDescriptor.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ManagementListStateDescriptor.cs @@ -15,7 +15,6 @@ namespace Microsoft.Management.UI.Internal /// /// Allows the state of the ManagementList to be saved and restored. /// - [Serializable] [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public class ManagementListStateDescriptor : StateDescriptor { @@ -63,10 +62,7 @@ public ManagementListStateDescriptor(string name) /// public override void SaveState(ManagementList subject) { - if (subject == null) - { - throw new ArgumentNullException("subject"); - } + ArgumentNullException.ThrowIfNull(subject); this.SaveColumns(subject); this.SaveSortOrder(subject); @@ -100,10 +96,7 @@ public override void RestoreState(ManagementList subject) /// public void RestoreState(ManagementList subject, bool applyRestoredFilter) { - if (subject == null) - { - throw new ArgumentNullException("subject"); - } + ArgumentNullException.ThrowIfNull(subject); // Clear the sort, otherwise restoring columns and filters may trigger extra sorting \\ subject.List.ClearSort(); @@ -142,7 +135,7 @@ private static bool VerifyColumnsSavable(ManagementList subject, RetryActionCall /// /// Target ManagementList. /// RetryActionAfterLoaded callback method. - /// True iff columns restorable. + /// True if-and-only-if columns are restorable. /// /// ManagementList.AutoGenerateColumns not supported. /// @@ -471,7 +464,6 @@ private static void SetColumnWidth(GridViewColumn ilc, double width) #region Helper Classes - [Serializable] internal class ColumnStateDescriptor { private int index; @@ -516,7 +508,6 @@ public double Width } } - [Serializable] internal class RuleStateDescriptor { /// diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/PropertyValueGetter.cs b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/PropertyValueGetter.cs index 61a1a71938c..2e9326cd909 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/PropertyValueGetter.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/PropertyValueGetter.cs @@ -48,10 +48,7 @@ public virtual bool TryGetPropertyValue(string propertyName, object value, out o throw new ArgumentException("propertyName is empty", "propertyName"); } - if (value == null) - { - throw new ArgumentNullException("value"); - } + ArgumentNullException.ThrowIfNull(value); PropertyDescriptor descriptor = this.GetPropertyDescriptor(propertyName, value); if (descriptor == null) diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ViewGroupToStringConverter.cs b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ViewGroupToStringConverter.cs index c69cd8e0c08..22741a7031b 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ViewGroupToStringConverter.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ViewGroupToStringConverter.cs @@ -35,7 +35,7 @@ public object Convert(object value, Type targetType, object parameter, System.Gl } string name = (!string.IsNullOrEmpty(cvg.Name.ToString())) ? cvg.Name.ToString() : UICultureResources.GroupTitleNone; - string display = string.Format(CultureInfo.CurrentCulture, "{0} ({1})", name, cvg.ItemCount); + string display = string.Create(CultureInfo.CurrentCulture, $"{name} ({cvg.ItemCount})"); return display; } diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/innerlistcolumn.cs b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/innerlistcolumn.cs index 9f91f2b74e8..965a66239d0 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/innerlistcolumn.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/innerlistcolumn.cs @@ -68,10 +68,7 @@ public InnerListColumn(UIPropertyGroupDescription dataDescription, bool isVisibl /// Whether the column should create a default binding using the specified data's property. public InnerListColumn(UIPropertyGroupDescription dataDescription, bool isVisible, bool createDefaultBinding) { - if (dataDescription == null) - { - throw new ArgumentNullException("dataDescription"); - } + ArgumentNullException.ThrowIfNull(dataDescription); GridViewColumnHeader header = new GridViewColumnHeader(); header.Content = dataDescription.DisplayContent; diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/managementlist.cs b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/managementlist.cs index 7c36244e0f1..4ac51c702d8 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/managementlist.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/managementlist.cs @@ -50,10 +50,7 @@ public IStateDescriptorFactory SavedViewFactory set { - if (value == null) - { - throw new ArgumentNullException("value"); - } + ArgumentNullException.ThrowIfNull(value); this.savedViewFactory = value; } @@ -177,10 +174,7 @@ private void Evaluator_PropertyChanged(object sender, PropertyChangedEventArgs e /// The specified value is a null reference. public void AddColumn(InnerListColumn column) { - if (column == null) - { - throw new ArgumentNullException("column"); - } + ArgumentNullException.ThrowIfNull(column); this.AddColumn(column, this.IsFilterShown); } @@ -193,10 +187,7 @@ public void AddColumn(InnerListColumn column) /// The specified value is a null reference. public void AddColumn(InnerListColumn column, bool addDefaultFilterRules) { - if (column == null) - { - throw new ArgumentNullException("column"); - } + ArgumentNullException.ThrowIfNull(column); this.List.Columns.Add(column); @@ -229,10 +220,7 @@ public void AddColumn(InnerListColumn column, bool addDefaultFilterRules) /// The specified value is a null reference. public void AddRule(FilterRule rule) { - if (rule == null) - { - throw new ArgumentNullException("rule"); - } + ArgumentNullException.ThrowIfNull(rule); this.AddFilterRulePicker.ShortcutFilterRules.Add(new AddFilterRulePickerItem(new FilterRulePanelItem(rule, rule.DisplayName))); } diff --git a/src/Microsoft.Management.UI.Internal/Microsoft.PowerShell.GraphicalHost.csproj b/src/Microsoft.Management.UI.Internal/Microsoft.PowerShell.GraphicalHost.csproj index 72eb8d6572f..acc4eae2c61 100644 --- a/src/Microsoft.Management.UI.Internal/Microsoft.PowerShell.GraphicalHost.csproj +++ b/src/Microsoft.Management.UI.Internal/Microsoft.PowerShell.GraphicalHost.csproj @@ -5,9 +5,11 @@ $(NoWarn);CS1570 Microsoft.Management.UI.Internal Microsoft.PowerShell.GraphicalHost - True + false Windows - 7.0 + 8.0 + true + True diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/MultipleSelectionControl.xaml.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/MultipleSelectionControl.xaml.cs index 54dcf1896ff..f57d5dfda51 100644 --- a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/MultipleSelectionControl.xaml.cs +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/MultipleSelectionControl.xaml.cs @@ -43,7 +43,7 @@ private void ButtonBrowse_Click(object sender, RoutedEventArgs e) foreach (object selectedItem in multipleSelectionDialog.listboxParameter.SelectedItems) { - newComboText.AppendFormat(CultureInfo.InvariantCulture, "{0},", selectedItem.ToString()); + newComboText.Append(CultureInfo.InvariantCulture, $"{selectedItem},"); } if (newComboText.Length > 1) diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ParameterSetControl.xaml.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ParameterSetControl.xaml.cs index 08f9df29337..6efef65eec6 100644 --- a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ParameterSetControl.xaml.cs +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ParameterSetControl.xaml.cs @@ -94,7 +94,7 @@ private static CheckBox CreateCheckBox(ParameterViewModel parameterViewModel, in //// Add AutomationProperties.AutomationId for Ui Automation test. checkBox.SetValue( System.Windows.Automation.AutomationProperties.AutomationIdProperty, - string.Format(CultureInfo.CurrentCulture, "chk{0}", parameterViewModel.Name)); + string.Create(CultureInfo.CurrentCulture, $"chk{parameterViewModel.Name}")); checkBox.SetValue( System.Windows.Automation.AutomationProperties.NameProperty, @@ -124,10 +124,7 @@ private static ComboBox CreateComboBoxControl(ParameterViewModel parameterViewMo Binding selectedItemBinding = new Binding("Value"); comboBox.SetBinding(ComboBox.SelectedItemProperty, selectedItemBinding); - string automationId = string.Format( - CultureInfo.CurrentCulture, - "combox{0}", - parameterViewModel.Name); + string automationId = string.Create(CultureInfo.CurrentCulture, $"combox{parameterViewModel.Name}"); //// Add AutomationProperties.AutomationId for Ui Automation test. comboBox.SetValue( @@ -164,7 +161,7 @@ private static MultipleSelectionControl CreateMultiSelectComboControl(ParameterV multiControls.comboxParameter.SetBinding(ComboBox.TextProperty, valueBinding); // Add AutomationProperties.AutomationId for Ui Automation test. - multiControls.SetValue(System.Windows.Automation.AutomationProperties.AutomationIdProperty, string.Format("combox{0}", parameterViewModel.Name)); + multiControls.SetValue(System.Windows.Automation.AutomationProperties.AutomationIdProperty, string.Create(CultureInfo.CurrentCulture, $"combox{parameterViewModel.Name}")); multiControls.comboxParameter.SetValue( System.Windows.Automation.AutomationProperties.NameProperty, @@ -206,7 +203,7 @@ private static TextBox CreateTextBoxControl(ParameterViewModel parameterViewMode //// Add AutomationProperties.AutomationId for UI Automation test. textBox.SetValue( System.Windows.Automation.AutomationProperties.AutomationIdProperty, - string.Format(CultureInfo.CurrentCulture, "txt{0}", parameterViewModel.Name)); + string.Create(CultureInfo.CurrentCulture, $"txt{parameterViewModel.Name}")); textBox.SetValue( System.Windows.Automation.AutomationProperties.NameProperty, @@ -366,7 +363,7 @@ private void AddControlToMainGrid(UIElement uiControl) } /// - /// Creates a Lable control and add it to MainGrid. + /// Creates a Label control and add it to MainGrid. /// /// DataContext object. /// Row number. @@ -397,7 +394,7 @@ private Label CreateLabel(ParameterViewModel parameterViewModel, int rowNumber) //// Add AutomationProperties.AutomationId for Ui Automation test. label.SetValue( System.Windows.Automation.AutomationProperties.AutomationIdProperty, - string.Format(CultureInfo.CurrentCulture, "lbl{0}", parameterViewModel.Name)); + string.Create(CultureInfo.CurrentCulture, $"lbl{parameterViewModel.Name}")); return label; } diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ShowModuleControl.xaml.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ShowModuleControl.xaml.cs index 440eb329f39..4bdaa32fd9f 100644 --- a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ShowModuleControl.xaml.cs +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ShowModuleControl.xaml.cs @@ -47,8 +47,8 @@ public Window Owner /// it will select the item under it, but if you keep the mouse button down and move the mouse /// (if the list supported drag and drop, the mouse action would be the same as dragging) it /// will select other list items. - /// If the first selection change causes details for the item to be displayed and resizes the list - /// the selection can skip to another list item it happend to be over as the list got resized. + /// If the first selection change causes details for the item to be displayed and resizes the list, + /// the selection can skip to another list item that happens to be over as the list got resized. /// In summary, resizing the list on selection can cause a selection bug. If the user selects an /// item in the end of the list the next item downwards can be selected. /// The WPF drag-and-select feature is not a standard win32 list behavior, and we can do without it diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/AllModulesViewModel.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/AllModulesViewModel.cs index 34159c8f837..04fc95e4223 100644 --- a/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/AllModulesViewModel.cs +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/AllModulesViewModel.cs @@ -79,7 +79,9 @@ public class AllModulesViewModel : INotifyPropertyChanged /// Commands to show. public AllModulesViewModel(Dictionary importedModules, IEnumerable commands) { - if (commands == null || !commands.GetEnumerator().MoveNext()) + ArgumentNullException.ThrowIfNull(commands); + + if (!commands.GetEnumerator().MoveNext()) { throw new ArgumentNullException("commands"); } @@ -95,10 +97,7 @@ public AllModulesViewModel(Dictionary importedMod /// True not to show common parameters. public AllModulesViewModel(Dictionary importedModules, IEnumerable commands, bool noCommonParameter) { - if (commands == null) - { - throw new ArgumentNullException("commands"); - } + ArgumentNullException.ThrowIfNull(commands); this.Initialization(importedModules, commands, noCommonParameter); } @@ -530,7 +529,7 @@ private void Initialization(Dictionary importedMo return; } - // If there are more modules, create an additional module to agregate all commands + // If there are more modules, create an additional module to aggregate all commands ModuleViewModel allCommandsModule = new ModuleViewModel(ShowCommandResources.All, null); this.modules.Add(allCommandsModule); allCommandsModule.SetAllModules(this); diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/CommandViewModel.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/CommandViewModel.cs index e5eb1be800d..cfa2798963c 100644 --- a/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/CommandViewModel.cs +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/CommandViewModel.cs @@ -431,19 +431,19 @@ public string GetScript() if (commandName.Contains(' ')) { - builder.AppendFormat("& \"{0}\"", commandName); + builder.Append($"& \"{commandName}\""); } else { builder.Append(commandName); } - builder.Append(" "); + builder.Append(' '); if (this.SelectedParameterSet != null) { builder.Append(this.SelectedParameterSet.GetScript()); - builder.Append(" "); + builder.Append(' '); } if (this.CommonParameters != null) @@ -457,7 +457,7 @@ public string GetScript() } /// - /// Showing help information for current actived cmdlet. + /// Showing help information for current active cmdlet. /// public void OpenHelpWindow() { @@ -465,7 +465,7 @@ public void OpenHelpWindow() } /// - /// Determins whether current command name and a specifed ParameterSetName have same name. + /// Determines whether current command name and a specified ParameterSetName have same name. /// /// The name of ShareParameterSet. /// Return true is ShareParameterSet. Else return false. @@ -490,10 +490,7 @@ internal static bool IsSharedParameterSetName(string name) /// The CommandViewModel corresponding to commandInfo. internal static CommandViewModel GetCommandViewModel(ModuleViewModel module, ShowCommandCommandInfo commandInfo, bool noCommonParameters) { - if (commandInfo == null) - { - throw new ArgumentNullException("commandInfo"); - } + ArgumentNullException.ThrowIfNull(commandInfo); CommandViewModel returnValue = new CommandViewModel(); returnValue.commandInfo = commandInfo; diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ModuleViewModel.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ModuleViewModel.cs index 38925e458a9..950dbe93758 100644 --- a/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ModuleViewModel.cs +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ModuleViewModel.cs @@ -67,10 +67,7 @@ public class ModuleViewModel : INotifyPropertyChanged /// All loaded modules. public ModuleViewModel(string name, Dictionary importedModules) { - if (name == null) - { - throw new ArgumentNullException("name"); - } + ArgumentNullException.ThrowIfNull(name); this.name = name; this.commands = new List(); @@ -373,7 +370,7 @@ internal void RefreshFilteredCommands(string filter) } /// - /// Callled in response to a GUI event that requires the command to be run. + /// Called in response to a GUI event that requires the command to be run. /// internal void OnRunSelectedCommand() { diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ParameterSetViewModel.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ParameterSetViewModel.cs index 756f58c62c8..b4f42dd78a2 100644 --- a/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ParameterSetViewModel.cs +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ParameterSetViewModel.cs @@ -39,21 +39,15 @@ public class ParameterSetViewModel : INotifyPropertyChanged /// Initializes a new instance of the ParameterSetViewModel class. /// /// The name of the parameterSet. - /// The array parametes of the parameterSet. + /// The array parameters of the parameterSet. [SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Justification = "this type is internal, made public only for WPF Binding")] public ParameterSetViewModel( string name, List parameters) { - if (name == null) - { - throw new ArgumentNullException("name"); - } + ArgumentNullException.ThrowIfNull(name); - if (parameters == null) - { - throw new ArgumentNullException("parameters"); - } + ArgumentNullException.ThrowIfNull(parameters); parameters.Sort(Compare); @@ -144,7 +138,7 @@ public string GetScript() { if (((bool?)parameter.Value) == true) { - builder.AppendFormat("-{0} ", parameter.Name); + builder.Append($"-{parameter.Name} "); } continue; @@ -172,7 +166,7 @@ public string GetScript() parameterValueString = ParameterSetViewModel.GetDelimitedParameter(parameterValueString, "(", ")"); } - builder.AppendFormat("-{0} {1} ", parameter.Name, parameterValueString); + builder.Append($"-{parameter.Name} {parameterValueString} "); } return builder.ToString().Trim(); @@ -232,12 +226,12 @@ internal static int Compare(ParameterViewModel source, ParameterViewModel target #endregion /// - /// Gets the delimited poarameter if it needs delimitation and is not delimited. + /// Gets the delimited parameter if it needs delimitation and is not delimited. /// /// Value needing delimitation. /// Open delimitation. /// Close delimitation. - /// The delimited poarameter if it needs delimitation and is not delimited. + /// The delimited parameter if it needs delimitation and is not delimited. private static string GetDelimitedParameter(string parameterValue, string openDelimiter, string closeDelimiter) { string parameterValueTrimmed = parameterValue.Trim(); diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ParameterViewModel.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ParameterViewModel.cs index 2c6931eb08e..93227df87a1 100644 --- a/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ParameterViewModel.cs +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ParameterViewModel.cs @@ -48,15 +48,9 @@ public class ParameterViewModel : INotifyPropertyChanged /// The name of the parameter set this parameter is in. public ParameterViewModel(ShowCommandParameterInfo parameter, string parameterSetName) { - if (parameter == null) - { - throw new ArgumentNullException("parameter"); - } + ArgumentNullException.ThrowIfNull(parameter); - if (parameterSetName == null) - { - throw new ArgumentNullException("parameterSetName"); - } + ArgumentNullException.ThrowIfNull(parameterSetName); this.parameter = parameter; this.parameterSetName = parameterSetName; @@ -165,7 +159,7 @@ public string NameCheckLabel string returnValue = this.Parameter.Name; if (this.Parameter.IsMandatory) { - returnValue = string.Format(CultureInfo.CurrentUICulture, "{0}{1}", returnValue, ShowCommandResources.MandatoryLabelSegment); + returnValue = string.Create(CultureInfo.CurrentUICulture, $"{returnValue}{ShowCommandResources.MandatoryLabelSegment}"); } return returnValue; diff --git a/src/Microsoft.Management.UI.Internal/commandHelpers/HelpWindowHelper.cs b/src/Microsoft.Management.UI.Internal/commandHelpers/HelpWindowHelper.cs index efed820e71c..e0b036a93d0 100644 --- a/src/Microsoft.Management.UI.Internal/commandHelpers/HelpWindowHelper.cs +++ b/src/Microsoft.Management.UI.Internal/commandHelpers/HelpWindowHelper.cs @@ -13,7 +13,7 @@ namespace Microsoft.PowerShell.Commands.Internal { /// - /// Implements thw WPF window part of the the ShowWindow option of get-help. + /// Implements the WPF window part of the ShowWindow option of get-help. /// internal static class HelpWindowHelper { diff --git a/src/Microsoft.Management.UI.Internal/commandHelpers/OutGridView.cs b/src/Microsoft.Management.UI.Internal/commandHelpers/OutGridView.cs index 85ecd95a36f..81621624992 100644 --- a/src/Microsoft.Management.UI.Internal/commandHelpers/OutGridView.cs +++ b/src/Microsoft.Management.UI.Internal/commandHelpers/OutGridView.cs @@ -6,6 +6,7 @@ using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; +using System.Management.Automation.Internal; using System.Threading; using System.Windows; using System.Windows.Automation; @@ -213,7 +214,7 @@ private void ZoomEventHandlerPlus(object sender, ExecutedRoutedEventArgs e) if (this.zoomLevel < ZOOM_MAX) { - this.zoomLevel = this.zoomLevel + ZOOM_INCREMENT; + this.zoomLevel += ZOOM_INCREMENT; Grid g = this.gridViewWindow.Content as Grid; if (g != null) @@ -232,7 +233,7 @@ private void ZoomEventHandlerMinus(object sender, ExecutedRoutedEventArgs e) { if (this.zoomLevel >= ZOOM_MIN) { - this.zoomLevel = this.zoomLevel - ZOOM_INCREMENT; + this.zoomLevel -= ZOOM_INCREMENT; Grid g = this.gridViewWindow.Content as Grid; if (g != null) { @@ -496,6 +497,16 @@ private void AddItem(PSObject value) { try { + // Remove any potential ANSI decoration + foreach (var property in value.Properties) + { + if (property.Value is string str) + { + StringDecorated decoratedString = new StringDecorated(str); + property.Value = decoratedString.ToString(OutputRendering.PlainText); + } + } + this.listItems.Add(value); } catch (Exception e) diff --git a/src/Microsoft.Management.UI.Internal/commandHelpers/ShowCommandHelper.cs b/src/Microsoft.Management.UI.Internal/commandHelpers/ShowCommandHelper.cs index 5d401fc94c6..32c68e961c6 100644 --- a/src/Microsoft.Management.UI.Internal/commandHelpers/ShowCommandHelper.cs +++ b/src/Microsoft.Management.UI.Internal/commandHelpers/ShowCommandHelper.cs @@ -21,7 +21,7 @@ namespace Microsoft.PowerShell.Commands.ShowCommandInternal { /// - /// Implements thw WPF window part of the show-command cmdlet. + /// Implements the WPF window part of the show-command cmdlet. /// internal class ShowCommandHelper : IDisposable { @@ -289,7 +289,7 @@ private ShowCommandHelper() } /// - /// Finalizes an instance of the ShowCommandHelper class. + /// Finalizes an instance of the class. /// ~ShowCommandHelper() { @@ -489,37 +489,6 @@ private static string GetSerializedCommandScript() @"Remove-Item -Path 'function:\PSGetSerializedShowCommandInfo' -Force"); } - /// - /// Gets the command to be run to in order to import a module and refresh the command data. - /// - /// Module we want to import. - /// Boolean flag determining whether Show-Command is queried in the local or remote runspace scenario. - /// Boolean flag to indicate that it is the second attempt to query Show-Command data. - /// The command to be run to in order to import a module and refresh the command data. - internal static string GetImportModuleCommand(string module, bool isRemoteRunspace = false, bool isFirstChance = true) - { - string scriptBase = "Import-Module " + ShowCommandHelper.SingleQuote(module); - - if (isRemoteRunspace) - { - if (isFirstChance) - { - scriptBase += ";@(Get-Command " + ShowCommandHelper.CommandTypeSegment + @" -ShowCommandInfo )"; - } - else - { - scriptBase += GetSerializedCommandScript(); - } - } - else - { - scriptBase += ";@(Get-Command " + ShowCommandHelper.CommandTypeSegment + ")"; - } - - scriptBase += ShowCommandHelper.GetGetModuleSuffix(); - return scriptBase; - } - /// /// Gets the command to be run in order to show help for a command. /// @@ -672,7 +641,7 @@ internal static AllModulesViewModel GetNewAllModulesViewModel(AllModulesViewMode /// /// Gets an error message to be displayed when failed to import a module. /// - /// Command belongiong to the module to import. + /// Command belonging to the module to import. /// Module to import. /// Error importing the module. /// An error message to be displayed when failed to import a module. @@ -750,7 +719,7 @@ private static object GetPropertyValue(Type type, object obj, string propertyNam try { - return property.GetValue(obj, new object[] { }); + return property.GetValue(obj, Array.Empty()); } catch (ArgumentException) { @@ -794,7 +763,7 @@ private static bool SetPropertyValue(Type type, object obj, string propertyName, try { - property.SetValue(obj, value, new object[] { }); + property.SetValue(obj, value, Array.Empty()); } catch (ArgumentException) { @@ -1000,7 +969,7 @@ private void ImportModuleDone(Dictionary imported { this.window.Dispatcher.Invoke( new SendOrPostCallback( - delegate (object ignored) + delegate(object ignored) { this.allModulesViewModel = ShowCommandHelper.GetNewAllModulesViewModel( this.allModulesViewModel, @@ -1050,7 +1019,7 @@ private void DisplayHelp(Collection getHelpResults) { this.window.Dispatcher.Invoke( new SendOrPostCallback( - delegate (object ignored) + delegate(object ignored) { HelpWindow help = new HelpWindow(getHelpResults[0]); help.Owner = this.window; @@ -1200,7 +1169,7 @@ private void Buttons_CopyClick(object sender, RoutedEventArgs e) } /// - /// Sets a succesfull dialog result and then closes the window. + /// Sets a successful dialog result and then closes the window. /// /// Event sender. /// Event arguments. diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/CommonUtils.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/CommonUtils.cs index fb788b4d26c..dfe046ec8ca 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/CommonUtils.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/CommonUtils.cs @@ -40,12 +40,12 @@ uint dwFlags [DllImport(LocalizationDllName, EntryPoint = "GetUserDefaultLangID", CallingConvention = CallingConvention.Winapi, SetLastError = true)] private static extern ushort GetUserDefaultLangID(); - public static uint FormatMessageFromModule(uint lastError, string moduleName, out String msg) + public static uint FormatMessageFromModule(uint lastError, string moduleName, out string msg) { Debug.Assert(!string.IsNullOrEmpty(moduleName)); uint formatError = 0; - msg = String.Empty; + msg = string.Empty; IntPtr moduleHandle = LoadLibraryEx(moduleName, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE); if (moduleHandle == IntPtr.Zero) diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/CoreCLR/Stubs.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/CoreCLR/Stubs.cs deleted file mode 100644 index 5d57816c0e7..00000000000 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/CoreCLR/Stubs.cs +++ /dev/null @@ -1,252 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if CORECLR - -namespace System.Diagnostics -{ - /// - /// Indicates whether the performance counter category can have multiple instances. - /// - /// 1 - public enum PerformanceCounterCategoryType - { - /// - /// The instance functionality for the performance counter category is unknown. - /// - Unknown = -1, - - /// - /// The performance counter category can have only a single instance. - /// - SingleInstance, - - /// - /// The performance counter category can have multiple instances. - /// - MultiInstance - } - - /// - /// Specifies the formula used to calculate the - /// method for a instance. - /// - /// 2 - public enum PerformanceCounterType - { - /// - /// An instantaneous counter that shows the most recently observed value. - /// Used, for example, to maintain a simple count of items or operations. - /// - NumberOfItems32 = 65536, - - /// - /// An instantaneous counter that shows the most recently observed value. - /// Used, for example, to maintain a simple count of a very large number - /// of items or operations. It is the same as NumberOfItems32 except that - /// it uses larger fields to accommodate larger values. - /// - NumberOfItems64 = 65792, - - /// - /// An instantaneous counter that shows the most recently observed value - /// in hexadecimal format. Used, for example, to maintain a simple count - /// of items or operations. - NumberOfItemsHEX32 = 0, - - /// - /// An instantaneous counter that shows the most recently observed value. - /// Used, for example, to maintain a simple count of a very large number - /// of items or operations. It is the same as NumberOfItemsHEX32 except - /// that it uses larger fields to accommodate larger values. - /// - NumberOfItemsHEX64 = 256, - - /// - /// A difference counter that shows the average number of operations completed - /// during each second of the sample interval. Counters of this type measure - /// time in ticks of the system clock. - RateOfCountsPerSecond32 = 272696320, - - /// - /// A difference counter that shows the average number of operations completed - /// during each second of the sample interval. Counters of this type measure - /// time in ticks of the system clock. This counter type is the same as the - /// RateOfCountsPerSecond32 type, but it uses larger fields to accommodate - /// larger values to track a high-volume number of items or operations per - /// second, such as a byte-transmission rate. - /// - RateOfCountsPerSecond64 = 272696576, - - /// - /// An average counter designed to monitor the average length of a queue - /// to a resource over time. It shows the difference between the queue - /// lengths observed during the last two sample intervals divided by the - /// duration of the interval. This type of counter is typically used to - /// track the number of items that are queued or waiting. - /// - CountPerTimeInterval32 = 4523008, - - /// - /// An average counter that monitors the average length of a queue to a - /// resource over time. Counters of this type display the difference - /// between the queue lengths observed during the last two sample intervals, - /// divided by the duration of the interval. This counter type is the same - /// as CountPerTimeInterval32 except that it uses larger fields to - /// accommodate larger values. This type of counter is typically used - /// to track a high-volume or very large number of items that are queued or waiting. - /// - CountPerTimeInterval64 = 4523264, - - /// - /// An instantaneous percentage counter that shows the ratio of a subset - /// to its set as a percentage. For example, it compares the number of bytes - /// in use on a disk to the total number of bytes on the disk. - /// Counters of this type display the current percentage only, not an average - /// over time. - /// - RawFraction = 537003008, - - /// - /// A base counter that stores the denominator of a counter that presents a - /// general arithmetic fraction. Check that this value is greater than zero - /// before using it as the denominator in a RawFraction value calculation. - /// - RawBase = 1073939459, - - /// - /// An average counter that measures the time it takes, on average, to - /// complete a process or operation. Counters of this type display a - /// ratio of the total elapsed time of the sample interval to the number - /// of processes or operations completed during that time. This counter - /// type measures time in ticks of the system clock. - /// - AverageTimer32 = 805438464, - - /// - /// A base counter that is used in the calculation of time or count averages, - /// such as AverageTimer32 and AverageCount64. Stores the denominator for - /// calculating a counter to present "time per operation" or "count per operation". - /// - AverageBase = 1073939458, - - /// - /// An average counter that shows how many items are processed, on average, - /// during an operation. Counters of this type display a ratio of the items - /// processed to the number of operations completed. The ratio is calculated - /// by comparing the number of items processed during the last interval to - /// the number of operations completed during the last interval. - /// - AverageCount64 = 1073874176, - - /// - /// A percentage counter that shows the average ratio of hits to all - /// operations during the last two sample intervals. - /// - SampleFraction = 549585920, - - /// - /// An average counter that shows the average number of operations completed - /// in one second. When a counter of this type samples the data, each sampling - /// interrupt returns one or zero. The counter data is the number of ones that - /// were sampled. It measures time in units of ticks of the system performance timer. - /// - SampleCounter = 4260864, - - /// - /// A base counter that stores the number of sampling interrupts taken - /// and is used as a denominator in the sampling fraction. The sampling - /// fraction is the number of samples that were 1 (or true) for a sample - /// interrupt. Check that this value is greater than zero before using - /// it as the denominator in a calculation of SampleFraction. - /// - SampleBase = 1073939457, - - /// - /// A percentage counter that shows the average time that a component is - /// active as a percentage of the total sample time. - /// - CounterTimer = 541132032, - - /// - /// A percentage counter that displays the average percentage of active - /// time observed during sample interval. The value of these counters is - /// calculated by monitoring the percentage of time that the service was - /// inactive and then subtracting that value from 100 percent. - /// - CounterTimerInverse = 557909248, - - /// A percentage counter that shows the active time of a component - /// as a percentage of the total elapsed time of the sample interval. - /// It measures time in units of 100 nanoseconds (ns). Counters of this - /// type are designed to measure the activity of one component at a time. - /// - Timer100Ns = 542180608, - - /// - /// A percentage counter that shows the average percentage of active time - /// observed during the sample interval. - /// - Timer100NsInverse = 558957824, - - /// - /// A difference timer that shows the total time between when the component - /// or process started and the time when this value is calculated. - /// - ElapsedTime = 807666944, - - /// - /// A percentage counter that displays the active time of one or more - /// components as a percentage of the total time of the sample interval. - /// Because the numerator records the active time of components operating - /// simultaneously, the resulting percentage can exceed 100 percent. - /// - CounterMultiTimer = 574686464, - - /// - /// A percentage counter that shows the active time of one or more components - /// as a percentage of the total time of the sample interval. It derives - /// the active time by measuring the time that the components were not - /// active and subtracting the result from 100 percent by the number of - /// objects monitored. - /// - CounterMultiTimerInverse = 591463680, - - /// - /// A percentage counter that shows the active time of one or more components - /// as a percentage of the total time of the sample interval. It measures - /// time in 100 nanosecond (ns) units. - CounterMultiTimer100Ns = 575735040, - - /// - /// A percentage counter that shows the active time of one or more components - /// as a percentage of the total time of the sample interval. Counters of - /// this type measure time in 100 nanosecond (ns) units. They derive the - /// active time by measuring the time that the components were not active - /// and subtracting the result from multiplying 100 percent by the number - /// of objects monitored. - CounterMultiTimer100NsInverse = 592512256, - - /// - /// A base counter that indicates the number of items sampled. It is used - /// as the denominator in the calculations to get an average among the - /// items sampled when taking timings of multiple, but similar items. - /// Used with CounterMultiTimer, CounterMultiTimerInverse, CounterMultiTimer100Ns, - /// and CounterMultiTimer100NsInverse. - CounterMultiBase = 1107494144, - - /// - /// A difference counter that shows the change in the measured attribute - /// between the two most recent sample intervals. - /// - CounterDelta32 = 4195328, - - /// - /// A difference counter that shows the change in the measured attribute - /// between the two most recent sample intervals. It is the same as the - /// CounterDelta32 counter type except that is uses larger fields to - /// accomodate larger values. - CounterDelta64 = 4195584 - } -} -#endif diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/GetCounterCommand.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/GetCounterCommand.cs index 4a6941cc13f..0122ad1941f 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/GetCounterCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/GetCounterCommand.cs @@ -248,7 +248,7 @@ protected override void ProcessRecord() break; default: - Debug.Fail(string.Format(CultureInfo.InvariantCulture, "Invalid parameter set name: {0}", ParameterSetName)); + Debug.Fail(string.Create(CultureInfo.InvariantCulture, $"Invalid parameter set name: {ParameterSetName}")); break; } } @@ -587,13 +587,14 @@ private List CombineMachinesAndCounterPaths() { foreach (string machine in ComputerName) { + string slashBeforePath = path.Length > 0 && path[0] == '\\' ? string.Empty : "\\"; if (machine.StartsWith("\\\\", StringComparison.OrdinalIgnoreCase)) { - retColl.Add(machine + "\\" + path); + retColl.Add(machine + slashBeforePath + path); } else { - retColl.Add("\\\\" + machine + "\\" + path); + retColl.Add("\\\\" + machine + slashBeforePath + path); } } } diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventCommand.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventCommand.cs index 1a74da73d1c..3aec30c9e4c 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventCommand.cs @@ -23,6 +23,9 @@ namespace Microsoft.PowerShell.Commands /// /// Class that implements the Get-WinEvent cmdlet. /// + [OutputType(typeof(EventRecord), ParameterSetName = new string[] { "GetLogSet", "GetProviderSet", "FileSet", "HashQuerySet", "XmlQuerySet" })] + [OutputType(typeof(ProviderMetadata), ParameterSetName = new string[] { "ListProviderSet" })] + [OutputType(typeof(EventLogConfiguration), ParameterSetName = new string[] { "ListLogSet" })] [Cmdlet(VerbsCommon.Get, "WinEvent", DefaultParameterSetName = "GetLogSet", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096581")] public sealed class GetWinEventCommand : PSCmdlet { @@ -144,8 +147,8 @@ public sealed class GetWinEventCommand : PSCmdlet ValueFromPipelineByPropertyName = false, HelpMessageBaseName = "GetEventResources", HelpMessageResourceId = "MaxEventsParamHelp")] - [ValidateRange((Int64)1, Int64.MaxValue)] - public Int64 MaxEvents { get; set; } = -1; + [ValidateRange((long)1, long.MaxValue)] + public long MaxEvents { get; set; } = -1; /// /// ComputerName parameter. @@ -393,7 +396,7 @@ protected override void ProcessRecord() break; default: - WriteDebug(string.Format(CultureInfo.InvariantCulture, "Invalid parameter set name: {0}", ParameterSetName)); + WriteDebug(string.Create(CultureInfo.InvariantCulture, $"Invalid parameter set name: {ParameterSetName}")); break; } } @@ -489,7 +492,7 @@ private void ProcessGetProvider() foreach (string log in _providersByLogMap.Keys) { logQuery = new EventLogQuery(log, PathType.LogName, AddProviderPredicatesToFilter(_providersByLogMap[log])); - WriteVerbose(string.Format(CultureInfo.InvariantCulture, "Log {0} will be queried", log)); + WriteVerbose(string.Create(CultureInfo.InvariantCulture, $"Log {log} will be queried")); } } @@ -677,7 +680,7 @@ private void ProcessFile() foreach (string resolvedPath in resolvedPaths) { _resolvedPaths.Add(resolvedPath); - WriteVerbose(string.Format(CultureInfo.InvariantCulture, "Found file {0}", resolvedPath)); + WriteVerbose(string.Create(CultureInfo.InvariantCulture, $"Found file {resolvedPath}")); } } @@ -777,7 +780,7 @@ private void ReadEvents(EventLogQuery logQuery) { using (EventLogReader readerObj = new(logQuery)) { - Int64 numEvents = 0; + long numEvents = 0; EventRecord evtObj = null; while (true) @@ -905,7 +908,7 @@ private string BuildStructuredQuery(EventLogSession eventLogSession) break; default: - WriteDebug(string.Format(CultureInfo.InvariantCulture, "Invalid parameter set name: {0}", ParameterSetName)); + WriteDebug(string.Create(CultureInfo.InvariantCulture, $"Invalid parameter set name: {ParameterSetName}")); break; } @@ -1184,8 +1187,7 @@ private string BuildStructuredQueryFromHashTable(EventLogSession eventLogSession // // Build xpath for // - Hashtable suppresshash = hash[hashkey_supress_lc] as Hashtable; - if (suppresshash != null) + if (hash[hashkey_supress_lc] is Hashtable suppresshash) { xpathStringSuppress = BuildXPathFromHashTable(suppresshash); } @@ -1252,8 +1254,7 @@ private string BuildStructuredQueryFromHashTable(EventLogSession eventLogSession private static string HandleEventIdHashValue(object value) { StringBuilder ret = new(); - Array idsArray = value as Array; - if (idsArray != null) + if (value is Array idsArray) { ret.Append('('); for (int i = 0; i < idsArray.Length; i++) @@ -1282,8 +1283,7 @@ private static string HandleEventIdHashValue(object value) private static string HandleLevelHashValue(object value) { StringBuilder ret = new(); - Array levelsArray = value as Array; - if (levelsArray != null) + if (value is Array levelsArray) { ret.Append('('); for (int i = 0; i < levelsArray.Length; i++) @@ -1311,11 +1311,10 @@ private static string HandleLevelHashValue(object value) // private string HandleKeywordHashValue(object value) { - Int64 keywordsMask = 0; - Int64 keywordLong = 0; + long keywordsMask = 0; + long keywordLong = 0; - Array keywordArray = value as Array; - if (keywordArray != null) + if (value is Array keywordArray) { foreach (object keyword in keywordArray) { @@ -1470,8 +1469,7 @@ private string HandleEndTimeHashValue(object value, Hashtable hash) private static string HandleDataHashValue(object value) { StringBuilder ret = new(); - Array dataArray = value as Array; - if (dataArray != null) + if (value is Array dataArray) { ret.Append('('); for (int i = 0; i < dataArray.Length; i++) @@ -1501,8 +1499,7 @@ private static string HandleDataHashValue(object value) private static string HandleNamedDataHashValue(string key, object value) { StringBuilder ret = new(); - Array dataArray = value as Array; - if (dataArray != null) + if (value is Array dataArray) { ret.Append('('); for (int i = 0; i < dataArray.Length; i++) @@ -1609,7 +1606,7 @@ private bool ValidateLogName(string logName, EventLogSession eventLogSession) // Returns true and keyLong ref if successful. // Writes an error and returns false if keyString cannot be converted. // - private bool KeywordStringToInt64(string keyString, ref Int64 keyLong) + private bool KeywordStringToInt64(string keyString, ref long keyLong) { try { @@ -1749,8 +1746,7 @@ private void CheckHashTablesForNullValues() } else { - Array eltArray = value as Array; - if (eltArray != null) + if (value is Array eltArray) { foreach (object elt in eltArray) { @@ -2044,7 +2040,7 @@ private void FindProvidersByLogForWildcardPatterns(EventLogSession eventLogSessi || (wildProvPattern.IsMatch(provName))) { - WriteVerbose(string.Format(CultureInfo.InvariantCulture, "Found matching provider: {0}", provName)); + WriteVerbose(string.Create(CultureInfo.InvariantCulture, $"Found matching provider: {provName}")); AddLogsForProviderToInternalMap(eventLogSession, provName); bMatched = true; } diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventSnapin.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventSnapin.cs index df3835d9e18..a4693c454c0 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventSnapin.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventSnapin.cs @@ -81,7 +81,7 @@ public override string DescriptionResource } /// - /// Get type files to be used for this mshsnapin. + /// Get type files to be used for this PSSnapin. /// public override string[] Types { @@ -94,7 +94,7 @@ public override string[] Types private string[] _types = new string[] { "getevent.types.ps1xml" }; /// - /// Get format files to be used for this mshsnapin. + /// Get format files to be used for this PSSnapin. /// public override string[] Formats { diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/ImportCounterCommand.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/ImportCounterCommand.cs index d571df34084..ed4cdb51ff9 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/ImportCounterCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/ImportCounterCommand.cs @@ -240,7 +240,7 @@ protected override void EndProcessing() break; default: - Debug.Assert(false, string.Format(CultureInfo.InvariantCulture, "Invalid parameter set name: {0}", ParameterSetName)); + Debug.Assert(false, $"Invalid parameter set name: {ParameterSetName}"); break; } 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 02e3b785509..1aa071fd325 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs index cb74c292a3e..b0b102870c4 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs @@ -317,8 +317,7 @@ protected override void ProcessRecord() /// protected override void EndProcessing() { - if (_providerMetadata != null) - _providerMetadata.Dispose(); + _providerMetadata?.Dispose(); base.EndProcessing(); } diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs index 97eff22db0d..b761aa6e30b 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs @@ -267,28 +267,65 @@ private struct PDH_TIME_INFO // We only need dwType and lDefaultScale fields from this structure. // We access those fields directly. The struct is here for reference only. // - [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)] - private struct PDH_COUNTER_INFO + [StructLayout(LayoutKind.Sequential)] + private unsafe struct PDH_COUNTER_INFO { - [FieldOffset(0)] public UInt32 dwLength; - [FieldOffset(4)] public UInt32 dwType; - [FieldOffset(8)] public UInt32 CVersion; - [FieldOffset(12)] public UInt32 CStatus; - [FieldOffset(16)] public UInt32 lScale; - [FieldOffset(20)] public UInt32 lDefaultScale; - [FieldOffset(24)] public IntPtr dwUserData; - [FieldOffset(32)] public IntPtr dwQueryUserData; - [FieldOffset(40)] public string szFullPath; - - [FieldOffset(48)] public string szMachineName; - [FieldOffset(56)] public string szObjectName; - [FieldOffset(64)] public string szInstanceName; - [FieldOffset(72)] public string szParentInstance; - [FieldOffset(80)] public UInt32 dwInstanceIndex; - [FieldOffset(88)] public string szCounterName; - - [FieldOffset(96)] public string szExplainText; - [FieldOffset(104)] public IntPtr DataBuffer; + public uint Length; + public uint Type; + public uint CVersion; + public uint CStatus; + public int Scale; + public int DefaultScale; + public ulong UserData; + public ulong QueryUserData; + public ushort* FullPath; + public _Anonymous_e__Union Anonymous; + public ushort* ExplainText; + public fixed uint DataBuffer[1]; + + [StructLayout(LayoutKind.Explicit)] + internal struct _Anonymous_e__Union + { + [FieldOffset(0)] + public PDH_DATA_ITEM_PATH_ELEMENTS_blittable DataItemPath; + + [FieldOffset(0)] + public PDH_COUNTER_PATH_ELEMENTS_blittable CounterPath; + + [FieldOffset(0)] + public _Anonymous_e__Struct Anonymous; + + [StructLayout(LayoutKind.Sequential)] + internal struct PDH_DATA_ITEM_PATH_ELEMENTS_blittable + { + public ushort* MachineName; + public Guid ObjectGUID; + public uint ItemId; + public ushort* InstanceName; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct PDH_COUNTER_PATH_ELEMENTS_blittable + { + public ushort* MachineName; + public ushort* ObjectName; + public ushort* InstanceName; + public ushort* ParentInstance; + public uint InstanceIndex; + public ushort* CounterName; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct _Anonymous_e__Struct + { + public ushort* MachineName; + public ushort* ObjectName; + public ushort* InstanceName; + public ushort* ParentInstance; + public uint InstanceIndex; + public ushort* CounterName; + } + } } [DllImport("pdh.dll", CharSet = CharSet.Unicode)] @@ -476,8 +513,8 @@ private static uint GetCounterInfoPlus(IntPtr hCounter, out UInt32 counterType, if (res == PdhResults.PDH_CSTATUS_VALID_DATA && bufCounterInfo != IntPtr.Zero) { PDH_COUNTER_INFO pdhCounterInfo = (PDH_COUNTER_INFO)Marshal.PtrToStructure(bufCounterInfo, typeof(PDH_COUNTER_INFO)); - counterType = pdhCounterInfo.dwType; - defaultScale = pdhCounterInfo.lDefaultScale; + counterType = pdhCounterInfo.Type; + defaultScale = (uint)pdhCounterInfo.DefaultScale; } } finally 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 31e6536380f..a79278370a8 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 @@ - + diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/SessionBasedWrapper.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/SessionBasedWrapper.cs index fb6b638b9df..3e26b2e2e98 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/SessionBasedWrapper.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/SessionBasedWrapper.cs @@ -82,11 +82,7 @@ protected TSession[] Session set { - if (value == null) - { - throw new ArgumentNullException("value"); - } - + ArgumentNullException.ThrowIfNull(value); _session = value; _sessionWasSpecified = true; } @@ -495,8 +491,7 @@ private IEnumerable GetSessionsToActAgainst(QueryBuilder queryBuilder) return this.Session; } - var sessionBoundQueryBuilder = queryBuilder as ISessionBoundQueryBuilder; - if (sessionBoundQueryBuilder != null) + if (queryBuilder is ISessionBoundQueryBuilder sessionBoundQueryBuilder) { TSession sessionOfTheQueryBuilder = sessionBoundQueryBuilder.GetTargetSession(); if (sessionOfTheQueryBuilder != null) @@ -582,8 +577,9 @@ private TSession GetImpliedSession() /// if successful method invocations should emit downstream the being operated on. public override void ProcessRecord(TObjectInstance objectInstance, MethodInvocationInfo methodInvocationInfo, bool passThru) { - if (objectInstance == null) throw new ArgumentNullException(nameof(objectInstance)); - if (methodInvocationInfo == null) throw new ArgumentNullException(nameof(methodInvocationInfo)); + ArgumentNullException.ThrowIfNull(objectInstance); + + ArgumentNullException.ThrowIfNull(methodInvocationInfo); foreach (TSession sessionForJob in this.GetSessionsToActAgainst(objectInstance)) { @@ -608,7 +604,7 @@ public override void ProcessRecord(TObjectInstance objectInstance, MethodInvocat /// Method invocation details. public override void ProcessRecord(MethodInvocationInfo methodInvocationInfo) { - if (methodInvocationInfo == null) throw new ArgumentNullException(nameof(methodInvocationInfo)); + ArgumentNullException.ThrowIfNull(methodInvocationInfo); foreach (TSession sessionForJob in this.GetSessionsToActAgainst(methodInvocationInfo)) { @@ -688,10 +684,7 @@ public override void EndProcessing() public override void StopProcessing() { Job jobToStop = _parentJob; - if (jobToStop != null) - { - jobToStop.StopJob(); - } + jobToStop?.StopJob(); base.StopProcessing(); } 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 808ca1a4de9..675c6c71404 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CimJobException.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CimJobException.cs @@ -15,7 +15,6 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// /// Represents an error during execution of a CIM job. /// - [Serializable] public class CimJobException : SystemException, IContainsErrorRecord { #region Standard constructors and methods required for all exceptions @@ -50,32 +49,12 @@ public CimJobException(string message, Exception inner) : base(message, inner) /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected CimJobException( SerializationInfo info, - StreamingContext context) : base(info, context) + StreamingContext context) { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - _errorRecord = (ErrorRecord)info.GetValue("errorRecord", typeof(ErrorRecord)); - } - - /// - /// Sets the SerializationInfo with information about the exception. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - info.AddValue("errorRecord", _errorRecord); + throw new NotSupportedException(); } #endregion @@ -104,16 +83,14 @@ internal static CimJobException CreateFromAnyException( Dbg.Assert(jobContext != null, "Caller should verify jobContext != null"); Dbg.Assert(inner != null, "Caller should verify inner != null"); - CimException cimException = inner as CimException; - if (cimException != null) + if (inner is CimException cimException) { return CreateFromCimException(jobDescription, jobContext, cimException); } string message = BuildErrorMessage(jobDescription, jobContext, inner.Message); CimJobException cimJobException = new(message, inner); - var containsErrorRecord = inner as IContainsErrorRecord; - if (containsErrorRecord != null) + if (inner is IContainsErrorRecord containsErrorRecord) { cimJobException.InitializeErrorRecord( jobContext, @@ -362,8 +339,7 @@ internal bool IsTerminatingError { get { - var cimException = this.InnerException as CimException; - if ((cimException == null) || (cimException.ErrorData == null)) + if ((this.InnerException is not CimException cimException) || (cimException.ErrorData == null)) { return false; } @@ -374,7 +350,7 @@ internal bool IsTerminatingError return false; } - UInt16 perceivedSeverityValue = (UInt16)perceivedSeverityProperty.Value; + ushort perceivedSeverityValue = (ushort)perceivedSeverityProperty.Value; if (perceivedSeverityValue != 7) { /* from CIM Schema: Interop\CIM_Error.mof: diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ExtrinsicMethodInvocationJob.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ExtrinsicMethodInvocationJob.cs index 1194da4ab0f..9eaac2b3d7f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ExtrinsicMethodInvocationJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ExtrinsicMethodInvocationJob.cs @@ -66,8 +66,7 @@ private void ProcessOutParameter(CimMethodResult methodResult, MethodParameter m methodParameter.Value = dotNetValue; cmdletOutput.Add(methodParameter.Name, methodParameter); - var cimInstances = dotNetValue as CimInstance[]; - if (cimInstances != null) + if (dotNetValue is CimInstance[] cimInstances) { foreach (var instance in cimInstances) { @@ -75,8 +74,7 @@ private void ProcessOutParameter(CimMethodResult methodResult, MethodParameter m } } - var cimInstance = dotNetValue as CimInstance; - if (cimInstance != null) + if (dotNetValue is CimInstance cimInstance) { CimCmdletAdapter.AssociateSessionOfOriginWithInstance(cimInstance, this.JobContext.Session); } @@ -105,7 +103,7 @@ private void OnNext(CimMethodResult methodResult) if (cmdletOutput.Count == 1) { - var singleOutputParameter = cmdletOutput.Values.Single(); + var singleOutputParameter = cmdletOutput.Values.First(); if (singleOutputParameter.Value == null) { return; @@ -191,15 +189,13 @@ public override void OnNext(CimMethodResultBase item) this.ExceptionSafeWrapper( delegate { - var methodResult = item as CimMethodResult; - if (methodResult != null) + if (item is CimMethodResult methodResult) { this.OnNext(methodResult); return; } - var streamedResult = item as CimMethodStreamedResult; - if (streamedResult != null) + if (item is CimMethodStreamedResult streamedResult) { this.OnNext(streamedResult); return; diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/MethodInvocationJobBase.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/MethodInvocationJobBase.cs index eb636b4a759..84b13b82097 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/MethodInvocationJobBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/MethodInvocationJobBase.cs @@ -66,8 +66,8 @@ private IEnumerable GetMethodInputParametersCore(Func GetMethodInputParameters() { - var allMethodParameters = this.GetMethodInputParametersCore(p => !p.Name.StartsWith(CustomOperationOptionPrefix, StringComparison.OrdinalIgnoreCase)); - var methodParametersWithInputValue = allMethodParameters.Where(p => p.IsValuePresent); + var allMethodParameters = this.GetMethodInputParametersCore(static p => !p.Name.StartsWith(CustomOperationOptionPrefix, StringComparison.OrdinalIgnoreCase)); + var methodParametersWithInputValue = allMethodParameters.Where(static p => p.IsValuePresent); return methodParametersWithInputValue; } @@ -81,7 +81,7 @@ internal override CimCustomOptionsDictionary CalculateJobSpecificCustomOptions() IDictionary result = new Dictionary(StringComparer.OrdinalIgnoreCase); IEnumerable customOptions = this - .GetMethodInputParametersCore(p => p.Name.StartsWith(CustomOperationOptionPrefix, StringComparison.OrdinalIgnoreCase)); + .GetMethodInputParametersCore(static p => p.Name.StartsWith(CustomOperationOptionPrefix, StringComparison.OrdinalIgnoreCase)); foreach (MethodParameter customOption in customOptions) { if (customOption.Value == null) @@ -104,7 +104,7 @@ internal IEnumerable GetMethodOutputParameters() } var outParameters = allParameters_plus_returnValue - .Where(p => ((p.Bindings & (MethodParameterBindings.Out | MethodParameterBindings.Error)) != 0)); + .Where(static p => ((p.Bindings & (MethodParameterBindings.Out | MethodParameterBindings.Error)) != 0)); return outParameters; } diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/TerminatingErrorTracker.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/TerminatingErrorTracker.cs index 515e19f895c..6b716fa5501 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/TerminatingErrorTracker.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/TerminatingErrorTracker.cs @@ -19,7 +19,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// /// Tracks (per-session) terminating errors in a given cmdlet invocation. /// - internal class TerminatingErrorTracker + internal sealed class TerminatingErrorTracker { #region Getting tracker for a given cmdlet invocation @@ -53,8 +53,7 @@ private static int GetNumberOfSessions(InvocationInfo invocationInfo) int maxNumberOfSessionsIndicatedByCimInstanceArguments = 1; foreach (object cmdletArgument in invocationInfo.BoundParameters.Values) { - CimInstance[] array = cmdletArgument as CimInstance[]; - if (array != null) + if (cmdletArgument is CimInstance[] array) { int numberOfSessionsAssociatedWithArgument = array .Select(CimCmdletAdapter.GetSessionOfOriginFromCimInstance) 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 6dc65424f50..d787389e9f0 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimChildJobBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimChildJobBase.cs @@ -111,7 +111,7 @@ private static bool IsWsManQuotaReached(Exception exception) return false; } - WsManErrorCode wsManErrorCode = (WsManErrorCode)(UInt32)(errorCodeProperty.Value); + WsManErrorCode wsManErrorCode = (WsManErrorCode)(uint)(errorCodeProperty.Value); switch (wsManErrorCode) // error codes that should result in sleep-and-retry are based on an email from Ryan { case WsManErrorCode.ERROR_WSMAN_QUOTA_MAX_SHELLS: @@ -315,10 +315,7 @@ internal override void StartJob() this.ExceptionSafeWrapper(delegate { IObservable observable = this.GetCimOperation(); - if (observable != null) - { - observable.Subscribe(this); - } + observable?.Subscribe(this); }); }); } @@ -423,11 +420,11 @@ internal CimOperationOptions CreateOperationOptions() (_jobContext.WarningActionPreference == ActionPreference.Ignore) ) && (!_jobContext.IsRunningInBackground)) { - operationOptions.DisableChannel((UInt32)MessageChannel.Warning); + operationOptions.DisableChannel((uint)MessageChannel.Warning); } else { - operationOptions.EnableChannel((UInt32)MessageChannel.Warning); + operationOptions.EnableChannel((uint)MessageChannel.Warning); } if (( @@ -435,11 +432,11 @@ internal CimOperationOptions CreateOperationOptions() (_jobContext.VerboseActionPreference == ActionPreference.Ignore) ) && (!_jobContext.IsRunningInBackground)) { - operationOptions.DisableChannel((UInt32)MessageChannel.Verbose); + operationOptions.DisableChannel((uint)MessageChannel.Verbose); } else { - operationOptions.EnableChannel((UInt32)MessageChannel.Verbose); + operationOptions.EnableChannel((uint)MessageChannel.Verbose); } if (( @@ -447,11 +444,11 @@ internal CimOperationOptions CreateOperationOptions() (_jobContext.DebugActionPreference == ActionPreference.Ignore) ) && (!_jobContext.IsRunningInBackground)) { - operationOptions.DisableChannel((UInt32)MessageChannel.Debug); + operationOptions.DisableChannel((uint)MessageChannel.Debug); } else { - operationOptions.EnableChannel((UInt32)MessageChannel.Debug); + operationOptions.EnableChannel((uint)MessageChannel.Debug); } switch (this.JobContext.ShouldProcessOptimization) @@ -522,10 +519,7 @@ internal CimOperationOptions CreateOperationOptions() } CimCustomOptionsDictionary jobSpecificCustomOptions = this.GetJobSpecificCustomOptions(); - if (jobSpecificCustomOptions != null) - { - jobSpecificCustomOptions.Apply(operationOptions, CimSensitiveValueConverter); - } + jobSpecificCustomOptions?.Apply(operationOptions, CimSensitiveValueConverter); return operationOptions; } @@ -629,8 +623,7 @@ internal void ReportJobFailure(IContainsErrorRecord exception) } else { - CimJobException cje = exception as CimJobException; - if ((cje != null) && (cje.IsTerminatingError)) + if ((exception is CimJobException cje) && (cje.IsTerminatingError)) { terminatingErrorTracker.MarkSessionAsTerminated(this.JobContext.Session, out sessionWasAlreadyTerminated); isThisTerminatingError = true; @@ -763,7 +756,7 @@ internal void FinishProgressReporting() #region Handling extended semantics callbacks - private void WriteProgressCallback(string activity, string currentOperation, string statusDescription, UInt32 percentageCompleted, UInt32 secondsRemaining) + private void WriteProgressCallback(string activity, string currentOperation, string statusDescription, uint percentageCompleted, uint secondsRemaining) { if (string.IsNullOrEmpty(activity)) { @@ -775,28 +768,28 @@ private void WriteProgressCallback(string activity, string currentOperation, str statusDescription = this.StatusMessage; } - Int32 signedSecondsRemaining; - if (secondsRemaining == UInt32.MaxValue) + int signedSecondsRemaining; + if (secondsRemaining == uint.MaxValue) { signedSecondsRemaining = -1; } - else if (secondsRemaining <= Int32.MaxValue) + else if (secondsRemaining <= int.MaxValue) { - signedSecondsRemaining = (Int32)secondsRemaining; + signedSecondsRemaining = (int)secondsRemaining; } else { - signedSecondsRemaining = Int32.MaxValue; + signedSecondsRemaining = int.MaxValue; } - Int32 signedPercentageComplete; - if (percentageCompleted == UInt32.MaxValue) + int signedPercentageComplete; + if (percentageCompleted == uint.MaxValue) { signedPercentageComplete = -1; } else if (percentageCompleted <= 100) { - signedPercentageComplete = (Int32)percentageCompleted; + signedPercentageComplete = (int)percentageCompleted; } else { @@ -825,7 +818,7 @@ private enum MessageChannel Debug = 2, } - private void WriteMessageCallback(UInt32 channel, string message) + private void WriteMessageCallback(uint channel, string message) { this.ExceptionSafeWrapper( delegate @@ -1019,8 +1012,7 @@ internal static bool IsShowComputerNameMarkerPresent(CimInstance cimInstance) internal static void AddShowComputerNameMarker(PSObject pso) { - PSPropertyInfo psShowComputerNameProperty = pso.InstanceMembers[RemotingConstants.ShowComputerNameNoteProperty] as PSPropertyInfo; - if (psShowComputerNameProperty != null) + if (pso.InstanceMembers[RemotingConstants.ShowComputerNameNoteProperty] is PSPropertyInfo psShowComputerNameProperty) { psShowComputerNameProperty.Value = true; } @@ -1053,10 +1045,7 @@ internal override void WriteObject(object outputObject) if (this.JobContext.ShowComputerName) { - if (pso == null) - { - pso = PSObject.AsPSObject(outputObject); - } + pso ??= PSObject.AsPSObject(outputObject); AddShowComputerNameMarker(pso); if (cimInstance == null) 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 91500758a06..17a085b7ce5 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimConverter.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimConverter.cs @@ -26,7 +26,7 @@ namespace Microsoft.PowerShell.Cim { internal class CimSensitiveValueConverter : IDisposable { - private class SensitiveString : IDisposable + private sealed class SensitiveString : IDisposable { private GCHandle _gcHandle; private string _string; @@ -50,15 +50,9 @@ internal SensitiveString(int numberOfCharacters) private unsafe void Copy(char* source, int offset, int charsToCopy) { - if ((offset < 0) || (offset >= _string.Length)) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if (offset + charsToCopy > _string.Length) - { - throw new ArgumentOutOfRangeException(nameof(charsToCopy)); - } + ArgumentOutOfRangeException.ThrowIfNegative(offset); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(offset, _string.Length); + ArgumentOutOfRangeException.ThrowIfGreaterThan(offset + charsToCopy, _string.Length, nameof(charsToCopy)); fixed (char* target = _string) { @@ -352,7 +346,7 @@ internal static object ConvertFromDotNetToCim(object dotNetObject) /// The only kind of exception this method can throw. internal static object ConvertFromCimToDotNet(object cimObject, Type expectedDotNetType) { - if (expectedDotNetType == null) { throw new ArgumentNullException(nameof(expectedDotNetType)); } + ArgumentNullException.ThrowIfNull(expectedDotNetType); if (cimObject == null) { @@ -431,7 +425,9 @@ internal static object ConvertFromCimToDotNet(object cimObject, Type expectedDot var cimIntrinsicValue = (byte[])LanguagePrimitives.ConvertTo(cimObject, typeof(byte[]), CultureInfo.InvariantCulture); return exceptionSafeReturn(delegate { + #pragma warning disable SYSLIB0057 return new X509Certificate2(cimIntrinsicValue); + #pragma warning restore SYSLIB0057 }); } diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimOperationOptionsHelper.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimOperationOptionsHelper.cs index 1445ed17a14..34c708b5f4c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimOperationOptionsHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimOperationOptionsHelper.cs @@ -14,7 +14,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim { - internal class CimCustomOptionsDictionary + internal sealed class CimCustomOptionsDictionary { private readonly IDictionary _dict; private readonly object _dictModificationLock = new(); 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 a6f5b9616b9..a065f79204f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimQuery.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimQuery.cs @@ -177,7 +177,7 @@ private static string GetMatchCondition(string propertyName, IEnumerable propert .Select(propertyValue => wildcardsEnabled ? GetMatchConditionForLikeOperator(propertyName, propertyValue) : GetMatchConditionForEqualityOperator(propertyName, propertyValue)) - .Where(individualCondition => !string.IsNullOrWhiteSpace(individualCondition)) + .Where(static individualCondition => !string.IsNullOrWhiteSpace(individualCondition)) .ToList(); if (individualConditions.Count == 0) { @@ -196,7 +196,7 @@ private static string GetMatchCondition(string propertyName, IEnumerable propert /// Property name to query on. /// Property values to accept in the query. /// - /// if should be treated as a containing a wildcard pattern; + /// if should be treated as a containing a wildcard pattern; /// otherwise. /// /// @@ -219,7 +219,7 @@ public override void FilterByProperty(string propertyName, IEnumerable allowedPr /// Property name to query on. /// Property values to reject in the query. /// - /// if should be treated as a containing a wildcard pattern; + /// if should be treated as a containing a wildcard pattern; /// otherwise. /// /// @@ -314,15 +314,8 @@ public override void FilterByAssociatedInstance(object associatedInstance, strin /// public override void AddQueryOption(string optionName, object optionValue) { - if (string.IsNullOrEmpty(optionName)) - { - throw new ArgumentNullException(nameof(optionName)); - } - - if (optionValue == null) - { - throw new ArgumentNullException(nameof(optionValue)); - } + ArgumentException.ThrowIfNullOrEmpty(optionName); + 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 7052b16c768..c812778456a 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimWrapper.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimWrapper.cs @@ -107,15 +107,12 @@ internal CimCmdletDefinitionContext CmdletDefinitionContext { get { - if (_cmdletDefinitionContext == null) - { - _cmdletDefinitionContext = new CimCmdletDefinitionContext( - this.ClassName, - this.ClassVersion, - this.ModuleVersion, - this.Cmdlet.CommandInfo.CommandMetadata.SupportsShouldProcess, - this.PrivateData); - } + _cmdletDefinitionContext ??= new CimCmdletDefinitionContext( + this.ClassName, + this.ClassVersion, + this.ModuleVersion, + this.Cmdlet.CommandInfo.CommandMetadata.SupportsShouldProcess, + this.PrivateData); return _cmdletDefinitionContext; } 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 39d1a5bc441..e8e4d46e075 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/clientSideQuery.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/clientSideQuery.cs @@ -36,8 +36,7 @@ public NotFoundError(string propertyName, object propertyValue, bool wildcardsEn if (wildcardsEnabled) { - var propertyValueAsString = propertyValue as string; - if ((propertyValueAsString != null) && (WildcardPattern.ContainsWildcardCharacters(propertyValueAsString))) + if ((propertyValue is string propertyValueAsString) && (WildcardPattern.ContainsWildcardCharacters(propertyValueAsString))) { this.ErrorMessageGenerator = (queryDescription, className) => GetErrorMessageForNotFound_ForWildcard(this.PropertyName, this.PropertyValue, className); @@ -181,7 +180,7 @@ protected override bool IsMatchCore(CimInstance cimInstance) } } - private class CimInstanceRegularFilter : CimInstancePropertyBasedFilter + private sealed class CimInstanceRegularFilter : CimInstancePropertyBasedFilter { public CimInstanceRegularFilter(string propertyName, IEnumerable allowedPropertyValues, bool wildcardsEnabled, BehaviorOnNoMatch behaviorOnNoMatch) { @@ -202,7 +201,7 @@ public CimInstanceRegularFilter(string propertyName, IEnumerable allowedProperty if (valueBehaviors.Count == 1) { - this.BehaviorOnNoMatch = valueBehaviors.Single(); + this.BehaviorOnNoMatch = valueBehaviors.First(); } else { @@ -223,7 +222,7 @@ public override bool ShouldReportErrorOnNoMatches_IfMultipleFilters() case BehaviorOnNoMatch.Default: default: return this.PropertyValueFilters - .Any(f => !f.HadMatch && f.BehaviorOnNoMatch == BehaviorOnNoMatch.ReportErrors); + .Any(static f => !f.HadMatch && f.BehaviorOnNoMatch == BehaviorOnNoMatch.ReportErrors); } } @@ -247,7 +246,7 @@ public override IEnumerable GetNotFoundErrors_IfThisIsTheOnlyFilt } } - private class CimInstanceExcludeFilter : CimInstancePropertyBasedFilter + private sealed class CimInstanceExcludeFilter : CimInstancePropertyBasedFilter { public CimInstanceExcludeFilter(string propertyName, IEnumerable excludedPropertyValues, bool wildcardsEnabled, BehaviorOnNoMatch behaviorOnNoMatch) { @@ -272,7 +271,7 @@ public CimInstanceExcludeFilter(string propertyName, IEnumerable excludedPropert } } - private class CimInstanceMinFilter : CimInstancePropertyBasedFilter + private sealed class CimInstanceMinFilter : CimInstancePropertyBasedFilter { public CimInstanceMinFilter(string propertyName, object minPropertyValue, BehaviorOnNoMatch behaviorOnNoMatch) { @@ -293,7 +292,7 @@ public CimInstanceMinFilter(string propertyName, object minPropertyValue, Behavi } } - private class CimInstanceMaxFilter : CimInstancePropertyBasedFilter + private sealed class CimInstanceMaxFilter : CimInstancePropertyBasedFilter { public CimInstanceMaxFilter(string propertyName, object minPropertyValue, BehaviorOnNoMatch behaviorOnNoMatch) { @@ -314,7 +313,7 @@ public CimInstanceMaxFilter(string propertyName, object minPropertyValue, Behavi } } - private class CimInstanceAssociationFilter : CimInstanceFilterBase + private sealed class CimInstanceAssociationFilter : CimInstanceFilterBase { public CimInstanceAssociationFilter(BehaviorOnNoMatch behaviorOnNoMatch) { @@ -466,8 +465,7 @@ protected override BehaviorOnNoMatch GetDefaultBehaviorWhenNoMatchesFound(object } else { - string expectedPropertyValueAsString = cimTypedExpectedPropertyValue as string; - if (expectedPropertyValueAsString != null && WildcardPattern.ContainsWildcardCharacters(expectedPropertyValueAsString)) + if (cimTypedExpectedPropertyValue is string expectedPropertyValueAsString && WildcardPattern.ContainsWildcardCharacters(expectedPropertyValueAsString)) { return BehaviorOnNoMatch.SilentlyContinue; } @@ -504,8 +502,7 @@ private static bool NonWildcardEqual(string propertyName, object actualPropertyV actualPropertyValue = actualPropertyValue.ToString(); } - var expectedPropertyValueAsString = expectedPropertyValue as string; - if (expectedPropertyValueAsString != null) + if (expectedPropertyValue is string expectedPropertyValueAsString) { var actualPropertyValueAsString = (string)actualPropertyValue; return actualPropertyValueAsString.Equals(expectedPropertyValueAsString, StringComparison.OrdinalIgnoreCase); @@ -656,7 +653,7 @@ internal IEnumerable GenerateNotFoundErrors() return Enumerable.Empty(); } - if (_filters.All(f => !f.ShouldReportErrorOnNoMatches_IfMultipleFilters())) + if (_filters.All(static f => !f.ShouldReportErrorOnNoMatches_IfMultipleFilters())) { return Enumerable.Empty(); } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs index 1b10bbfe7ab..035d5b17bbe 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs @@ -79,8 +79,7 @@ internal static string WqlQueryAll(string from) /// internal static T GetFirst(CimSession session, string nameSpace, string wmiClassName) where T : class, new() { - if (string.IsNullOrEmpty(wmiClassName)) - throw new ArgumentException("String argument may not be null or empty", nameof(wmiClassName)); + ArgumentException.ThrowIfNullOrEmpty(wmiClassName); try { @@ -132,9 +131,8 @@ internal static string WqlQueryAll(string from) /// internal static T[] GetAll(CimSession session, string nameSpace, string wmiClassName) where T : class, new() { - if (string.IsNullOrEmpty(wmiClassName)) - throw new ArgumentException("String argument may not be null or empty", nameof(wmiClassName)); - + ArgumentException.ThrowIfNullOrEmpty(wmiClassName); + var rv = new List(); try diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearRecycleBinCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearRecycleBinCommand.cs index 78be89b90da..bc4e44220dd 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearRecycleBinCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearRecycleBinCommand.cs @@ -41,7 +41,7 @@ public string[] DriveLetter /// /// Property that sets force parameter. This will allow to clear the recyclebin. /// - [Parameter()] + [Parameter] public SwitchParameter Force { get @@ -237,7 +237,7 @@ private void EmptyRecycleBin(string drivePath) } } - internal static class NativeMethod + internal static partial class NativeMethod { // Internal code to SHEmptyRecycleBin internal enum RecycleFlags : uint @@ -247,8 +247,8 @@ internal enum RecycleFlags : uint SHERB_NOSOUND = 0x00000004 } - [DllImport("Shell32.dll", CharSet = CharSet.Unicode)] - internal static extern uint SHEmptyRecycleBin(IntPtr hwnd, string pszRootPath, RecycleFlags dwFlags); + [LibraryImport("Shell32.dll", StringMarshalling = StringMarshalling.Utf16, EntryPoint = "SHEmptyRecycleBinW")] + internal static partial uint SHEmptyRecycleBin(IntPtr hwnd, string pszRootPath, RecycleFlags dwFlags); } } #endif diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Clipboard.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Clipboard.cs index 5ce64350fbd..77e1b497b95 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Clipboard.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Clipboard.cs @@ -8,7 +8,7 @@ namespace Microsoft.PowerShell.Commands.Internal { - internal static class Clipboard + internal static partial class Clipboard { private static bool? _clipboardSupported; @@ -19,7 +19,8 @@ internal static class Clipboard private static string StartProcess( string tool, string args, - string stdin = "") + string stdin = "", + bool readStdout = true) { ProcessStartInfo startInfo = new(); startInfo.UseShellExecute = false; @@ -28,7 +29,7 @@ private static string StartProcess( startInfo.RedirectStandardError = true; startInfo.FileName = tool; startInfo.Arguments = args; - string stdout; + string stdout = string.Empty; using (Process process = new()) { @@ -43,15 +44,15 @@ private static string StartProcess( return string.Empty; } - if (!string.IsNullOrEmpty(stdin)) + process.StandardInput.Write(stdin); + process.StandardInput.Close(); + + if (readStdout) { - process.StandardInput.Write(stdin); - process.StandardInput.Close(); + stdout = process.StandardOutput.ReadToEnd(); } - stdout = process.StandardOutput.ReadToEnd(); process.WaitForExit(250); - _clipboardSupported = process.ExitCode == 0; } @@ -93,11 +94,6 @@ public static string GetText() public static void SetText(string text) { - if (string.IsNullOrEmpty(text)) - { - return; - } - if (_clipboardSupported == false) { _internalClipboard = text; @@ -114,7 +110,14 @@ public static void SetText(string text) else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { tool = "xclip"; - args = "-selection clipboard -in"; + if (string.IsNullOrEmpty(text)) + { + args = "-selection clipboard /dev/null"; + } + else + { + args = "-selection clipboard -in"; + } } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { @@ -126,7 +129,7 @@ public static void SetText(string text) return; } - StartProcess(tool, args, text); + StartProcess(tool, args, text, readStdout: false); if (_clipboardSupported == false) { _internalClipboard = text; @@ -154,46 +157,46 @@ public static void SetRtf(string plainText, string rtfText) private const uint GMEM_ZEROINIT = 0x0040; private const uint GHND = GMEM_MOVEABLE | GMEM_ZEROINIT; - [DllImport("kernel32.dll")] - private static extern IntPtr GlobalAlloc(uint flags, UIntPtr dwBytes); + [LibraryImport("kernel32.dll")] + private static partial IntPtr GlobalAlloc(uint flags, UIntPtr dwBytes); - [DllImport("kernel32.dll")] - private static extern IntPtr GlobalFree(IntPtr hMem); + [LibraryImport("kernel32.dll")] + private static partial IntPtr GlobalFree(IntPtr hMem); - [DllImport("kernel32.dll")] - private static extern IntPtr GlobalLock(IntPtr hMem); + [LibraryImport("kernel32.dll")] + private static partial IntPtr GlobalLock(IntPtr hMem); - [DllImport("kernel32.dll")] + [LibraryImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool GlobalUnlock(IntPtr hMem); + private static partial bool GlobalUnlock(IntPtr hMem); - [DllImport("kernel32.dll", ExactSpelling = true, EntryPoint = "RtlMoveMemory", SetLastError = true)] - private static extern void CopyMemory(IntPtr dest, IntPtr src, uint count); + [LibraryImport("kernel32.dll", EntryPoint = "RtlMoveMemory")] + private static partial void CopyMemory(IntPtr dest, IntPtr src, uint count); - [DllImport("user32.dll", SetLastError = false)] + [LibraryImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool IsClipboardFormatAvailable(uint format); + private static partial bool IsClipboardFormatAvailable(uint format); - [DllImport("user32.dll", SetLastError = true)] + [LibraryImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool OpenClipboard(IntPtr hWndNewOwner); + private static partial bool OpenClipboard(IntPtr hWndNewOwner); - [DllImport("user32.dll", SetLastError = true)] + [LibraryImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool CloseClipboard(); + private static partial bool CloseClipboard(); - [DllImport("user32.dll", SetLastError = true)] + [LibraryImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool EmptyClipboard(); + private static partial bool EmptyClipboard(); - [DllImport("user32.dll", SetLastError = true)] - private static extern IntPtr GetClipboardData(uint format); + [LibraryImport("user32.dll")] + private static partial IntPtr GetClipboardData(uint format); - [DllImport("user32.dll")] - private static extern IntPtr SetClipboardData(uint format, IntPtr data); + [LibraryImport("user32.dll")] + private static partial IntPtr SetClipboardData(uint format, IntPtr data); - [DllImport("user32.dll", SetLastError = true)] - private static extern uint RegisterClipboardFormat(string lpszFormat); + [LibraryImport("user32.dll", StringMarshalling = StringMarshalling.Utf16)] + private static partial uint RegisterClipboardFormat(string lpszFormat); private const uint CF_TEXT = 1; private const uint CF_UNICODETEXT = 13; diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs index bdeef0dd7a0..71ef5df8f08 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; } /// /// Gets or sets additional childPaths to the command. @@ -64,7 +65,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) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs index 61b5fe4487d..3b791d37a1a 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs @@ -35,7 +35,6 @@ namespace Microsoft.PowerShell.Commands /// /// This exception is thrown when the timeout expires before a computer finishes restarting. /// - [Serializable] public sealed class RestartComputerTimeoutException : RuntimeException { /// @@ -87,50 +86,6 @@ public RestartComputerTimeoutException(string message) : base(message) { } /// An exception that led to this exception. /// public RestartComputerTimeoutException(string message, Exception innerException) : base(message, innerException) { } - - #region Serialization - /// - /// Serialization constructor for class RestartComputerTimeoutException. - /// - /// - /// serialization information - /// - /// - /// streaming context - /// - private RestartComputerTimeoutException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - ComputerName = info.GetString("ComputerName"); - Timeout = info.GetInt32("Timeout"); - } - - /// - /// Serializes the RestartComputerTimeoutException. - /// - /// - /// serialization information - /// - /// - /// streaming context - /// - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - info.AddValue("ComputerName", ComputerName); - info.AddValue("Timeout", Timeout); - } - #endregion Serialization } /// @@ -275,12 +230,12 @@ public WaitForServiceTypes For /// The specific time interval (in second) to wait between network pings or service queries. /// [Parameter(ParameterSetName = DefaultParameterSet)] - [ValidateRange(1, Int16.MaxValue)] - public Int16 Delay + [ValidateRange(1, short.MaxValue)] + public short Delay { get { - return (Int16)_delay; + return (short)_delay; } set @@ -306,7 +261,7 @@ public Int16 Delay ComputerName = $computerName ScriptBlock = { $true } - SessionOption = NewPSSessionOption -NoMachineProfile + SessionOption = New-PSSessionOption -NoMachineProfile ErrorAction = 'SilentlyContinue' } @@ -393,17 +348,10 @@ public void Dispose(bool disposing) { if (disposing) { - if (_timer != null) - { - _timer.Dispose(); - } - + _timer?.Dispose(); _waitHandler.Dispose(); _cancel.Dispose(); - if (_powershell != null) - { - _powershell.Dispose(); - } + _powershell?.Dispose(); } } @@ -522,7 +470,7 @@ private void OnTimedEvent(object s) } } - private class ComputerInfo + private sealed class ComputerInfo { internal string LastBootUpTime; internal bool RebootComplete; @@ -540,7 +488,10 @@ private List TestRestartStageUsingWsman(IEnumerable computerName { try { - if (token.IsCancellationRequested) { break; } + if (token.IsCancellationRequested) + { + break; + } using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, Credential, WsmanAuthentication, isLocalHost: false, this, token)) { @@ -682,7 +633,10 @@ internal static List TestWmiConnectionUsingWsman(List computerNa { try { - if (token.IsCancellationRequested) { break; } + if (token.IsCancellationRequested) + { + break; + } using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, credential, wsmanAuthentication, isLocalHost: false, cmdlet, token)) { @@ -798,7 +752,7 @@ protected override void BeginProcessing() if (Wait) { - _activityId = (new Random()).Next(); + _activityId = Random.Shared.Next(); if (_timeout == -1 || _timeout >= int.MaxValue / 1000) { _timeoutInMilliseconds = int.MaxValue; @@ -837,7 +791,10 @@ protected override void ProcessRecord() ValidateComputerNames(); object[] flags = new object[] { 2, 0 }; - if (Force) flags[0] = forcedReboot; + if (Force) + { + flags[0] = forcedReboot; + } if (ParameterSetName.Equals(DefaultParameterSet, StringComparison.OrdinalIgnoreCase)) { @@ -912,14 +869,18 @@ protected override void ProcessRecord() while (true) { - int loopCount = actualDelay * 4; // (delay * 1000)/250ms + // (delay * 1000)/250ms + int loopCount = actualDelay * 4; while (loopCount > 0) { WriteProgress(_indicator[(indicatorIndex++) % 4] + _activity, _status, _percent, ProgressRecordType.Processing); loopCount--; _waitHandler.Wait(250); - if (_exit) { break; } + if (_exit) + { + break; + } } if (first) @@ -939,7 +900,10 @@ protected override void ProcessRecord() // Test restart stage. // We check if the target machine has already rebooted by querying the LastBootUpTime from the Win32_OperatingSystem object. // So after this step, we are sure that both the Network and the WMI or WinRM service have already come up. - if (_exit) { break; } + if (_exit) + { + break; + } if (restartStageTestList.Count > 0) { @@ -955,7 +919,10 @@ protected override void ProcessRecord() } // Test WMI service - if (_exit) { break; } + if (_exit) + { + break; + } if (wmiTestList.Count > 0) { @@ -973,10 +940,16 @@ protected override void ProcessRecord() } } - if (isForWmi) { break; } + if (isForWmi) + { + break; + } // Test WinRM service - if (_exit) { break; } + if (_exit) + { + break; + } if (winrmTestList.Count > 0) { @@ -1001,16 +974,25 @@ protected override void ProcessRecord() loopCount--; _waitHandler.Wait(250); - if (_exit) { break; } + if (_exit) + { + break; + } } } } } - if (isForWinRm) { break; } + if (isForWinRm) + { + break; + } // Test PowerShell - if (_exit) { break; } + if (_exit) + { + break; + } if (psTestList.Count > 0) { @@ -1026,7 +1008,10 @@ protected override void ProcessRecord() } while (false); // if time is up or Ctrl+c is typed, break out - if (_exit) { break; } + if (_exit) + { + break; + } // Check if the restart completes switch (_waitFor) @@ -1070,18 +1055,38 @@ protected override void ProcessRecord() // The timeout expires. Write out timeout error messages for the computers that haven't finished restarting do { - if (restartStageTestList.Count > 0) { WriteOutTimeoutError(restartStageTestList); } + if (restartStageTestList.Count > 0) + { + WriteOutTimeoutError(restartStageTestList); + } + + if (wmiTestList.Count > 0) + { + WriteOutTimeoutError(wmiTestList); + } - if (wmiTestList.Count > 0) { WriteOutTimeoutError(wmiTestList); } // Wait for WMI. All computers that finished restarting are put in "winrmTestList" - if (isForWmi) { break; } + if (isForWmi) + { + break; + } // Wait for WinRM. All computers that finished restarting are put in "psTestList" - if (winrmTestList.Count > 0) { WriteOutTimeoutError(winrmTestList); } + if (winrmTestList.Count > 0) + { + WriteOutTimeoutError(winrmTestList); + } - if (isForWinRm) { break; } + if (isForWinRm) + { + break; + } + + if (psTestList.Count > 0) + { + WriteOutTimeoutError(psTestList); + } - if (psTestList.Count > 0) { WriteOutTimeoutError(psTestList); } // Wait for PowerShell. All computers that finished restarting are put in "allDoneList" } while (false); } @@ -1098,10 +1103,7 @@ protected override void StopProcessing() _cancel.Cancel(); _waitHandler.Set(); - if (_timer != null) - { - _timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); - } + _timer?.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); if (_powershell != null) { @@ -1231,7 +1233,10 @@ private void ProcessWSManProtocol(object[] flags) string strLocal = string.Empty; bool isLocalHost = false; - if (_cancel.Token.IsCancellationRequested) { break; } + if (_cancel.Token.IsCancellationRequested) + { + break; + } if ((computer.Equals("localhost", StringComparison.OrdinalIgnoreCase)) || (computer.Equals(".", StringComparison.OrdinalIgnoreCase))) { @@ -1278,6 +1283,7 @@ private void ProcessWSManProtocol(object[] flags) /// [Cmdlet(VerbsCommon.Rename, "Computer", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097054", RemotingCapability = RemotingCapability.SupportedByCommand)] + [OutputType(typeof(RenameComputerChangeInfo))] public class RenameComputerCommand : PSCmdlet { #region Private Members @@ -1585,7 +1591,10 @@ private void DoRenameComputerWsman(string computer, string computerName, string protected override void ProcessRecord() { string targetComputer = ValidateComputerName(); - if (targetComputer == null) return; + if (targetComputer == null) + { + return; + } bool isLocalhost = targetComputer.Equals("localhost", StringComparison.OrdinalIgnoreCase); if (isLocalhost) @@ -1605,7 +1614,10 @@ protected override void ProcessRecord() /// protected override void EndProcessing() { - if (!_containsLocalHost) return; + if (!_containsLocalHost) + { + return; + } DoRenameComputerAction("localhost", _newNameForLocalHost, true); } @@ -2042,25 +2054,6 @@ internal static bool IsComputerNameValid(string computerName) return !allDigits; } - /// - /// System Restore APIs are not supported on the ARM platform. Skip the system restore operation is necessary. - /// - /// - /// - 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; - } - /// /// Invokes the Win32Shutdown command on provided target computer using WSMan /// over a CIMSession. The flags parameter determines the type of shutdown operation @@ -2230,8 +2223,7 @@ internal static string ValidateComputerName( bool isIPAddress = false; try { - IPAddress unused; - isIPAddress = IPAddress.TryParse(nameToCheck, out unused); + isIPAddress = IPAddress.TryParse(nameToCheck, out _); } catch (Exception) { diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ComputerUnix.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ComputerUnix.cs index fb01823790f..eb286f7c190 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ComputerUnix.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ComputerUnix.cs @@ -5,10 +5,13 @@ using System; using System.Diagnostics; +using System.IO; using System.Management.Automation; using System.Management.Automation.Internal; using System.Runtime.InteropServices; +#nullable enable + namespace Microsoft.PowerShell.Commands { #region Restart-Computer @@ -36,13 +39,13 @@ protected override void BeginProcessing() { string errMsg = StringUtil.Format("Command returned 0x{0:X}", retVal); ErrorRecord error = new ErrorRecord( - new InvalidOperationException(errMsg), "Command Failed", ErrorCategory.OperationStopped, "localhost"); + new InvalidOperationException(errMsg), "CommandFailed", ErrorCategory.OperationStopped, "localhost"); WriteError(error); } return; } - RunCommand("/sbin/shutdown", "-r now"); + RunShutdown("-r now"); } #endregion "Overrides" } @@ -67,7 +70,7 @@ public sealed class StopComputerCommand : CommandLineCmdletBase protected override void BeginProcessing() { var args = "-P now"; - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + if (Platform.IsMacOS) { args = "now"; } @@ -78,13 +81,13 @@ protected override void BeginProcessing() { string errMsg = StringUtil.Format("Command returned 0x{0:X}", retVal); ErrorRecord error = new ErrorRecord( - new InvalidOperationException(errMsg), "Command Failed", ErrorCategory.OperationStopped, "localhost"); + new InvalidOperationException(errMsg), "CommandFailed", ErrorCategory.OperationStopped, "localhost"); WriteError(error); } return; } - RunCommand("/sbin/shutdown", args); + RunShutdown(args); } #endregion "Overrides" } @@ -95,7 +98,7 @@ protected override void BeginProcessing() public class CommandLineCmdletBase : PSCmdlet, IDisposable { #region Private Members - private Process _process = null; + private Process? _process = null; #endregion #region "IDisposable Members" @@ -150,22 +153,52 @@ protected override void StopProcessing() #region "Internals" + private static string? shutdownPath; + /// - /// Run a command. + /// Run shutdown command. /// - protected void RunCommand(String command, String args) { + protected void RunShutdown(String args) + { + if (shutdownPath is null) + { + CommandInfo cmdinfo = CommandDiscovery.LookupCommandInfo( + "shutdown", CommandTypes.Application, + SearchResolutionOptions.None, CommandOrigin.Internal, this.Context); + + if (cmdinfo is not null) + { + shutdownPath = cmdinfo.Definition; + } + else + { + ErrorRecord error = new ErrorRecord( + new InvalidOperationException(ComputerResources.ShutdownCommandNotFound), "CommandNotFound", ErrorCategory.ObjectNotFound, targetObject: null); + ThrowTerminatingError(error); + } + } + _process = new Process() { StartInfo = new ProcessStartInfo { - FileName = "/sbin/shutdown", + FileName = shutdownPath, Arguments = string.Empty, RedirectStandardOutput = false, + RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, } }; _process.Start(); + _process.WaitForExit(); + if (_process.ExitCode != 0) + { + string stderr = _process.StandardError.ReadToEnd(); + ErrorRecord error = new ErrorRecord( + new InvalidOperationException(stderr), "CommandFailed", ErrorCategory.OperationStopped, null); + ThrowTerminatingError(error); + } } #endregion } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ContentCommandBase.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ContentCommandBase.cs index 0c990292b58..87c7731ed70 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ContentCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ContentCommandBase.cs @@ -377,10 +377,7 @@ internal void CloseContent(List contentHolders, bool disposing) { try { - if (holder.Writer != null) - { - holder.Writer.Close(); - } + holder.Writer?.Close(); } catch (Exception e) // Catch-all OK. 3rd party callout { @@ -414,10 +411,7 @@ internal void CloseContent(List contentHolders, bool disposing) try { - if (holder.Reader != null) - { - holder.Reader.Close(); - } + holder.Reader?.Close(); } catch (Exception e) // Catch-all OK. 3rd party callout { diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ConvertPathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ConvertPathCommand.cs index c32d0f2aa67..33796b23378 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ConvertPathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ConvertPathCommand.cs @@ -55,6 +55,16 @@ public string[] LiteralPath } } + /// + /// Gets or sets the force property. + /// + [Parameter] + public override SwitchParameter Force + { + get => base.Force; + set => base.Force = value; + } + #endregion Parameters #region parameter data diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Eventlog.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Eventlog.cs index 45c484744fa..3731687599c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Eventlog.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Eventlog.cs @@ -48,8 +48,8 @@ public sealed class GetEventLogCommand : PSCmdlet /// /// Read eventlog entries from this computer. /// - [Parameter()] - [ValidateNotNullOrEmpty()] + [Parameter] + [ValidateNotNullOrEmpty] [Alias("Cn")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] ComputerName { get; set; } = Array.Empty(); @@ -123,7 +123,7 @@ public string[] UserName /// gets or sets an array of instanceIds. /// [Parameter(Position = 1, ParameterSetName = "LogName")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [ValidateRangeAttribute((long)0, long.MaxValue)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public long[] InstanceId @@ -144,7 +144,7 @@ public long[] InstanceId /// gets or sets an array of indexes. /// [Parameter(ParameterSetName = "LogName")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [ValidateRangeAttribute((int)1, int.MaxValue)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public int[] Index @@ -165,7 +165,7 @@ public int[] Index /// gets or sets an array of EntryTypes. /// [Parameter(ParameterSetName = "LogName")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [ValidateSetAttribute(new string[] { "Error", "Information", "FailureAudit", "SuccessAudit", "Warning" })] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [Alias("ET")] @@ -186,7 +186,7 @@ public string[] EntryType /// Get or sets an array of Source. /// [Parameter(ParameterSetName = "LogName")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [Alias("ABO")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] Source @@ -207,7 +207,7 @@ public string[] Source /// Get or Set Message string to searched in EventLog. /// [Parameter(ParameterSetName = "LogName")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [Alias("MSG")] public string Message { @@ -360,7 +360,11 @@ private void OutputEvents(string logName) } catch (InvalidOperationException e) { - if (processing) throw; + if (processing) + { + throw; + } + ThrowTerminatingError(new ErrorRecord( e, // default exception text is OK "EventLogNotFound", @@ -490,7 +494,10 @@ private bool FiltersMatch(EventLogEntry entry) } } - if (!entrymatch) return entrymatch; + if (!entrymatch) + { + return entrymatch; + } } if (_sources != null) @@ -511,7 +518,10 @@ private bool FiltersMatch(EventLogEntry entry) } } - if (!sourcematch) return sourcematch; + if (!sourcematch) + { + return sourcematch; + } } if (_message != null) @@ -545,7 +555,10 @@ private bool FiltersMatch(EventLogEntry entry) } } - if (!usernamematch) return usernamematch; + if (!usernamematch) + { + return usernamematch; + } } if (_isDateSpecified) @@ -582,7 +595,10 @@ private bool FiltersMatch(EventLogEntry entry) } } - if (!datematch) return datematch; + if (!datematch) + { + return datematch; + } } return true; @@ -1344,7 +1360,7 @@ public class RemoveEventLogCommand : PSCmdlet /// /// The following is the definition of the input parameter "RemoveSource". - /// Specifies either to remove the event log and and associated source or + /// Specifies either to remove the event log and associated source or /// source. alone. /// When this parameter is not specified, the cmdlet uses Delete Method which /// clears the eventlog and also the source associated with it. diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetChildrenCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetChildrenCommand.cs index d99ece1fb81..c049370e4b4 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetChildrenCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetChildrenCommand.cs @@ -122,7 +122,7 @@ public override string[] Exclude /// Gets or sets the recurse switch. /// [Parameter] - [Alias("s")] + [Alias("s", "r")] public SwitchParameter Recurse { get diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs index 4c8a95dddeb..3de7c723745 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs @@ -31,7 +31,7 @@ namespace Microsoft.PowerShell.Commands public class GetComputerInfoCommand : PSCmdlet { #region Inner Types - private class OSInfoGroup + private sealed class OSInfoGroup { public WmiOperatingSystem os; public HotFix[] hotFixes; @@ -41,7 +41,7 @@ private class OSInfoGroup public RegWinNtCurrentVersion regCurVer; } - private class SystemInfoGroup + private sealed class SystemInfoGroup { public WmiBaseBoard baseboard; public WmiBios bios; @@ -50,7 +50,7 @@ private class SystemInfoGroup public NetworkAdapter[] networkAdapters; } - private class HyperVInfo + private sealed class HyperVInfo { public bool? Present; public bool? VMMonitorModeExtensions; @@ -59,13 +59,13 @@ private class HyperVInfo public bool? DataExecutionPreventionAvailable; } - private class DeviceGuardInfo + private sealed class DeviceGuardInfo { public DeviceGuardSmartStatus status; public DeviceGuard deviceGuard; } - private class MiscInfoGroup + private sealed class MiscInfoGroup { public ulong? physicallyInstalledMemory; public string timeZone; @@ -248,7 +248,7 @@ private static string GetHalVersion(CimSession session, string systemDirectory) try { var halPath = CIMHelper.EscapePath(System.IO.Path.Combine(systemDirectory, "hal.dll")); - var query = string.Format("SELECT * FROM CIM_DataFile Where Name='{0}'", halPath); + var query = string.Create(CultureInfo.InvariantCulture, $"SELECT * FROM CIM_DataFile Where Name='{halPath}'"); var instance = session.QueryFirstInstance(query); if (instance != null) @@ -1125,16 +1125,13 @@ internal static string GetLocaleName(string locale) // base-indication prefix. For example, the string "0409" will be // parsed into the base-10 integer value 1033, while the string "0x0409" // will fail to parse due to the "0x" base-indication prefix. - if (UInt32.TryParse(locale, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint localeNum)) + if (uint.TryParse(locale, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint localeNum)) { culture = CultureInfo.GetCultureInfo((int)localeNum); } - if (culture == null) - { - // If TryParse failed we'll try using the original string as culture name - culture = CultureInfo.GetCultureInfo(locale); - } + // If TryParse failed we'll try using the original string as culture name + culture ??= CultureInfo.GetCultureInfo(locale); } catch (Exception) { @@ -1193,7 +1190,7 @@ internal static class EnumConverter where T : struct, IConvertible /// /// /// A Nullable enum object. If the value - /// is convertable to a valid enum value, the returned object's + /// is convertible to a valid enum value, the returned object's /// value will contain the converted value, otherwise the returned /// object will be null. /// @@ -1231,11 +1228,11 @@ internal static class EnumConverter where T : struct, IConvertible internal static class RegistryInfo { - public static Dictionary GetServerLevels() + public static Dictionary GetServerLevels() { const string keyPath = @"Software\Microsoft\Windows NT\CurrentVersion\Server\ServerLevels"; - var rv = new Dictionary(); + var rv = new Dictionary(); using (var key = Registry.LocalMachine.OpenSubKey(keyPath)) { @@ -1373,7 +1370,7 @@ internal class WmiBaseBoard [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] internal class WmiBios : WmiClassBase { - public UInt16[] BiosCharacteristics; + public ushort[] BiosCharacteristics; public string[] BIOSVersion; public string BuildNumber; public string Caption; @@ -1416,11 +1413,11 @@ internal class WmiComputerSystem public ushort? BootOptionOnWatchDog; public bool? BootROMSupported; public string BootupState; - public UInt16[] BootStatus; + public ushort[] BootStatus; public string Caption; public ushort? ChassisBootupState; public string ChassisSKUNumber; - public Int16? CurrentTimeZone; + public short? CurrentTimeZone; public bool? DaylightInEffect; public string Description; public string DNSHostName; @@ -1442,10 +1439,10 @@ internal class WmiComputerSystem public uint? NumberOfProcessors; public string[] OEMStringArray; public bool? PartOfDomain; - public Int64? PauseAfterReset; + public long? PauseAfterReset; public ushort? PCSystemType; public ushort? PCSystemTypeEx; - public UInt16[] PowerManagementCapabilities; + public ushort[] PowerManagementCapabilities; public bool? PowerManagementSupported; public ushort? PowerOnPasswordStatus; public ushort? PowerState; @@ -1453,8 +1450,8 @@ internal class WmiComputerSystem public string PrimaryOwnerContact; public string PrimaryOwnerName; public ushort? ResetCapability; - public Int16? ResetCount; - public Int16? ResetLimit; + public short? ResetCount; + public short? ResetLimit; public string[] Roles; public string Status; public string[] SupportContactDescription; @@ -1491,12 +1488,12 @@ public PowerManagementCapabilities[] GetPowerManagementCapabilities() [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] internal class WmiDeviceGuard { - public UInt32[] AvailableSecurityProperties; + public uint[] AvailableSecurityProperties; public uint? CodeIntegrityPolicyEnforcementStatus; public uint? UsermodeCodeIntegrityPolicyEnforcementStatus; - public UInt32[] RequiredSecurityProperties; - public UInt32[] SecurityServicesConfigured; - public UInt32[] SecurityServicesRunning; + public uint[] RequiredSecurityProperties; + public uint[] SecurityServicesConfigured; + public uint[] SecurityServicesRunning; public uint? VirtualizationBasedSecurityStatus; public DeviceGuard AsOutputType @@ -1582,7 +1579,7 @@ internal class WmiKeyboard public ushort? NumberOfFunctionKeys; public ushort? Password; public string PNPDeviceID; - public UInt16[] PowerManagementCapabilities; + public ushort[] PowerManagementCapabilities; public bool? PowerManagementSupported; public string Status; public ushort? StatusInfo; @@ -1613,7 +1610,7 @@ internal class WmiMsftNetAdapter public string ErrorDescription; public uint? LastErrorCode; public string PNPDeviceID; - public UInt16[] PowerManagementCapabilities; + public ushort[] PowerManagementCapabilities; public bool? PowerManagementSupported; public ushort? StatusInfo; public string SystemCreationClassName; @@ -1680,8 +1677,8 @@ internal class WmiMsftNetAdapter public string PnPDeviceID; public string DriverProvider; public string ComponentID; - public UInt32[] LowerLayerInterfaceIndices; - public UInt32[] HigherLayerInterfaceIndices; + public uint[] LowerLayerInterfaceIndices; + public uint[] HigherLayerInterfaceIndices; public bool? AdminLocked; } @@ -1717,7 +1714,7 @@ internal class WmiNetworkAdapter public string PermanentAddress; public bool? PhysicalAdapter; public string PNPDeviceID; - public UInt16[] PowerManagementCapabilities; + public ushort[] PowerManagementCapabilities; public bool? PowerManagementSupported; public string ProductName; public string ServiceName; @@ -1753,7 +1750,7 @@ internal class WmiNetworkAdapterConfiguration public bool? DomainDNSRegistrationEnabled; public uint? ForwardBufferMemory; public bool? FullDNSRegistrationEnabled; - public UInt16[] GatewayCostMetric; + public ushort[] GatewayCostMetric; public byte? IGMPLevel; public uint? Index; public uint? InterfaceIndex; @@ -1769,7 +1766,7 @@ internal class WmiNetworkAdapterConfiguration public bool? IPUseZeroBroadcast; public string IPXAddress; public bool? IPXEnabled; - public UInt32[] IPXFrameType; + public uint[] IPXFrameType; public uint? IPXMediaType; public string[] IPXNetworkNumber; public string IPXVirtualNetNumber; @@ -1807,7 +1804,7 @@ internal class WmiOperatingSystem : WmiClassBase public string CountryCode; public string CSDVersion; public string CSName; - public Int16? CurrentTimeZone; + public short? CurrentTimeZone; public bool? DataExecutionPrevention_Available; public bool? DataExecutionPrevention_32BitApplications; public bool? DataExecutionPrevention_Drivers; @@ -1893,8 +1890,8 @@ private static OSProductSuite[] MakeProductSuites(uint? suiteMask) var mask = suiteMask.Value; var list = new List(); - foreach (OSProductSuite suite in Enum.GetValues(typeof(OSProductSuite))) - if ((mask & (UInt32)suite) != 0) + foreach (OSProductSuite suite in Enum.GetValues()) + if ((mask & (uint)suite) != 0) list.Add(suite); return list.ToArray(); @@ -1954,7 +1951,7 @@ internal class WmiProcessor public string OtherFamilyDescription; public string PartNumber; public string PNPDeviceID; - public UInt16[] PowerManagementCapabilities; + public ushort[] PowerManagementCapabilities; public bool? PowerManagementSupported; public string ProcessorId; public ushort? ProcessorType; @@ -2282,7 +2279,7 @@ public class ComputerInfo /// the System Management BIOS Reference Specification. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public UInt16[] BiosCharacteristics { get; internal set; } + public ushort[] BiosCharacteristics { get; internal set; } /// /// Array of the complete system BIOS information. In many computers @@ -2320,12 +2317,12 @@ public class ComputerInfo /// /// Major version of the embedded controller firmware. /// - public Int16? BiosEmbeddedControllerMajorVersion { get; internal set; } + public short? BiosEmbeddedControllerMajorVersion { get; internal set; } /// /// Minor version of the embedded controller firmware. /// - public Int16? BiosEmbeddedControllerMinorVersion { get; internal set; } + public short? BiosEmbeddedControllerMinorVersion { get; internal set; } /// /// Firmware type of the local computer. @@ -2496,7 +2493,7 @@ public class ComputerInfo /// Status and Additional Data fields that identify the boot status. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public UInt16[] CsBootStatus { get; internal set; } + public ushort[] CsBootStatus { get; internal set; } /// /// System is started. Fail-safe boot bypasses the user startup files—also called SafeBoot. @@ -2524,7 +2521,7 @@ public class ComputerInfo /// Amount of time the unitary computer system is offset from Coordinated /// Universal Time (UTC). /// - public Int16? CsCurrentTimeZone { get; internal set; } + public short? CsCurrentTimeZone { get; internal set; } /// /// If True, the daylight savings mode is ON. @@ -2675,7 +2672,7 @@ public class ComputerInfo /// and automatic system reset. A value of –1 (minus one) indicates that /// the pause value is unknown. /// - public Int64? CsPauseAfterReset { get; internal set; } + public long? CsPauseAfterReset { get; internal set; } /// /// Type of the computer in use, such as laptop, desktop, or tablet. @@ -2743,13 +2740,13 @@ public class ComputerInfo /// Number of automatic resets since the last reset. /// A value of –1 (minus one) indicates that the count is unknown. /// - public Int16? CsResetCount { get; internal set; } + public short? CsResetCount { get; internal set; } /// /// Number of consecutive times a system reset is attempted. /// A value of –1 (minus one) indicates that the limit is unknown. /// - public Int16? CsResetLimit { get; internal set; } + public short? CsResetLimit { get; internal set; } /// /// Array that specifies the roles of a system in the information @@ -2909,7 +2906,7 @@ public class ComputerInfo /// Number, in minutes, an operating system is offset from Greenwich /// mean time (GMT). The number is positive, negative, or zero. /// - public Int16? OsCurrentTimeZone { get; internal set; } + public short? OsCurrentTimeZone { get; internal set; } /// /// Language identifier used by the operating system. @@ -3058,7 +3055,7 @@ public class ComputerInfo public ulong? OsFreeSpaceInPagingFiles { get; internal set; } /// - /// Array of fiel paths to the operating system's paging files. + /// Array of file paths to the operating system's paging files. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] OsPagingFiles { get; internal set; } @@ -3328,9 +3325,9 @@ public enum AdminPasswordStatus [SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Justification = "The underlying MOF definition does not contain a zero value. The converter method will handle it appropriately.")] public enum BootOptionAction { - // - // This value is reserved - // + // + // This value is reserved + // // Reserved = 0, /// @@ -3687,7 +3684,22 @@ public enum DeviceGuardHardwareSecure /// /// Secure Memory Overwrite. /// - SecureMemoryOverwrite = 4 + SecureMemoryOverwrite = 4, + + /// + /// UEFI Code Readonly. + /// + UEFICodeReadonly = 5, + + /// + /// SMM Security Mitigations 1.0. + /// + SMMSecurityMitigations = 6, + + /// + /// Mode Based Execution Control. + /// + ModeBasedExecutionControl = 7 } /// @@ -5086,7 +5098,7 @@ public enum SoftwareElementState #endregion Output components #region Native - internal static class Native + internal static partial class Native { private static class PInvokeDllNames { @@ -5099,24 +5111,24 @@ private static class PInvokeDllNames public const uint POWER_PLATFORM_ROLE_V1 = 0x1; public const uint POWER_PLATFORM_ROLE_V2 = 0x2; - public const UInt32 S_OK = 0; + public const uint S_OK = 0; /// /// Import WINAPI function PowerDeterminePlatformRoleEx. /// /// The version of the POWER_PLATFORM_ROLE enumeration for the platform. /// POWER_PLATFORM_ROLE enumeration. - [DllImport(PInvokeDllNames.PowerDeterminePlatformRoleExDllName, EntryPoint = "PowerDeterminePlatformRoleEx", CharSet = CharSet.Ansi)] - public static extern uint PowerDeterminePlatformRoleEx(uint version); + [LibraryImport(PInvokeDllNames.PowerDeterminePlatformRoleExDllName, EntryPoint = "PowerDeterminePlatformRoleEx")] + public static partial uint PowerDeterminePlatformRoleEx(uint version); /// /// Retrieve the amount of RAM physically installed in the computer. /// /// /// - [DllImport(PInvokeDllNames.GetPhysicallyInstalledSystemMemoryDllName, SetLastError = true)] + [LibraryImport(PInvokeDllNames.GetPhysicallyInstalledSystemMemoryDllName)] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool GetPhysicallyInstalledSystemMemory(out ulong MemoryInKilobytes); + public static partial bool GetPhysicallyInstalledSystemMemory(out ulong MemoryInKilobytes); /// /// Retrieve the firmware type of the local computer. @@ -5126,9 +5138,9 @@ private static class PInvokeDllNames /// the resultant firmware type /// /// - [DllImport(PInvokeDllNames.GetFirmwareTypeDllName, SetLastError = true)] + [LibraryImport(PInvokeDllNames.GetFirmwareTypeDllName)] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool GetFirmwareType(out FirmwareType firmwareType); + public static partial bool GetFirmwareType(out FirmwareType firmwareType); /// /// Gets the data specified for the passed in property name from the @@ -5137,8 +5149,8 @@ private static class PInvokeDllNames /// Name of the licensing property to get. /// Out parameter for the value. /// An hresult indicating success or failure. - [DllImport("slc.dll", CharSet = CharSet.Unicode)] - internal static extern int SLGetWindowsInformationDWORD(string licenseProperty, out int propertyValue); + [LibraryImport("slc.dll", StringMarshalling = StringMarshalling.Utf16)] + internal static partial int SLGetWindowsInformationDWORD(string licenseProperty, out int propertyValue); } #endregion Native } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetContentCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetContentCommand.cs index f689695fc38..a9d8772a5fc 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetContentCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetContentCommand.cs @@ -31,48 +31,20 @@ public class GetContentCommand : ContentCommandBase public long ReadCount { get; set; } = 1; /// - /// The number of content items to retrieve. By default this - /// value is -1 which means read all the content. + /// The number of content items to retrieve. /// [Parameter(ValueFromPipelineByPropertyName = true)] + [ValidateRange(0, long.MaxValue)] [Alias("First", "Head")] - public long TotalCount - { - get - { - return _totalCount; - } - - set - { - _totalCount = value; - _totalCountSpecified = true; - } - } - - private bool _totalCountSpecified = false; + public long TotalCount { get; set; } = -1; /// /// The number of content items to retrieve from the back of the file. /// [Parameter(ValueFromPipelineByPropertyName = true)] + [ValidateRange(0, int.MaxValue)] [Alias("Last")] - public int Tail - { - get - { - return _backCount; - } - - set - { - _backCount = value; - _tailSpecified = true; - } - } - - private int _backCount = -1; - private bool _tailSpecified = false; + public int Tail { get; set; } = -1; /// /// A virtual method for retrieving the dynamic parameters for a cmdlet. Derived cmdlets @@ -98,15 +70,6 @@ internal override object GetDynamicParameters(CmdletProviderContext context) #endregion Parameters - #region parameter data - - /// - /// The number of content items to retrieve. - /// - private long _totalCount = -1; - - #endregion parameter data - #region Command code /// @@ -116,7 +79,7 @@ protected override void ProcessRecord() { // TotalCount and Tail should not be specified at the same time. // Throw out terminating error if this is the case. - if (_totalCountSpecified && _tailSpecified) + if (TotalCount != -1 && Tail != -1) { string errMsg = StringUtil.Format(SessionStateStrings.GetContent_TailAndHeadCannotCoexist, "TotalCount", "Tail"); ErrorRecord error = new(new InvalidOperationException(errMsg), "TailAndHeadCannotCoexist", ErrorCategory.InvalidOperation, null); @@ -141,11 +104,9 @@ protected override void ProcessRecord() { long countRead = 0; - Dbg.Diagnostics.Assert( - holder.Reader != null, - "All holders should have a reader assigned"); + Dbg.Diagnostics.Assert(holder.Reader != null, "All holders should have a reader assigned"); - if (_tailSpecified && holder.Reader is not FileSystemContentReaderWriter) + if (Tail != -1 && holder.Reader is not FileSystemContentReaderWriter) { string errMsg = SessionStateStrings.GetContent_TailNotSupported; ErrorRecord error = new(new InvalidOperationException(errMsg), "TailNotSupported", ErrorCategory.InvalidOperation, Tail); @@ -153,7 +114,7 @@ protected override void ProcessRecord() continue; } - // If Tail is negative, we are supposed to read all content out. This is same + // If Tail is -1, we are supposed to read all content out. This is same // as reading forwards. So we read forwards in this case. // If Tail is positive, we seek the right position. Or, if the seek failed // because of an unsupported encoding, we scan forward to get the tail content. @@ -197,72 +158,61 @@ protected override void ProcessRecord() } } - if (TotalCount != 0) + IList results = null; + + do { - IList results = null; + long countToRead = ReadCount; - do + // Make sure we only ask for the amount the user wanted + // I am using TotalCount - countToRead so that I don't + // have to worry about overflow + if (TotalCount > 0 && (countToRead == 0 || TotalCount - countToRead < countRead)) { - long countToRead = ReadCount; + countToRead = TotalCount - countRead; + } - // Make sure we only ask for the amount the user wanted - // I am using TotalCount - countToRead so that I don't - // have to worry about overflow + try + { + results = holder.Reader.Read(countToRead); + } + catch (Exception e) // Catch-all OK. 3rd party callout + { + ProviderInvocationException providerException = + new( + "ProviderContentReadError", + SessionStateStrings.ProviderContentReadError, + holder.PathInfo.Provider, + holder.PathInfo.Path, + e); - if ((TotalCount > 0) && (countToRead == 0 || (TotalCount - countToRead < countRead))) - { - countToRead = TotalCount - countRead; - } + // Log a provider health event + MshLog.LogProviderHealthEvent(this.Context, holder.PathInfo.Provider.Name, providerException, Severity.Warning); + WriteError(new ErrorRecord(providerException.ErrorRecord, providerException)); - try - { - results = holder.Reader.Read(countToRead); - } - catch (Exception e) // Catch-all OK. 3rd party callout + break; + } + + if (results != null && results.Count > 0) + { + countRead += results.Count; + if (ReadCount == 1) { - ProviderInvocationException providerException = - new( - "ProviderContentReadError", - SessionStateStrings.ProviderContentReadError, - holder.PathInfo.Provider, - holder.PathInfo.Path, - e); - - // Log a provider health event - MshLog.LogProviderHealthEvent( - this.Context, - holder.PathInfo.Provider.Name, - providerException, - Severity.Warning); - - WriteError(new ErrorRecord( - providerException.ErrorRecord, - providerException)); - - break; + // Write out the content as a single object + WriteContentObject(results[0], countRead, holder.PathInfo, currentContext); } - - if (results != null && results.Count > 0) + else { - countRead += results.Count; - if (ReadCount == 1) - { - // Write out the content as a single object - WriteContentObject(results[0], countRead, holder.PathInfo, currentContext); - } - else - { - // Write out the content as an array of objects - WriteContentObject(results, countRead, holder.PathInfo, currentContext); - } + // Write out the content as an array of objects + WriteContentObject(results, countRead, holder.PathInfo, currentContext); } - } while (results != null && results.Count > 0 && ((TotalCount < 0) || countRead < TotalCount)); - } + } + } while (results != null && results.Count > 0 && (TotalCount == -1 || countRead < TotalCount)); } } finally { - // close all the content readers + // Close all the content readers CloseContent(contentStreams, false); @@ -277,14 +227,14 @@ protected override void ProcessRecord() /// /// /// - /// true if no error occured + /// true if no error occurred /// false if there was an error /// private bool ScanForwardsForTail(in ContentHolder holder, CmdletProviderContext currentContext) { var fsReader = holder.Reader as FileSystemContentReaderWriter; Dbg.Diagnostics.Assert(fsReader != null, "Tail is only supported for FileSystemContentReaderWriter"); - var tailResultQueue = new Queue(); + Queue tailResultQueue = new(); IList results = null; ErrorRecord error = null; @@ -327,7 +277,10 @@ private bool ScanForwardsForTail(in ContentHolder holder, CmdletProviderContext foreach (object entry in results) { if (tailResultQueue.Count == Tail) + { tailResultQueue.Dequeue(); + } + tailResultQueue.Enqueue(entry); } } @@ -349,21 +302,25 @@ private bool ScanForwardsForTail(in ContentHolder holder, CmdletProviderContext { // Write out the content as single object while (tailResultQueue.Count > 0) + { WriteContentObject(tailResultQueue.Dequeue(), count++, holder.PathInfo, currentContext); + } } else // ReadCount < Queue.Count { while (tailResultQueue.Count >= ReadCount) { - var outputList = new List((int)ReadCount); + List outputList = new((int)ReadCount); for (int idx = 0; idx < ReadCount; idx++, count++) + { outputList.Add(tailResultQueue.Dequeue()); + } + // Write out the content as an array of objects WriteContentObject(outputList.ToArray(), count, holder.PathInfo, currentContext); } - int remainder = tailResultQueue.Count; - if (remainder > 0) + if (tailResultQueue.Count > 0) { // Write out the content as an array of objects WriteContentObject(tailResultQueue.ToArray(), count, holder.PathInfo, currentContext); diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetWMIObjectCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetWMIObjectCommand.cs index f460b2c4957..85d8c4feb3f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetWMIObjectCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetWMIObjectCommand.cs @@ -26,7 +26,7 @@ public class GetWmiObjectCommand : WmiBaseCmdlet [Alias("ClassName")] [Parameter(Position = 0, Mandatory = true, ParameterSetName = "query")] [Parameter(Position = 1, ParameterSetName = "list")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public string Class { get; set; } /// @@ -39,7 +39,7 @@ public class GetWmiObjectCommand : WmiBaseCmdlet /// The WMI properties to retrieve. /// [Parameter(Position = 1, ParameterSetName = "query")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public string[] Property { get { return (string[])_property.Clone(); } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs index d6768a1315e..1aa94509469 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs @@ -169,10 +169,7 @@ protected override void ProcessRecord() /// protected override void StopProcessing() { - if (_searchProcess != null) - { - _searchProcess.Dispose(); - } + _searchProcess?.Dispose(); } #endregion Overrides @@ -227,10 +224,7 @@ public void Dispose(bool disposing) { if (disposing) { - if (_searchProcess != null) - { - _searchProcess.Dispose(); - } + _searchProcess?.Dispose(); } } 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; + +/// +/// 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. +/// +internal sealed class JobProcessCollection : IDisposable +{ + /// + /// Stores the initialisation state of the job and completion port. + /// + private bool? _initStatus; + + /// + /// 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. + /// + private Interop.Windows.SafeJobHandle? _jobObject; + + /// + /// The completion port handle that is used to monitor job events. + /// + private Interop.Windows.SafeIoCompletionPort? _completionPort; + + /// + /// Initializes a new instance of the class. + /// + public JobProcessCollection() + { } + + /// + /// Initializes the job and IO completion port and adds the process to the + /// job object. + /// + /// The process to add to the job. + /// Whether the job creation and assignment worked or not. + public bool AssignProcessToJobObject(SafeProcessHandle process) + => InitializeJob() && Interop.Windows.AssignProcessToJobObject(_jobObject, process); + + /// + /// Blocks the current thread until all processes in the job have exited. + /// + /// A token to cancel the operation. + 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/MovePropertyCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/MovePropertyCommand.cs index fb90ec1c502..a40cae64b9f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/MovePropertyCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/MovePropertyCommand.cs @@ -61,10 +61,7 @@ public string[] Name set { - if (value == null) - { - value = Array.Empty(); - } + value ??= Array.Empty(); _property = value; } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs index 23aa621266d..f4ba55202ed 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. /// - [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 @@ -283,7 +283,7 @@ public class CoreCommandWithCredentialsBase : CoreCommandBase /// Gets or sets the credential parameter. /// [Parameter(ValueFromPipelineByPropertyName = true)] - [Credential()] + [Credential] public PSCredential Credential { get; set; } #endregion Parameters @@ -618,7 +618,7 @@ protected override void ProcessRecord() break; default: - Dbg.Diagnostics.Assert(false, string.Format(System.Globalization.CultureInfo.InvariantCulture, "One of the predefined parameter sets should have been specified, instead we got: {0}", ParameterSetName)); + Dbg.Diagnostics.Assert(false, string.Create(System.Globalization.CultureInfo.InvariantCulture, $"One of the predefined parameter sets should have been specified, instead we got: {ParameterSetName}")); break; } } @@ -1075,7 +1075,7 @@ protected override void ProcessRecord() #region NewPSDriveCommand /// - /// Mounts a drive in the Monad namespace. + /// Mounts a drive in PowerShell runspace. /// [Cmdlet(VerbsCommon.New, "PSDrive", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low, SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096815")] @@ -1129,6 +1129,7 @@ public string Description /// Gets or sets the scope identifier for the drive being created. /// [Parameter(ValueFromPipelineByPropertyName = true)] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } #if !UNIX @@ -1477,7 +1478,7 @@ internal List GetMatchingDrives( #region RemovePSDriveCommand /// - /// Removes a drive that is mounted in the Monad namespace. + /// Removes a drive that is mounted in the PowerShell runspace. /// [Cmdlet(VerbsCommon.Remove, "PSDrive", DefaultParameterSetName = NameParameterSet, SupportsShouldProcess = true, SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097050")] @@ -1533,6 +1534,7 @@ public string[] PSProvider /// global scope until a drive of the given name is found to remove. /// [Parameter(ValueFromPipelineByPropertyName = true)] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } /// @@ -1653,7 +1655,7 @@ protected override void ProcessRecord() #region GetPSDriveCommand /// - /// Gets a specified or listing of drives that are mounted in the Monad + /// Gets a specified or listing of drives that are mounted in PowerShell /// namespace. /// [Cmdlet(VerbsCommon.Get, "PSDrive", DefaultParameterSetName = NameParameterSet, SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096494")] @@ -1701,6 +1703,7 @@ public string[] LiteralName /// Gets or sets the scope parameter to the command. /// [Parameter(ValueFromPipelineByPropertyName = true)] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } /// @@ -2699,7 +2702,7 @@ protected override void ProcessRecord() try { System.IO.DirectoryInfo di = new(providerPath); - if (di != null && (di.Attributes & System.IO.FileAttributes.ReparsePoint) != 0) + if (InternalSymbolicLinkLinkCodeMethods.IsReparsePointLikeSymlink(di)) { shouldRecurse = false; treatAsFile = true; @@ -4128,7 +4131,7 @@ public class GetPSProviderCommand : CoreCommandBase /// Gets or sets the provider that will be removed. /// [Parameter(Position = 0, ValueFromPipelineByPropertyName = true)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public string[] PSProvider { get => _provider; 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 /// [Parameter(ValueFromPipelineByPropertyName = true)] [Alias("Type")] +#if !UNIX + [ArgumentCompleter(typeof(PropertyTypeArgumentCompleter))] +#endif public string PropertyType { get; set; } /// @@ -175,4 +182,114 @@ protected override void ProcessRecord() #endregion Command code } + +#if !UNIX + /// + /// Provides argument completion for PropertyType parameter. + /// + 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 s_RegistryPropertyTypes = new List(capacity: 7) + { + "String", + "ExpandString", + "Binary", + "DWord", + "MultiString", + "QWord", + "Unknown" + }; + + /// + /// Returns completion results for PropertyType parameter. + /// + /// The command name. + /// The parameter name. + /// The word to complete. + /// The command AST. + /// The fake bound parameters. + /// List of Completion Results. + public IEnumerable CompleteArgument( + string commandName, + string parameterName, + string wordToComplete, + CommandAst commandAst, + IDictionary fakeBoundParameters) + => IsRegistryProvider(fakeBoundParameters) + ? CompletionHelpers.GetMatchingResults( + wordToComplete, + possibleCompletionValues: s_RegistryPropertyTypes, + displayInfoMapper: RegistryPropertyTypeDisplayInfoMapper, + resultType: CompletionResultType.ParameterValue) + : []; + + /// + /// Checks if parameter paths are from Registry provider. + /// + /// The fake bound parameters. + /// Boolean indicating if paths are from Registry Provider. + private static bool IsRegistryProvider(IDictionary fakeBoundParameters) + { + Collection 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"); + } + + /// + /// Resolve path or literal path using Resolve-Path. + /// + /// The path to resolve. + /// Specifies if path is literal path. + /// Collection of Pathinfo objects. + private static Collection 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 output = ps.Invoke(); + + 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 d56a20faba0..b6609f8b692 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ParsePathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ParsePathCommand.cs @@ -12,8 +12,8 @@ namespace Microsoft.PowerShell.Commands { /// - /// A command to resolve MSH paths containing glob characters to - /// MSH paths that match the glob strings. + /// A command to resolve PowerShell paths containing glob characters to + /// PowerShell paths that match the glob strings. /// [Cmdlet(VerbsCommon.Split, "Path", DefaultParameterSetName = "ParentSet", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097149")] [OutputType(typeof(string), ParameterSetName = new[] { leafSet, @@ -105,7 +105,7 @@ public string[] LiteralPath /// /// If true the qualifier of the path will be returned. /// The qualifier is the drive or provider that is qualifying - /// the MSH path. + /// the PowerShell path. /// [Parameter(ParameterSetName = qualifierSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] public SwitchParameter Qualifier { get; set; } @@ -116,7 +116,7 @@ public string[] LiteralPath /// /// If true the qualifier of the path will be returned. /// The qualifier is the drive or provider that is qualifying - /// the MSH path. + /// the PowerShell path. /// [Parameter(ParameterSetName = noQualifierSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] public SwitchParameter NoQualifier { get; set; } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs index 6656dcc549b..56efe65b4bb 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; // Win32Exception using System.Diagnostics; // Process class @@ -11,18 +12,16 @@ using System.IO; using System.Management.Automation; using System.Management.Automation.Internal; +using System.Management.Automation.Language; using System.Net; using System.Runtime.InteropServices; using System.Runtime.Serialization; -using System.Security; using System.Security.Principal; using System.Text; using System.Threading; using Microsoft.Management.Infrastructure; using Microsoft.PowerShell.Commands.Internal; using Microsoft.Win32.SafeHandles; -using DWORD = System.UInt32; -using FileNakedHandle = System.IntPtr; namespace Microsoft.PowerShell.Commands { @@ -266,30 +265,17 @@ private void RetrieveProcessesByInput() } /// - /// Retrieve the master list of all processes. + /// Gets an array of all processes. /// - /// + /// An array of components that represents all the process resources. /// /// MSDN does not document the list of exceptions, /// but it is reasonable to expect that SecurityException is /// among them. Errors here will terminate the cmdlet. /// - internal Process[] AllProcesses - { - get - { - if (_allProcesses == null) - { - List processes = new(); - processes.AddRange(Process.GetProcesses()); - _allProcesses = processes.ToArray(); - } + internal Process[] AllProcesses => _allProcesses ??= Process.GetProcesses(); - return _allProcesses; - } - } - - private Process[] _allProcesses = null; + private Process[] _allProcesses; /// /// Add to , @@ -532,27 +518,20 @@ public override Process[] InputObject [Parameter(ParameterSetName = NameWithUserNameParameterSet, Mandatory = true)] [Parameter(ParameterSetName = IdWithUserNameParameterSet, Mandatory = true)] [Parameter(ParameterSetName = InputObjectWithUserNameParameterSet, Mandatory = true)] - public SwitchParameter IncludeUserName - { - get { return _includeUserName; } - - set { _includeUserName = value; } - } - - private bool _includeUserName = false; + public SwitchParameter IncludeUserName { get; set; } - /// + /// /// To display the modules of a process. - /// + /// [Parameter(ParameterSetName = NameParameterSet)] [Parameter(ParameterSetName = IdParameterSet)] [Parameter(ParameterSetName = InputObjectParameterSet)] [ValidateNotNull] public SwitchParameter Module { get; set; } - /// + /// /// To display the fileversioninfo of the main module of a process. - /// + /// [Parameter(ParameterSetName = NameParameterSet)] [Parameter(ParameterSetName = IdParameterSet)] [Parameter(ParameterSetName = InputObjectParameterSet)] @@ -564,20 +543,6 @@ public SwitchParameter IncludeUserName #region Overrides - /// - /// Check the elevation mode if IncludeUserName is specified. - /// - protected override void BeginProcessing() - { - // The parameter 'IncludeUserName' requires administrator privilege - if (IncludeUserName.IsPresent && !Utils.IsAdministrator()) - { - var ex = new InvalidOperationException(ProcessResources.IncludeUserNameRequiresElevation); - var er = new ErrorRecord(ex, "IncludeUserNameRequiresElevation", ErrorCategory.InvalidOperation, null); - ThrowTerminatingError(er); - } - } - /// /// Write the process objects. /// @@ -653,6 +618,10 @@ protected override void ProcessRecord() WriteNonTerminatingError(process, ex, ProcessResources.CouldNotEnumerateModules, "CouldNotEnumerateModules", ErrorCategory.PermissionDenied); } } + catch (PipelineStoppedException) + { + throw; + } catch (Exception exception) { WriteNonTerminatingError(process, exception, ProcessResources.CouldNotEnumerateModules, "CouldNotEnumerateModules", ErrorCategory.PermissionDenied); @@ -662,7 +631,7 @@ protected override void ProcessRecord() { try { - ProcessModule mainModule = PsUtils.GetMainModule(process); + ProcessModule mainModule = process.MainModule; if (mainModule != null) { WriteObject(mainModule.FileVersionInfo, true); @@ -682,7 +651,7 @@ protected override void ProcessRecord() { if (exception.HResult == 299) { - WriteObject(PsUtils.GetMainModule(process).FileVersionInfo, true); + WriteObject(process.MainModule?.FileVersionInfo, true); } else { @@ -701,7 +670,7 @@ protected override void ProcessRecord() } else { - WriteObject(IncludeUserName.IsPresent ? AddUserNameToProcess(process) : (object)process); + WriteObject(IncludeUserName.IsPresent ? AddUserNameToProcess(process) : process); } } } @@ -752,56 +721,46 @@ private static string RetrieveProcessUserName(Process process) try { - do + int error; + if (!Win32Native.OpenProcessToken(process.Handle, TOKEN_QUERY, out processTokenHandler)) { - int error; - if (!Win32Native.OpenProcessToken(process.Handle, TOKEN_QUERY, out processTokenHandler)) { break; } + return null; + } - // Set the default length to be 256, so it will be sufficient for most cases. - int tokenInfoLength = 256; - tokenUserInfo = Marshal.AllocHGlobal(tokenInfoLength); - if (!Win32Native.GetTokenInformation(processTokenHandler, Win32Native.TOKEN_INFORMATION_CLASS.TokenUser, tokenUserInfo, tokenInfoLength, out tokenInfoLength)) + // Set the default length to be 256, so it will be sufficient for most cases. + int tokenInfoLength = 256; + tokenUserInfo = Marshal.AllocHGlobal(tokenInfoLength); + if (!Win32Native.GetTokenInformation(processTokenHandler, Win32Native.TOKEN_INFORMATION_CLASS.TokenUser, tokenUserInfo, tokenInfoLength, out tokenInfoLength)) + { + error = Marshal.GetLastWin32Error(); + if (error == Win32Native.ERROR_INSUFFICIENT_BUFFER) { - error = Marshal.GetLastWin32Error(); - if (error == Win32Native.ERROR_INSUFFICIENT_BUFFER) - { - Marshal.FreeHGlobal(tokenUserInfo); - tokenUserInfo = Marshal.AllocHGlobal(tokenInfoLength); + Marshal.FreeHGlobal(tokenUserInfo); + tokenUserInfo = Marshal.AllocHGlobal(tokenInfoLength); - if (!Win32Native.GetTokenInformation(processTokenHandler, Win32Native.TOKEN_INFORMATION_CLASS.TokenUser, tokenUserInfo, tokenInfoLength, out tokenInfoLength)) { break; } - } - else + if (!Win32Native.GetTokenInformation(processTokenHandler, Win32Native.TOKEN_INFORMATION_CLASS.TokenUser, tokenUserInfo, tokenInfoLength, out tokenInfoLength)) { - break; + return null; } } - - var tokenUser = Marshal.PtrToStructure(tokenUserInfo); - - // Max username is defined as UNLEN = 256 in lmcons.h - // Max domainname is defined as DNLEN = CNLEN = 15 in lmcons.h - // The buffer length must be +1, last position is for a null string terminator. - int userNameLength = 257; - int domainNameLength = 16; -#pragma warning disable CA2014 - Span userNameStr = stackalloc char[userNameLength]; - Span domainNameStr = stackalloc char[domainNameLength]; -#pragma warning restore CA2014 - Win32Native.SID_NAME_USE accountType; - - // userNameLength and domainNameLength will be set to actual lengths. - if (!Win32Native.LookupAccountSid(null, tokenUser.User.Sid, userNameStr, ref userNameLength, domainNameStr, ref domainNameLength, out accountType)) + else { - break; + return null; } + } - userName = string.Concat(domainNameStr.Slice(0, domainNameLength), "\\", userNameStr.Slice(0, userNameLength)); - } while (false); + var tokenUser = Marshal.PtrToStructure(tokenUserInfo); + SecurityIdentifier sid = new SecurityIdentifier(tokenUser.User.Sid); + userName = sid.Translate(typeof(System.Security.Principal.NTAccount)).Value; } catch (NotSupportedException) { // The Process not started yet, or it's a process from a remote machine. } + catch (IdentityNotMappedException) + { + // SID cannot be mapped to a user + } catch (InvalidOperationException) { // The Process has exited, Process.Handle will raise this exception. @@ -826,7 +785,6 @@ private static string RetrieveProcessUserName(Process process) Win32Native.CloseHandle(processTokenHandler); } } - #endif return userName; } @@ -840,6 +798,7 @@ private static string RetrieveProcessUserName(Process process) /// This class implements the Wait-process command. /// [Cmdlet(VerbsLifecycle.Wait, "Process", DefaultParameterSetName = "Name", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097146")] + [OutputType(typeof(Process))] public sealed class WaitProcessCommand : ProcessBaseCommand { #region Parameters @@ -914,6 +873,18 @@ public int Timeout } } + /// + /// Gets or sets a value indicating whether to return after any one process exits. + /// + [Parameter] + public SwitchParameter Any { get; set; } + + /// + /// Gets or sets a value indicating whether to return the Process objects after waiting. + /// + [Parameter] + public SwitchParameter PassThru { get; set; } + private int _timeout = 0; private bool _timeOutSpecified; @@ -945,12 +916,9 @@ public void Dispose() // Handle Exited event and display process information. private void myProcess_Exited(object sender, System.EventArgs e) { - if (System.Threading.Interlocked.Decrement(ref _numberOfProcessesToWaitFor) == 0) + if (Any || (Interlocked.Decrement(ref _numberOfProcessesToWaitFor) == 0)) { - if (_waitHandle != null) - { - _waitHandle.Set(); - } + _waitHandle?.Set(); } } @@ -1000,7 +968,12 @@ protected override void EndProcessing() { try { - if (!process.HasExited) + // Check for processes that exit too soon for us to add an event. + if (Any && process.HasExited) + { + _waitHandle.Set(); + } + else if (!process.HasExited) { process.EnableRaisingEvents = true; process.Exited += myProcess_Exited; @@ -1016,11 +989,12 @@ protected override void EndProcessing() } } + bool hasTimedOut = false; if (_numberOfProcessesToWaitFor > 0) { if (_timeOutSpecified) { - _waitHandle.WaitOne(_timeout * 1000); + hasTimedOut = !_waitHandle.WaitOne(_timeout * 1000); } else { @@ -1028,34 +1002,37 @@ protected override void EndProcessing() } } - foreach (Process process in _processList) + if (hasTimedOut || (!Any && _numberOfProcessesToWaitFor > 0)) { - try + foreach (Process process in _processList) { - if (!process.HasExited) + try { - string message = StringUtil.Format(ProcessResources.ProcessNotTerminated, new object[] { process.ProcessName, process.Id }); - ErrorRecord errorRecord = new(new TimeoutException(message), "ProcessNotTerminated", ErrorCategory.CloseError, process); - WriteError(errorRecord); + if (!process.HasExited) + { + string message = StringUtil.Format(ProcessResources.ProcessNotTerminated, new object[] { process.ProcessName, process.Id }); + ErrorRecord errorRecord = new(new TimeoutException(message), "ProcessNotTerminated", ErrorCategory.CloseError, process); + WriteError(errorRecord); + } + } + catch (Win32Exception exception) + { + WriteNonTerminatingError(process, exception, ProcessResources.ProcessIsNotTerminated, "ProcessNotTerminated", ErrorCategory.CloseError); } } - catch (Win32Exception exception) - { - WriteNonTerminatingError(process, exception, ProcessResources.ProcessIsNotTerminated, "ProcessNotTerminated", ErrorCategory.CloseError); - } + } + + if (PassThru) + { + WriteObject(_processList, enumerateCollection: true); } } /// /// StopProcessing. /// - protected override void StopProcessing() - { - if (_waitHandle != null) - { - _waitHandle.Set(); - } - } + protected override void StopProcessing() => _waitHandle?.Set(); + #endregion Overrides } @@ -1188,7 +1165,10 @@ protected override void ProcessRecord() SafeGetProcessName(process), SafeGetProcessId(process)); - if (!ShouldProcess(targetString)) { continue; } + if (!ShouldProcess(targetString)) + { + continue; + } try { @@ -1346,7 +1326,10 @@ private bool IsProcessOwnedByCurrentUser(Process process) } finally { - if (ph != IntPtr.Zero) { Win32Native.CloseHandle(ph); } + if (ph != IntPtr.Zero) + { + Win32Native.CloseHandle(ph); + } } return false; @@ -1506,7 +1489,10 @@ protected override void ProcessRecord() SafeGetProcessName(process), SafeGetProcessId(process)); - if (!ShouldProcess(targetMessage)) { continue; } + if (!ShouldProcess(targetMessage)) + { + continue; + } // Sometimes Idle process has processid zero,so handle that because we cannot attach debugger to it. if (process.Id == 0) @@ -1521,7 +1507,10 @@ protected override void ProcessRecord() { // If the process has exited, we skip it. If the process is from a remote // machine, then we generate a non-terminating error. - if (process.HasExited) { continue; } + if (process.HasExited) + { + continue; + } } catch (NotSupportedException ex) { @@ -1572,8 +1561,7 @@ private void AttachDebuggerToProcess(Process process) } catch (CimException e) { - string message = e.Message; - if (!string.IsNullOrEmpty(message)) { message = message.Trim(); } + string message = e.Message?.Trim(); var errorRecord = new ErrorRecord( new InvalidOperationException(StringUtil.Format(ProcessResources.DebuggerError, message)), @@ -1589,16 +1577,17 @@ private void AttachDebuggerToProcess(Process process) /// private static string MapReturnCodeToErrorMessage(int returnCode) { - string errorMessage = string.Empty; - switch (returnCode) + string errorMessage = returnCode switch { - case 2: errorMessage = ProcessResources.AttachDebuggerReturnCode2; break; - case 3: errorMessage = ProcessResources.AttachDebuggerReturnCode3; break; - case 8: errorMessage = ProcessResources.AttachDebuggerReturnCode8; break; - case 9: errorMessage = ProcessResources.AttachDebuggerReturnCode9; break; - case 21: errorMessage = ProcessResources.AttachDebuggerReturnCode21; break; - default: Diagnostics.Assert(false, "Unreachable code."); break; - } + 2 => ProcessResources.AttachDebuggerReturnCode2, + 3 => ProcessResources.AttachDebuggerReturnCode3, + 8 => ProcessResources.AttachDebuggerReturnCode8, + 9 => ProcessResources.AttachDebuggerReturnCode9, + 21 => ProcessResources.AttachDebuggerReturnCode21, + _ => string.Empty + }; + + Diagnostics.Assert(!string.IsNullOrEmpty(errorMessage), "Error message should not be null or empty."); return errorMessage; } @@ -1614,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 @@ -1687,7 +1676,7 @@ public SwitchParameter LoadUserProfile private SwitchParameter _loaduserprofile = SwitchParameter.Present; /// - /// Starts process in a new window. + /// Starts process in the current console window. /// [Parameter(ParameterSetName = "Default")] [Alias("nnw")] @@ -1787,6 +1776,7 @@ public string RedirectStandardOutput /// [Parameter(ParameterSetName = "UseShellExecute")] [ValidateNotNullOrEmpty] + [ArgumentCompleter(typeof(VerbArgumentCompleter))] public string Verb { get; set; } /// @@ -1837,6 +1827,26 @@ public SwitchParameter UseNewEnvironment private SwitchParameter _UseNewEnvironment; + /// + /// Gets or sets the environment variables for the process. + /// + [Parameter] + public Hashtable Environment + { + get + { + return _environment; + } + + set + { + _environment = value; + _isDefaultSetParameterSpecified = true; + } + } + + private Hashtable _environment; + #endregion #region overrides @@ -1925,8 +1935,12 @@ protected override void BeginProcessing() } else { - // Working Directory not specified -> Assign Current Path. - startInfo.WorkingDirectory = PathUtils.ResolveFilePath(this.SessionState.Path.CurrentFileSystemLocation.Path, this, isLiteralPath: true); + // Working Directory not specified -> Assign Current Path, but only if it still exists + var currentDirectory = PathUtils.ResolveFilePath(this.SessionState.Path.CurrentFileSystemLocation.Path, this, isLiteralPath: true); + if (Directory.Exists(currentDirectory)) + { + startInfo.WorkingDirectory = currentDirectory; + } } if (this.ParameterSetName.Equals("Default")) @@ -1939,13 +1953,20 @@ protected override void BeginProcessing() if (_UseNewEnvironment) { startInfo.EnvironmentVariables.Clear(); - LoadEnvironmentVariable(startInfo, Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Machine)); - LoadEnvironmentVariable(startInfo, Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User)); + LoadEnvironmentVariable(startInfo, System.Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Machine)); + LoadEnvironmentVariable(startInfo, System.Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User)); + } + + if (_environment != null) + { + LoadEnvironmentVariable(startInfo, _environment); } 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; } @@ -2025,15 +2046,89 @@ protected override void BeginProcessing() } else if (ParameterSetName.Equals("UseShellExecute")) { - if (Verb != null) { startInfo.Verb = Verb; } + if (Verb != null) + { + startInfo.Verb = Verb; + } startInfo.WindowStyle = _windowstyle; } string targetMessage = StringUtil.Format(ProcessResources.StartProcessTarget, startInfo.FileName, startInfo.Arguments.Trim()); - if (!ShouldProcess(targetMessage)) { return; } + if (!ShouldProcess(targetMessage)) + { + return; + } + + Process process = null; + +#if !UNIX + using JobProcessCollection jobObject = new(); + bool? jobAssigned = null; +#endif + if (startInfo.UseShellExecute) + { + process = StartWithShellExecute(startInfo); + } + else + { +#if UNIX + process = new Process() { StartInfo = startInfo }; + SetupInputOutputRedirection(process); + process.Start(); + if (process.StartInfo.RedirectStandardOutput) + { + process.BeginOutputReadLine(); + } + + if (process.StartInfo.RedirectStandardError) + { + process.BeginErrorReadLine(); + } - Process process = Start(startInfo); + if (process.StartInfo.RedirectStandardInput) + { + WriteToStandardInput(process); + } +#else + using ProcessInformation processInfo = StartWithCreateProcess(startInfo); + process = Process.GetProcessById(processInfo.ProcessId); + + // Starting a process as another user might make it impossible + // to get the process handle from the S.D.Process object. Use + // the ALL_ACCESS token from CreateProcess here to setup the + // job object assignment early if -Wait was specified. + // https://github.com/PowerShell/PowerShell/issues/17033 + if (Wait) + { + jobAssigned = jobObject.AssignProcessToJobObject(processInfo.Process); + } + + // Since the process wasn't spawned by .NET, we need to trigger .NET to get a lock on the handle of the process. + // Otherwise, accessing properties like `ExitCode` will throw the following exception: + // "Process was not started by this object, so requested information cannot be determined." + // Fetching the process handle will trigger the `Process` object to update its internal state by calling `SetProcessHandle`, + // the result is discarded as it's not used later in this code. + try + { + _ = process.Handle; + } + catch (Win32Exception e) + { + // If the caller was not an admin and the process was started with another user's credentials .NET + // won't be able to retrieve the process handle. As this is not a critical failure we treat this as + // a warning. + if (PassThru) + { + string msg = StringUtil.Format(ProcessResources.FailedToCreateProcessObject, e.Message); + WriteDebug(msg); + } + } + + // Resume the process now that is has been set up. + processInfo.Resume(); +#endif + } if (PassThru.IsPresent) { @@ -2056,23 +2151,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 - ProcessCollection jobObject = new(); - if (jobObject.AssignProcessToJobObject(process)) + // 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 if (!process.HasExited) + else { // WinBlue: 27537 Start-Process -Wait doesn't work in a remote session on Windows 7 or lower. - process.Exited += myProcess_Exited; - process.EnableRaisingEvents = true; - process.WaitForExit(); + // A Remote session is in it's own job and nested job support was only added in Windows 8/Server 2012. + process.WaitForExitAsync(_cancellationTokenSource.Token).GetAwaiter().GetResult(); } #endif } @@ -2088,13 +2180,7 @@ protected override void BeginProcessing() /// /// Implements ^c, after creating a process. /// - protected override void StopProcessing() - { - if (_waithandle != null) - { - _waithandle.Set(); - } - } + protected override void StopProcessing() => _cancellationTokenSource.Cancel(); #endregion @@ -2111,28 +2197,13 @@ public void Dispose() private void Dispose(bool isDisposing) { - if (_waithandle != null) - { - _waithandle.Dispose(); - _waithandle = null; - } + _cancellationTokenSource.Dispose(); } #endregion #region Private Methods - /// - /// When Process exits the wait handle is set. - /// - private void myProcess_Exited(object sender, System.EventArgs e) - { - if (_waithandle != null) - { - _waithandle.Set(); - } - } - private string ResolveFilePath(string path) { string filepath = PathUtils.ResolveFilePath(path, this); @@ -2149,50 +2220,22 @@ private static void LoadEnvironmentVariable(ProcessStartInfo startinfo, IDiction processEnvironment.Remove(entry.Key.ToString()); } - if (entry.Key.ToString().Equals("PATH")) + if (entry.Value != null) { - processEnvironment.Add(entry.Key.ToString(), Environment.GetEnvironmentVariable(entry.Key.ToString(), EnvironmentVariableTarget.Machine) + ";" + Environment.GetEnvironmentVariable(entry.Key.ToString(), EnvironmentVariableTarget.User)); - } - else - { - processEnvironment.Add(entry.Key.ToString(), entry.Value.ToString()); - } - } - } - - private Process Start(ProcessStartInfo startInfo) - { - Process process = null; - if (startInfo.UseShellExecute) - { - process = StartWithShellExecute(startInfo); - } - else - { + if (entry.Key.ToString().Equals("PATH")) + { #if UNIX - process = new Process() { StartInfo = startInfo }; - SetupInputOutputRedirection(process); - process.Start(); - if (process.StartInfo.RedirectStandardOutput) - { - process.BeginOutputReadLine(); - } - - if (process.StartInfo.RedirectStandardError) - { - process.BeginErrorReadLine(); - } - - if (process.StartInfo.RedirectStandardInput) - { - WriteToStandardInput(process); - } + processEnvironment.Add(entry.Key.ToString(), entry.Value.ToString()); #else - process = StartWithCreateProcess(startInfo); + processEnvironment.Add(entry.Key.ToString(), entry.Value.ToString() + Path.PathSeparator + System.Environment.GetEnvironmentVariable(entry.Key.ToString(), EnvironmentVariableTarget.Machine) + Path.PathSeparator + System.Environment.GetEnvironmentVariable(entry.Key.ToString(), EnvironmentVariableTarget.User)); #endif + } + else + { + processEnvironment.Add(entry.Key.ToString(), entry.Value.ToString()); + } + } } - - return process; } #if UNIX @@ -2230,15 +2273,8 @@ private void StreamClosing() { Thread.Sleep(1000); - if (_outputWriter != null) - { - _outputWriter.Dispose(); - } - - if (_errorWriter != null) - { - _errorWriter.Dispose(); - } + _outputWriter?.Dispose(); + _errorWriter?.Dispose(); } private void SetupInputOutputRedirection(Process p) @@ -2299,28 +2335,22 @@ private void WriteToStandardInput(Process p) writer.Dispose(); } #else - private SafeFileHandle GetSafeFileHandleForRedirection(string RedirectionPath, uint dwCreationDisposition) - { - System.IntPtr hFileHandle = System.IntPtr.Zero; - ProcessNativeMethods.SECURITY_ATTRIBUTES lpSecurityAttributes = new(); - - hFileHandle = ProcessNativeMethods.CreateFileW(RedirectionPath, - ProcessNativeMethods.GENERIC_READ | ProcessNativeMethods.GENERIC_WRITE, - ProcessNativeMethods.FILE_SHARE_WRITE | ProcessNativeMethods.FILE_SHARE_READ, - lpSecurityAttributes, - dwCreationDisposition, - ProcessNativeMethods.FILE_ATTRIBUTE_NORMAL, - System.IntPtr.Zero); - if (hFileHandle == System.IntPtr.Zero) - { - int error = Marshal.GetLastWin32Error(); - Win32Exception win32ex = new(error); + + private SafeFileHandle GetSafeFileHandleForRedirection(string RedirectionPath, FileMode mode) + { + SafeFileHandle sf = null; + try + { + sf = File.OpenHandle(RedirectionPath, mode, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Inheritable, FileOptions.WriteThrough); + } + catch (Win32Exception win32ex) + { + sf?.Dispose(); string message = StringUtil.Format(ProcessResources.InvalidStartProcess, win32ex.Message); ErrorRecord er = new(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); ThrowTerminatingError(er); } - SafeFileHandle sf = new(hFileHandle, true); return sf; } @@ -2376,16 +2406,31 @@ private static byte[] ConvertEnvVarsToByteArray(StringDictionary sd) private void SetStartupInfo(ProcessStartInfo startinfo, ref ProcessNativeMethods.STARTUPINFO lpStartupInfo, ref int creationFlags) { + // 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) { startinfo.RedirectStandardInput = true; _redirectstandardinput = ResolveFilePath(_redirectstandardinput); - lpStartupInfo.hStdInput = GetSafeFileHandleForRedirection(_redirectstandardinput, ProcessNativeMethods.OPEN_EXISTING); + lpStartupInfo.hStdInput = GetSafeFileHandleForRedirection(_redirectstandardinput, FileMode.Open); } - else + else if (startinfo.CreateNoWindow) { - lpStartupInfo.hStdInput = new SafeFileHandle(ProcessNativeMethods.GetStdHandle(-10), false); + lpStartupInfo.hStdInput = new SafeFileHandle( + ProcessNativeMethods.GetStdHandle(-10), + ownsHandle: false); } // RedirectionStandardOutput @@ -2393,11 +2438,13 @@ private void SetStartupInfo(ProcessStartInfo startinfo, ref ProcessNativeMethods { startinfo.RedirectStandardOutput = true; _redirectstandardoutput = ResolveFilePath(_redirectstandardoutput); - lpStartupInfo.hStdOutput = GetSafeFileHandleForRedirection(_redirectstandardoutput, ProcessNativeMethods.CREATE_ALWAYS); + lpStartupInfo.hStdOutput = GetSafeFileHandleForRedirection(_redirectstandardoutput, FileMode.Create); } - else + else if (startinfo.CreateNoWindow) { - lpStartupInfo.hStdOutput = new SafeFileHandle(ProcessNativeMethods.GetStdHandle(-11), false); + lpStartupInfo.hStdOutput = new SafeFileHandle( + ProcessNativeMethods.GetStdHandle(-11), + ownsHandle: false); } // RedirectionStandardError @@ -2405,15 +2452,20 @@ private void SetStartupInfo(ProcessStartInfo startinfo, ref ProcessNativeMethods { startinfo.RedirectStandardError = true; _redirectstandarderror = ResolveFilePath(_redirectstandarderror); - lpStartupInfo.hStdError = GetSafeFileHandleForRedirection(_redirectstandarderror, ProcessNativeMethods.CREATE_ALWAYS); + lpStartupInfo.hStdError = GetSafeFileHandleForRedirection(_redirectstandarderror, FileMode.Create); } - else + else if (startinfo.CreateNoWindow) { - lpStartupInfo.hStdError = new SafeFileHandle(ProcessNativeMethods.GetStdHandle(-12), false); + lpStartupInfo.hStdError = new SafeFileHandle( + ProcessNativeMethods.GetStdHandle(-12), + ownsHandle: false); } - // STARTF_USESTDHANDLES - lpStartupInfo.dwFlags = 0x100; + if (hasRedirection) + { + // Set STARTF_USESTDHANDLES only if there is redirection. + lpStartupInfo.dwFlags = 0x100; + } if (startinfo.CreateNoWindow) { @@ -2457,10 +2509,10 @@ private void SetStartupInfo(ProcessStartInfo startinfo, ref ProcessNativeMethods /// /// This method will be used on all windows platforms, both full desktop and headless SKUs. /// - private Process StartWithCreateProcess(ProcessStartInfo startinfo) + private ProcessInformation StartWithCreateProcess(ProcessStartInfo startinfo) { ProcessNativeMethods.STARTUPINFO lpStartupInfo = new(); - SafeNativeMethods.PROCESS_INFORMATION lpProcessInformation = new(); + ProcessNativeMethods.PROCESS_INFORMATION lpProcessInformation = new(); int error = 0; GCHandle pinnedEnvironmentBlock = new(); IntPtr AddressOfEnvironmentBlock = IntPtr.Zero; @@ -2507,7 +2559,7 @@ private Process StartWithCreateProcess(ProcessStartInfo startinfo) try { password = (startinfo.Password == null) ? Marshal.StringToCoTaskMemUni(string.Empty) : Marshal.SecureStringToCoTaskMemUnicode(startinfo.Password); - flag = ProcessNativeMethods.CreateProcessWithLogonW(startinfo.UserName, startinfo.Domain, password, logonFlags, null, cmdLine, creationFlags, AddressOfEnvironmentBlock, startinfo.WorkingDirectory, lpStartupInfo, lpProcessInformation); + flag = ProcessNativeMethods.CreateProcessWithLogonW(startinfo.UserName, startinfo.Domain, password, logonFlags, null, cmdLine, creationFlags, AddressOfEnvironmentBlock, startinfo.WorkingDirectory, lpStartupInfo, ref lpProcessInformation); if (!flag) { error = Marshal.GetLastWin32Error(); @@ -2563,7 +2615,7 @@ private Process StartWithCreateProcess(ProcessStartInfo startinfo) ProcessNativeMethods.SECURITY_ATTRIBUTES lpProcessAttributes = new(); ProcessNativeMethods.SECURITY_ATTRIBUTES lpThreadAttributes = new(); - flag = ProcessNativeMethods.CreateProcess(null, cmdLine, lpProcessAttributes, lpThreadAttributes, true, creationFlags, AddressOfEnvironmentBlock, startinfo.WorkingDirectory, lpStartupInfo, lpProcessInformation); + flag = ProcessNativeMethods.CreateProcess(null, cmdLine, lpProcessAttributes, lpThreadAttributes, true, creationFlags, AddressOfEnvironmentBlock, startinfo.WorkingDirectory, lpStartupInfo, ref lpProcessInformation); if (!flag) { error = Marshal.GetLastWin32Error(); @@ -2576,11 +2628,7 @@ private Process StartWithCreateProcess(ProcessStartInfo startinfo) Label_03AE: - // At this point, we should have a suspended process. Get the .Net Process object, resume the process, and return. - Process result = Process.GetProcessById(lpProcessInformation.dwProcessId); - ProcessNativeMethods.ResumeThread(lpProcessInformation.hThread); - - return result; + return new ProcessInformation(lpProcessInformation); } finally { @@ -2594,7 +2642,6 @@ private Process StartWithCreateProcess(ProcessStartInfo startinfo) } lpStartupInfo.Dispose(); - lpProcessInformation.Dispose(); } } #endif @@ -2621,122 +2668,120 @@ private Process StartWithShellExecute(ProcessStartInfo startInfo) #endregion } -#if !UNIX /// - /// 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. + /// Provides argument completion for Verb parameter. /// - internal class ProcessCollection + public class VerbArgumentCompleter : IArgumentCompleter { /// - /// 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. + /// Returns completion results for verb parameter. /// - private readonly Microsoft.PowerShell.Commands.SafeJobHandle _jobObjectHandle; - - /// - /// ProcessCollection constructor. - /// - internal ProcessCollection() + /// The command name. + /// The parameter name. + /// The word to complete. + /// The command AST. + /// The fake bound parameters. + /// List of Completion Results. + public IEnumerable CompleteArgument( + string commandName, + string parameterName, + string wordToComplete, + CommandAst commandAst, + IDictionary fakeBoundParameters) { - IntPtr jobObjectHandleIntPtr = NativeMethods.CreateJobObject(IntPtr.Zero, null); - _jobObjectHandle = new SafeJobHandle(jobObjectHandleIntPtr); - } + // -Verb is not supported on non-Windows platforms as well as Windows headless SKUs + if (!Platform.IsWindowsDesktop) + { + return Array.Empty(); + } - /// - /// Start API assigns the process to the JobObject and starts monitoring - /// the child processes hosted by the process created by Start-Process cmdlet. - /// - internal bool AssignProcessToJobObject(Process process) - { - // Add the process to the job object - bool result = NativeMethods.AssignProcessToJobObject(_jobObjectHandle, process.Handle); - return result; - } + // Completion: Start-Process -FilePath -Verb + if (commandName.Equals("Start-Process", StringComparison.OrdinalIgnoreCase) + && fakeBoundParameters.Contains("FilePath")) + { + string filePath = fakeBoundParameters["FilePath"].ToString(); - /// - /// 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. - /// - 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(); + // Complete file verbs if extension exists + if (Path.HasExtension(filePath)) + { + return CompleteFileVerbs(wordToComplete, filePath); + } - dwSize = Marshal.SizeOf(JobList); - if (NativeMethods.QueryInformationJobObject(_jobObjectHandle, - JOB_OBJECT_BASIC_PROCESS_ID_LIST, - ref JobList, dwSize, IntPtr.Zero)) - { - if (JobList.NumberOfAssignedProcess == 0) + // Otherwise check if command is an Application to resolve executable full path with extension + // e.g if powershell was given, resolve to powershell.exe to get verbs + using var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); + + var commandInfo = new CmdletInfo("Get-Command", typeof(GetCommandCommand)); + + ps.AddCommand(commandInfo); + ps.AddParameter("Name", filePath); + ps.AddParameter("CommandType", CommandTypes.Application); + + Collection commands = ps.Invoke(); + + // Start-Process & Get-Command select first found application based on PATHEXT environment variable + if (commands.Count >= 1) { - emptyJobAutoEvent.Set(); + return CompleteFileVerbs(wordToComplete, filePath: commands[0].Source); } } + + return Array.Empty(); } /// - /// 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. + /// Completes file verbs. /// - /// - /// WaitHandle to use for waiting on the job object. - /// - internal void WaitOne(ManualResetEvent waitHandleToUse) - { - TimerCallback jobObjectStatusCb = this.CheckJobStatus; - using (Timer stateTimer = new(jobObjectStatusCb, waitHandleToUse, 0, 1000)) - { - waitHandleToUse.WaitOne(); - } - } + /// The word to complete. + /// The file path to get verbs. + /// List of file verbs to complete. + private static IEnumerable CompleteFileVerbs(string wordToComplete, string filePath) + => CompletionHelpers.GetMatchingResults( + wordToComplete, + possibleCompletionValues: new ProcessStartInfo(filePath).Verbs); } +#if !UNIX /// - /// 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. + /// ProcessInformation is a helper class that wraps the native PROCESS_INFORMATION structure + /// returned by CreateProcess or CreateProcessWithLogon. It ensures the process and thread + /// HANDLEs are disposed once it's not needed. /// - [StructLayout(LayoutKind.Sequential)] - internal struct JOBOBJECT_BASIC_PROCESS_ID_LIST + internal sealed class ProcessInformation : IDisposable { - /// - /// The number of process identifiers to be stored in ProcessIdList. - /// - public uint NumberOfAssignedProcess; + public SafeProcessHandle Process { get; } - /// - /// 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. - /// - public uint NumberOfProcessIdsInList; + public SafeProcessHandle Thread { get; } - /// - /// A variable-length array of process identifiers returned by this call. - /// Array elements 0 through NumberOfProcessIdsInList minus 1 - /// contain valid process identifiers. - /// - public IntPtr ProcessIdList; + public Int32 ProcessId { get; } + + public Int32 ThreadId { get; } + + internal ProcessInformation(ProcessNativeMethods.PROCESS_INFORMATION info) + { + Process = new(info.hProcess, true); + Thread = new(info.hThread, true); + ProcessId = info.dwProcessId; + ThreadId = info.dwThreadId; + } + + public void Resume() + { + ProcessNativeMethods.ResumeThread(Thread.DangerousGetHandle()); + } + + public void Dispose() + { + Process.Dispose(); + Thread.Dispose(); + GC.SuppressFinalize(this); + } + + ~ProcessInformation() => Dispose(); } internal static class ProcessNativeMethods { - // Fields - internal static readonly UInt32 GENERIC_READ = 0x80000000; - internal static readonly UInt32 GENERIC_WRITE = 0x40000000; - internal static readonly UInt32 FILE_ATTRIBUTE_NORMAL = 0x80000000; - internal static readonly UInt32 CREATE_ALWAYS = 2; - internal static readonly UInt32 FILE_SHARE_WRITE = 0x00000002; - internal static readonly UInt32 FILE_SHARE_READ = 0x00000001; - internal static readonly UInt32 OF_READWRITE = 0x00000002; - internal static readonly UInt32 OPEN_EXISTING = 3; - - // Methods - [DllImport(PinvokeDllNames.GetStdHandleDllName, SetLastError = true)] public static extern IntPtr GetStdHandle(int whichHandle); @@ -2752,7 +2797,7 @@ internal static extern bool CreateProcessWithLogonW(string userName, IntPtr environmentBlock, [MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory, STARTUPINFO lpStartupInfo, - SafeNativeMethods.PROCESS_INFORMATION lpProcessInformation); + ref PROCESS_INFORMATION lpProcessInformation); [DllImport(PinvokeDllNames.CreateProcessDllName, CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] @@ -2765,22 +2810,11 @@ public static extern bool CreateProcess([MarshalAs(UnmanagedType.LPWStr)] string IntPtr lpEnvironment, [MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory, STARTUPINFO lpStartupInfo, - SafeNativeMethods.PROCESS_INFORMATION lpProcessInformation); + ref PROCESS_INFORMATION lpProcessInformation); [DllImport(PinvokeDllNames.ResumeThreadDllName, CharSet = CharSet.Unicode, SetLastError = true)] public static extern uint ResumeThread(IntPtr threadHandle); - [DllImport(PinvokeDllNames.CreateFileDllName, CharSet = CharSet.Unicode, SetLastError = true)] - public static extern FileNakedHandle CreateFileW( - [In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName, - DWORD dwDesiredAccess, - DWORD dwShareMode, - ProcessNativeMethods.SECURITY_ATTRIBUTES lpSecurityAttributes, - DWORD dwCreationDisposition, - DWORD dwFlagsAndAttributes, - System.IntPtr hTemplateFile - ); - [DllImport("userenv.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit); @@ -2796,6 +2830,15 @@ internal enum LogonFlags LOGON_WITH_PROFILE = 1 } + [StructLayout(LayoutKind.Sequential)] + internal struct PROCESS_INFORMATION + { + public IntPtr hProcess; + public IntPtr hThread; + public int dwProcessId; + public int dwThreadId; + } + [StructLayout(LayoutKind.Sequential)] internal class SECURITY_ATTRIBUTES { @@ -2898,72 +2941,6 @@ public void Dispose() } } } - - internal static class SafeNativeMethods - { - [DllImport(PinvokeDllNames.CloseHandleDllName, SetLastError = true, ExactSpelling = true)] - public static extern bool CloseHandle(IntPtr handle); - - [StructLayout(LayoutKind.Sequential)] - internal class PROCESS_INFORMATION - { - public IntPtr hProcess; - public IntPtr hThread; - public int dwProcessId; - public int dwThreadId; - - public PROCESS_INFORMATION() - { - this.hProcess = IntPtr.Zero; - this.hThread = IntPtr.Zero; - } - - /// - /// Dispose. - /// - public void Dispose() - { - Dispose(true); - } - - /// - /// Dispose. - /// - /// - private void Dispose(bool disposing) - { - if (disposing) - { - if (this.hProcess != IntPtr.Zero) - { - CloseHandle(this.hProcess); - this.hProcess = IntPtr.Zero; - } - - if (this.hThread != IntPtr.Zero) - { - CloseHandle(this.hThread); - this.hThread = IntPtr.Zero; - } - } - } - } - } - - [SuppressUnmanagedCodeSecurity] - internal sealed class SafeJobHandle : SafeHandleZeroOrMinusOneIsInvalid - { - internal SafeJobHandle(IntPtr jobHandle) - : base(true) - { - base.SetHandle(jobHandle); - } - - protected override bool ReleaseHandle() - { - return SafeNativeMethods.CloseHandle(base.handle); - } - } #endif #endregion @@ -2971,7 +2948,6 @@ protected override bool ReleaseHandle() /// /// Non-terminating errors occurring in the process noun commands. /// - [Serializable] public class ProcessCommandException : SystemException { #region ctors @@ -3011,29 +2987,14 @@ public ProcessCommandException(string message, Exception innerException) /// /// /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ProcessCommandException( SerializationInfo info, StreamingContext context) - : base(info, context) { - _processName = info.GetString("ProcessName"); + throw new NotSupportedException(); } - /// - /// Serializer. - /// - /// Serialization information. - /// Streaming context. - public override void GetObjectData( - SerializationInfo info, - StreamingContext context) - { - base.GetObjectData(info, context); - if (info == null) - throw new ArgumentNullException(nameof(info)); - - info.AddValue("ProcessName", _processName); - } #endregion Serialization #region Properties diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/RegisterWMIEventCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/RegisterWMIEventCommand.cs index 0a4d9c6cb34..0d547abfa9d 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/RegisterWMIEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/RegisterWMIEventCommand.cs @@ -33,7 +33,7 @@ public class RegisterWmiEventCommand : ObjectEventRegistrationBase /// The credential to use. /// [Parameter] - [Credential()] + [Credential] public PSCredential Credential { get; set; } /// @@ -91,7 +91,7 @@ private string GetScopeString(string computer, string namespaceParameter) { StringBuilder returnValue = new StringBuilder("\\\\"); returnValue.Append(computer); - returnValue.Append("\\"); + returnValue.Append('\\'); returnValue.Append(namespaceParameter); return returnValue.ToString(); } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs index fcc9aef195f..3d1b66933d2 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs @@ -8,8 +8,8 @@ namespace Microsoft.PowerShell.Commands { /// - /// A command to resolve MSH paths containing glob characters to - /// MSH paths that match the glob strings. + /// A command to resolve PowerShell paths containing glob characters to + /// PowerShell paths that match the glob strings. /// [Cmdlet(VerbsDiagnostic.Resolve, "Path", DefaultParameterSetName = "Path", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097143")] @@ -59,7 +59,8 @@ public string[] LiteralPath /// Gets or sets the value that determines if the resolved path should /// be resolved to its relative version. /// - [Parameter()] + [Parameter(ParameterSetName = "Path")] + [Parameter(ParameterSetName = "LiteralPath")] public SwitchParameter Relative { get @@ -75,6 +76,33 @@ public SwitchParameter Relative private SwitchParameter _relative; + /// + /// Gets or sets the path the resolved relative path should be based off. + /// + [Parameter] + public string RelativeBasePath + { + get + { + return _relativeBasePath; + } + + set + { + _relativeBasePath = value; + } + } + + /// + /// Gets or sets the force property. + /// + [Parameter] + public override SwitchParameter Force + { + get => base.Force; + set => base.Force = value; + } + #endregion Parameters #region parameter data @@ -84,12 +112,68 @@ public SwitchParameter Relative /// private string[] _paths; + private PSDriveInfo _relativeDrive; + private string _relativeBasePath; + #endregion parameter data #region Command code /// - /// Resolves the path containing glob characters to the MSH paths that it + /// Finds the path and drive that should be used for relative path resolution + /// represents. + /// + protected override void BeginProcessing() + { + if (!string.IsNullOrEmpty(RelativeBasePath)) + { + try + { + _relativeBasePath = SessionState.Internal.Globber.GetProviderPath(RelativeBasePath, CmdletProviderContext, out _, out _relativeDrive); + } + catch (ProviderNotFoundException providerNotFound) + { + ThrowTerminatingError( + new ErrorRecord( + providerNotFound.ErrorRecord, + providerNotFound)); + } + catch (DriveNotFoundException driveNotFound) + { + ThrowTerminatingError( + new ErrorRecord( + driveNotFound.ErrorRecord, + driveNotFound)); + } + catch (ProviderInvocationException providerInvocation) + { + ThrowTerminatingError( + new ErrorRecord( + providerInvocation.ErrorRecord, + providerInvocation)); + } + catch (NotSupportedException notSupported) + { + ThrowTerminatingError( + new ErrorRecord(notSupported, "ProviderIsNotNavigationCmdletProvider", ErrorCategory.InvalidArgument, RelativeBasePath)); + } + catch (InvalidOperationException invalidOperation) + { + ThrowTerminatingError( + new ErrorRecord(invalidOperation, "InvalidHomeLocation", ErrorCategory.InvalidOperation, RelativeBasePath)); + } + + return; + } + else if (_relative) + { + _relativeDrive = SessionState.Path.CurrentLocation.Drive; + _relativeBasePath = SessionState.Path.CurrentLocation.ProviderPath; + } + } + + /// + /// Resolves the path containing glob characters to the PowerShell paths that it /// represents. /// protected override void ProcessRecord() @@ -99,25 +183,53 @@ protected override void ProcessRecord() Collection result = null; try { - result = SessionState.Path.GetResolvedPSPathFromPSPath(path, CmdletProviderContext); + if (MyInvocation.BoundParameters.ContainsKey("RelativeBasePath")) + { + // Pushing and popping the location is done because GetResolvedPSPathFromPSPath uses the current path to resolve relative paths. + // It's important that we pop the location before writing an object to the pipeline to avoid affecting downstream commands. + try + { + SessionState.Path.PushCurrentLocation(string.Empty); + _ = SessionState.Path.SetLocation(_relativeBasePath); + result = SessionState.Path.GetResolvedPSPathFromPSPath(path, CmdletProviderContext); + } + finally + { + _ = SessionState.Path.PopLocation(string.Empty); + } + } + else + { + result = SessionState.Path.GetResolvedPSPathFromPSPath(path, CmdletProviderContext); + } if (_relative) { + ReadOnlySpan baseCache = null; + ReadOnlySpan adjustedBaseCache = null; foreach (PathInfo currentPath in result) { // When result path and base path is on different PSDrive // (../)*path should not go beyond the root of base path - if (currentPath.Drive != SessionState.Path.CurrentLocation.Drive && - SessionState.Path.CurrentLocation.Drive != null && - !currentPath.ProviderPath.StartsWith( - SessionState.Path.CurrentLocation.Drive.Root, StringComparison.OrdinalIgnoreCase)) + if (currentPath.Drive != _relativeDrive && + _relativeDrive != null && + !currentPath.ProviderPath.StartsWith(_relativeDrive.Root, StringComparison.OrdinalIgnoreCase)) { WriteObject(currentPath.Path, enumerateCollection: false); continue; } - string adjustedPath = SessionState.Path.NormalizeRelativePath(currentPath.Path, - SessionState.Path.CurrentLocation.ProviderPath); + int leafIndex = currentPath.Path.LastIndexOf(currentPath.Provider.ItemSeparator); + var basePath = currentPath.Path.AsSpan(0, leafIndex); + if (basePath == baseCache) + { + WriteObject(string.Concat(adjustedBaseCache, currentPath.Path.AsSpan(leafIndex + 1)), enumerateCollection: false); + continue; + } + + baseCache = basePath; + string adjustedPath = SessionState.Path.NormalizeRelativePath(currentPath.Path, _relativeBasePath); + // Do not insert './' if result path is not relative if (!adjustedPath.StartsWith( currentPath.Drive?.Root ?? currentPath.Path, StringComparison.OrdinalIgnoreCase) && @@ -126,6 +238,9 @@ protected override void ProcessRecord() adjustedPath = SessionState.Path.Combine(".", adjustedPath); } + leafIndex = adjustedPath.LastIndexOf(currentPath.Provider.ItemSeparator); + adjustedBaseCache = adjustedPath.AsSpan(0, leafIndex + 1); + WriteObject(adjustedPath, enumerateCollection: false); } } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index 82210f627ce..739baa15bab 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -103,7 +103,7 @@ internal void WriteNonTerminatingError( string message = StringUtil.Format(errorMessage, serviceName, displayName, - (innerException == null) ? string.Empty : innerException.Message); + (innerException == null) ? category.ToString() : innerException.Message); var exception = new ServiceCommandException(message, innerException); exception.ServiceName = serviceName; @@ -610,146 +610,163 @@ public string[] Name /// 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 /// /// Adds UserName, Description, BinaryPathName, DelayedAutoStart and StartupType to a ServiceController object. /// + /// Handle to the local SCManager instance. /// /// ServiceController as PSObject with UserName, Description and StartupType added. - private PSObject AddProperties(ServiceController service) + private static 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(hService, NativeMethods.SERVICE_CONFIG_DESCRIPTION, out description); - - NativeMethods.SERVICE_DELAYED_AUTO_START_INFO autostartInfo = new(); - querySuccessful = querySuccessful && NativeMethods.QueryServiceConfig2(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; + } - 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; + } - if (!querySuccessful) - { - WriteNonTerminatingError( - service: service, - innerException: null, - errorId: "CouldNotGetServiceInfo", - errorMessage: ServiceResources.CouldNotGetServiceInfo, - category: ErrorCategory.PermissionDenied - ); + 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); + } + } } - - 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 @@ -888,7 +905,7 @@ internal bool DoWaitForStatus( /// This will start the service. /// /// Service to start. - /// True iff the service was started. + /// True if-and-only-if the service was started. internal bool DoStartService(ServiceController serviceController) { Exception exception = null; @@ -903,8 +920,7 @@ internal bool DoStartService(ServiceController serviceController) } catch (InvalidOperationException e) { - Win32Exception eInner = e.InnerException as Win32Exception; - if (eInner == null + if (e.InnerException is not Win32Exception eInner || eInner.NativeErrorCode != NativeMethods.ERROR_SERVICE_ALREADY_RUNNING) { exception = e; @@ -945,7 +961,7 @@ internal bool DoStartService(ServiceController serviceController) /// Service to stop. /// Stop dependent services. /// - /// True iff the service was stopped. + /// True if-and-only-if the service was stopped. internal List DoStopService(ServiceController serviceController, bool force, bool waitForServiceToStop) { // Ignore ServiceController.CanStop. CanStop will be set false @@ -1020,9 +1036,7 @@ internal List DoStopService(ServiceController serviceControll } catch (InvalidOperationException e) { - Win32Exception eInner = - e.InnerException as Win32Exception; - if (eInner == null + if (e.InnerException is not Win32Exception eInner || eInner.NativeErrorCode != NativeMethods.ERROR_SERVICE_NOT_ACTIVE) { exception = e; @@ -1078,7 +1092,7 @@ internal List DoStopService(ServiceController serviceControll /// private static bool HaveAllDependentServicesStopped(ServiceController[] dependentServices) { - return Array.TrueForAll(dependentServices, service => service.Status == ServiceControllerStatus.Stopped); + return Array.TrueForAll(dependentServices, static service => service.Status == ServiceControllerStatus.Stopped); } /// @@ -1097,7 +1111,7 @@ internal void RemoveNotStoppedServices(List services) /// This will pause the service. /// /// Service to pause. - /// True iff the service was paused. + /// True if-and-only-if the service was paused. internal bool DoPauseService(ServiceController serviceController) { Exception exception = null; @@ -1117,8 +1131,7 @@ internal bool DoPauseService(ServiceController serviceController) } catch (InvalidOperationException e) { - Win32Exception eInner = e.InnerException as Win32Exception; - if (eInner != null + if (e.InnerException is Win32Exception eInner && eInner.NativeErrorCode == NativeMethods.ERROR_SERVICE_NOT_ACTIVE) { serviceNotRunning = true; @@ -1178,7 +1191,7 @@ internal bool DoPauseService(ServiceController serviceController) /// This will resume the service. /// /// Service to resume. - /// True iff the service was resumed. + /// True if-and-only-if the service was resumed. internal bool DoResumeService(ServiceController serviceController) { Exception exception = null; @@ -1198,8 +1211,7 @@ internal bool DoResumeService(ServiceController serviceController) } catch (InvalidOperationException e) { - Win32Exception eInner = e.InnerException as Win32Exception; - if (eInner != null + if (e.InnerException is Win32Exception eInner && eInner.NativeErrorCode == NativeMethods.ERROR_SERVICE_NOT_ACTIVE) { serviceNotRunning = true; @@ -1680,7 +1692,6 @@ public string Status #region Overrides /// /// - [ArchitectureSensitive] protected override void ProcessRecord() { ServiceController service = null; @@ -1756,10 +1767,14 @@ protected override void ProcessRecord() return; } + var access = NativeMethods.SERVICE_CHANGE_CONFIG; + if (!string.IsNullOrEmpty(SecurityDescriptorSddl)) + access |= NativeMethods.WRITE_DAC | NativeMethods.WRITE_OWNER; + hService = NativeMethods.OpenServiceW( hScManager, Name, - NativeMethods.SERVICE_CHANGE_CONFIG | NativeMethods.WRITE_DAC | NativeMethods.WRITE_OWNER + access ); if (hService == IntPtr.Zero) @@ -2111,7 +2126,6 @@ public string[] DependsOn /// /// Create the service. /// - [ArchitectureSensitive] protected override void BeginProcessing() { ServiceController service = null; @@ -2353,7 +2367,7 @@ protected override void BeginProcessing() /// /// This class implements the Remove-Service command. /// - [Cmdlet(VerbsCommon.Remove, "Service", SupportsShouldProcess = true, DefaultParameterSetName = "Name")] + [Cmdlet(VerbsCommon.Remove, "Service", SupportsShouldProcess = true, DefaultParameterSetName = "Name", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2248980")] public class RemoveServiceCommand : ServiceBaseCommand { #region Parameters @@ -2380,7 +2394,6 @@ public class RemoveServiceCommand : ServiceBaseCommand /// /// Remove the service. /// - [ArchitectureSensitive] protected override void ProcessRecord() { ServiceController service = null; @@ -2523,7 +2536,6 @@ protected override void ProcessRecord() /// /// Non-terminating errors occurring in the service noun commands. /// - [Serializable] public class ServiceCommandException : SystemException { #region ctors @@ -2565,31 +2577,12 @@ public ServiceCommandException(string message, Exception innerException) /// /// /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8, hence this method is now marked as obsolete", DiagnosticId = "SYSLIB0051")] protected ServiceCommandException(SerializationInfo info, StreamingContext context) - : base(info, context) { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - _serviceName = info.GetString("ServiceName"); + throw new NotSupportedException(); } - /// - /// Serializer. - /// - /// Serialization information. - /// Streaming context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - base.GetObjectData(info, context); - info.AddValue("ServiceName", _serviceName); - } #endregion Serialization #region Properties @@ -2761,69 +2754,6 @@ bool SetServiceObjectSecurity( byte[] lpSecurityDescriptor ); - /// - /// CreateJobObject API creates or opens a job object. - /// - /// - /// 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. - /// - /// - /// The name of the job. - /// - /// - /// 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. - /// - [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)] - internal static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string lpName); - - /// - /// AssignProcessToJobObject API is used to assign a process to an existing job object. - /// - /// - /// A handle to the job object to which the process will be associated. - /// - /// - /// A handle to the process to associate with the job object. - /// - /// If the function succeeds, the return value is nonzero. - /// If the function fails, the return value is zero. - /// - [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool AssignProcessToJobObject(SafeHandle hJob, IntPtr hProcess); - - /// - /// Retrieves job state information from the job object. - /// - /// - /// A handle to the job whose information is being queried. - /// - /// - /// The information class for the limits to be queried. - /// - /// - /// The limit or job state information. - /// - /// - /// The count of the job information being queried, in bytes. - /// - /// - /// A pointer to a variable that receives the length of - /// data written to the structure pointed to by the lpJobObjectInfo parameter. - /// - /// If the function succeeds, the return value is nonzero. - /// If the function fails, the return value is zero. - /// - [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; @@ -2960,20 +2890,20 @@ internal static ServiceStartupType GetServiceStartupType(ServiceStartMode startM #endregion NativeMethods #region ServiceStartupType - /// - ///Enum for usage with StartupType. Automatic, Manual and Disabled index matched from System.ServiceProcess.ServiceStartMode - /// + /// + /// Enum for usage with StartupType. Automatic, Manual and Disabled index matched from System.ServiceProcess.ServiceStartMode + /// public enum ServiceStartupType { - ///Invalid service + /// Invalid service InvalidValue = -1, - ///Automatic service + /// Automatic service Automatic = 2, - ///Manual service + /// Manual service Manual = 3, - ///Disabled service + /// Disabled service Disabled = 4, - ///Automatic (Delayed Start) service + /// Automatic (Delayed Start) service AutomaticDelayedStart = 10 } #endregion ServiceStartupType diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/SetClipboardCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/SetClipboardCommand.cs index b53ffeb01b3..49ab5d768f6 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/SetClipboardCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/SetClipboardCommand.cs @@ -17,6 +17,7 @@ namespace Microsoft.PowerShell.Commands /// [Cmdlet(VerbsCommon.Set, "Clipboard", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Medium, HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2109826")] [Alias("scb")] + [OutputType(typeof(string))] public class SetClipboardCommand : PSCmdlet { private readonly List _contentList = new(); @@ -37,6 +38,19 @@ public class SetClipboardCommand : PSCmdlet [Parameter] public SwitchParameter Append { get; set; } + /// + /// Gets or sets if the values sent down the pipeline. + /// + [Parameter] + public SwitchParameter PassThru { get; set; } + + /// + /// Gets or sets whether to use OSC52 escape sequence to set the clipboard of host instead of target. + /// + [Parameter] + [Alias("ToLocalhost")] + public SwitchParameter AsOSC52 { get; set; } + /// /// This method implements the BeginProcessing method for Set-Clipboard command. /// @@ -53,6 +67,11 @@ protected override void ProcessRecord() if (Value != null) { _contentList.AddRange(Value); + + if (PassThru) + { + WriteObject(Value); + } } } @@ -117,8 +136,28 @@ private void SetClipboardContent(List contentList, bool append) if (ShouldProcess(setClipboardShouldProcessTarget, "Set-Clipboard")) { - Clipboard.SetText(content.ToString()); + SetClipboardContent(content.ToString()); } } + + /// + /// Set the clipboard content. + /// + /// The content to store into the clipboard. + private void SetClipboardContent(string content) + { + if (!AsOSC52) + { + Clipboard.SetText(content); + return; + } + + var bytes = System.Text.Encoding.UTF8.GetBytes(content); + var encoded = System.Convert.ToBase64String(bytes); + var osc = $"\u001B]52;;{encoded}\u0007"; + + var message = new HostInformationMessage { Message = osc, NoNewLine = true }; + WriteInformation(message, new string[] { "PSHOST" }); + } } } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/StartTransactionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/StartTransactionCommand.cs index 4f5c239cc64..6c178626d96 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/StartTransactionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/StartTransactionCommand.cs @@ -18,7 +18,7 @@ public class StartTransactionCommand : PSCmdlet /// The time, in minutes, before this transaction is rolled back /// automatically. /// - [Parameter()] + [Parameter] [Alias("TimeoutMins")] public int Timeout { @@ -47,7 +47,7 @@ public int Timeout /// Gets or sets the flag to determine if this transaction can /// be committed or rolled back independently of other transactions. /// - [Parameter()] + [Parameter] public SwitchParameter Independent { get { return _independent; } @@ -60,7 +60,7 @@ public SwitchParameter Independent /// /// Gets or sets the rollback preference for this transaction. /// - [Parameter()] + [Parameter] public RollbackSeverity RollbackPreference { get { return _rollbackPreference; } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index e6b33142457..17c9ab9a5d3 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -27,6 +27,7 @@ namespace Microsoft.PowerShell.Commands [OutputType(typeof(PingMtuStatus), ParameterSetName = new string[] { MtuSizeDetectParameterSet })] [OutputType(typeof(int), ParameterSetName = new string[] { MtuSizeDetectParameterSet })] [OutputType(typeof(TraceStatus), ParameterSetName = new string[] { TraceRouteParameterSet })] + [OutputType(typeof(TcpPortStatus), ParameterSetName = new string[] { TcpPortParameterSet })] public class TestConnectionCommand : PSCmdlet, IDisposable { #region Parameter Set Names @@ -55,7 +56,7 @@ public class TestConnectionCommand : PSCmdlet, IDisposable #region Private Fields - private static byte[]? s_DefaultSendBuffer; + private static readonly byte[] s_DefaultSendBuffer = Array.Empty(); private readonly CancellationTokenSource _dnsLookupCancel = new(); @@ -134,6 +135,7 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// The default (from Windows) is 4 times. /// [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = TcpPortParameterSet)] [ValidateRange(ValidateRangeKind.Positive)] public int Count { get; set; } = 4; @@ -143,6 +145,7 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// [Parameter(ParameterSetName = DefaultPingParameterSet)] [Parameter(ParameterSetName = RepeatPingParameterSet)] + [Parameter(ParameterSetName = TcpPortParameterSet)] [ValidateRange(ValidateRangeKind.Positive)] public int Delay { get; set; } = 1; @@ -169,6 +172,7 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// Gets or sets whether to continue pinging until user presses Ctrl-C (or Int.MaxValue threshold reached). /// [Parameter(Mandatory = true, ParameterSetName = RepeatPingParameterSet)] + [Parameter(ParameterSetName = TcpPortParameterSet)] [Alias("Continuous")] public SwitchParameter Repeat { get; set; } @@ -180,6 +184,13 @@ public class TestConnectionCommand : PSCmdlet, IDisposable [Parameter] public SwitchParameter Quiet { get; set; } + /// + /// Gets or sets whether to enable detailed output mode while running a TCP connection test. + /// Without this flag, the TCP test will return a boolean result. + /// + [Parameter(ParameterSetName = TcpPortParameterSet)] + public SwitchParameter Detailed; + /// /// Gets or sets the timeout value for an individual ping in seconds. /// If a response is not received in this time, no response is assumed. @@ -227,6 +238,7 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// /// BeginProcessing implementation for TestConnectionCommand. + /// Sets Count for different types of tests unless specified explicitly. /// protected override void BeginProcessing() { @@ -235,6 +247,9 @@ protected override void BeginProcessing() case RepeatPingParameterSet: Count = int.MaxValue; break; + case TcpPortParameterSet: + SetCountForTcpTest(); + break; } } @@ -281,6 +296,18 @@ protected override void StopProcessing() #region ConnectionTest + private void SetCountForTcpTest() + { + if (Repeat.IsPresent) + { + Count = int.MaxValue; + } + else if (!MyInvocation.BoundParameters.ContainsKey(nameof(Count))) + { + Count = 1; + } + } + private void ProcessConnectionByTCPPort(string targetNameOrAddress) { if (!TryResolveNameOrAddress(targetNameOrAddress, out _, out IPAddress? targetAddress)) @@ -293,42 +320,80 @@ private void ProcessConnectionByTCPPort(string targetNameOrAddress) return; } - TcpClient client = new(); + int timeoutMilliseconds = TimeoutSeconds * 1000; + int delayMilliseconds = Delay * 1000; - try + for (var i = 1; i <= Count; i++) { - Task connectionTask = client.ConnectAsync(targetAddress, TcpPort); - string targetString = targetAddress.ToString(); + long latency = 0; + SocketError status = SocketError.SocketError; + + Stopwatch stopwatch = new Stopwatch(); + + using var client = new TcpClient(); - for (var i = 1; i <= TimeoutSeconds; i++) + try { - Task timeoutTask = Task.Delay(millisecondsDelay: 1000); - Task.WhenAny(connectionTask, timeoutTask).Result.Wait(); + stopwatch.Start(); - if (timeoutTask.Status == TaskStatus.Faulted || timeoutTask.Status == TaskStatus.Canceled) + if (client.ConnectAsync(targetAddress, TcpPort).Wait(timeoutMilliseconds, _dnsLookupCancel.Token)) { - // Waiting is interrupted by Ctrl-C. - WriteObject(false); - return; + latency = stopwatch.ElapsedMilliseconds; + status = SocketError.Success; } - - if (connectionTask.Status == TaskStatus.RanToCompletion) + else { - WriteObject(true); - return; + status = SocketError.TimedOut; } } - } - catch - { - // Silently ignore connection errors. - } - finally - { - client.Close(); - } + catch (AggregateException ae) + { + ae.Handle((ex) => + { + if (ex is TaskCanceledException) + { + throw new PipelineStoppedException(); + } + if (ex is SocketException socketException) + { + status = socketException.SocketErrorCode; + return true; + } + else + { + return false; + } + }); + } + finally + { + stopwatch.Reset(); + } - WriteObject(false); + if (!Detailed.IsPresent) + { + WriteObject(status == SocketError.Success); + return; + } + else + { + WriteObject(new TcpPortStatus( + i, + Source, + targetNameOrAddress, + targetAddress, + TcpPort, + latency, + status == SocketError.Success, + status + )); + } + + if (i < Count) + { + Task.Delay(delayMilliseconds).Wait(_dnsLookupCancel.Token); + } + } } #endregion ConnectionTest @@ -419,7 +484,10 @@ private void ProcessTraceroute(string targetNameOrAddress) reply.Status == IPStatus.Success ? reply.RoundtripTime : timer.ElapsedMilliseconds, - buffer.Length, + + // If we use the empty buffer, then .NET actually uses a 32 byte buffer so we want to show + // as the result object the actual buffer size used instead of 0. + buffer.Length == 0 ? DefaultSendBufferSize : buffer.Length, pingNum: i); WriteObject(new TraceStatus( currentHop, @@ -497,6 +565,8 @@ private void ProcessMTUSize(string targetNameOrAddress) int LowMTUSize = targetAddress.AddressFamily == AddressFamily.InterNetworkV6 ? 1280 : 68; int timeout = TimeoutSeconds * 1000; + PingReply? timeoutReply = null; + try { PingOptions pingOptions = new(MaxHops, true); @@ -517,6 +587,7 @@ private void ProcessMTUSize(string targetNameOrAddress) if (reply.Status == IPStatus.PacketTooBig || reply.Status == IPStatus.TimedOut) { HighMTUSize = CurrentMTUSize; + timeoutReply = reply; retry = 1; } else if (reply.Status == IPStatus.Success) @@ -575,11 +646,32 @@ private void ProcessMTUSize(string targetNameOrAddress) } else { - WriteObject(new PingMtuStatus( - Source, - resolvedTargetName, - replyResult ?? throw new ArgumentNullException(nameof(replyResult)), - CurrentMTUSize)); + if (replyResult is null) + { + if (timeoutReply is not null) + { + Exception timeoutException = new TimeoutException(targetAddress.ToString()); + ErrorRecord errorRecord = new( + timeoutException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + timeoutReply); + WriteError(errorRecord); + } + else + { + ArgumentNullException.ThrowIfNull(replyResult); + } + } + else + { + WriteObject(new PingMtuStatus( + Source, + resolvedTargetName, + replyResult, + CurrentMTUSize)); + } + } } @@ -640,7 +732,7 @@ private void ProcessPing(string targetNameOrAddress) resolvedTargetName, reply, reply.RoundtripTime, - buffer.Length, + buffer.Length == 0 ? DefaultSendBufferSize : buffer.Length, pingNum: (uint)i)); } @@ -795,7 +887,7 @@ private IPHostEntry GetCancellableHostEntry(string targetNameOrAddress) // Creates and fills a send buffer. This follows the ping.exe and CoreFX model. private static byte[] GetSendBuffer(int bufferSize) { - if (bufferSize == DefaultSendBufferSize && s_DefaultSendBuffer != null) + if (bufferSize == DefaultSendBufferSize) { return s_DefaultSendBuffer; } @@ -807,11 +899,6 @@ private static byte[] GetSendBuffer(int bufferSize) sendBuffer[i] = (byte)((int)'a' + i % 23); } - if (bufferSize == DefaultSendBufferSize && s_DefaultSendBuffer == null) - { - s_DefaultSendBuffer = sendBuffer; - } - return sendBuffer; } @@ -875,6 +962,75 @@ private PingReply SendCancellablePing( } } + /// + /// The class contains information about the TCP connection test. + /// + public class TcpPortStatus + { + /// + /// Initializes a new instance of the class. + /// + /// The number of this test. + /// The source machine name or IP of the test. + /// The target machine name or IP of the test. + /// The resolved IP from the target. + /// The port used for the connection. + /// The latency of the test. + /// If the test connection succeeded. + /// Status of the underlying socket. + internal TcpPortStatus(int id, string source, string target, IPAddress targetAddress, int port, long latency, bool connected, SocketError status) + { + Id = id; + Source = source; + Target = target; + TargetAddress = targetAddress; + Port = port; + Latency = latency; + Connected = connected; + Status = status; + } + + /// + /// Gets and sets the count of the test. + /// + public int Id { get; set; } + + /// + /// Gets the source from which the test was sent. + /// + public string Source { get; } + + /// + /// Gets the target name. + /// + public string Target { get; } + + /// + /// Gets the resolved address for the target. + /// + public IPAddress TargetAddress { get; } + + /// + /// Gets the port used for the test. + /// + public int Port { get; } + + /// + /// Gets or sets the latancy of the connection. + /// + public long Latency { get; set; } + + /// + /// Gets or sets the result of the test. + /// + public bool Connected { get; set; } + + /// + /// Gets or sets the state of the socket after the test. + /// + public SocketError Status { get; set; } + } + /// /// The class contains information about the source, the destination and ping results. /// diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/PingPathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestPathCommand.cs similarity index 87% rename from src/Microsoft.PowerShell.Commands.Management/commands/management/PingPathCommand.cs rename to src/Microsoft.PowerShell.Commands.Management/commands/management/TestPathCommand.cs index 9bebcd076ca..d8748b6ef9c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/PingPathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestPathCommand.cs @@ -136,7 +136,7 @@ internal override object GetDynamicParameters(CmdletProviderContext context) { object result = null; - if (this.PathType == TestPathType.Any && !IsValid) + if (!IsValid) { if (Path != null && Path.Length > 0 && Path[0] != null) { @@ -186,19 +186,21 @@ protected override void ProcessRecord() { bool result = false; - if (path == null) - { - WriteError(new ErrorRecord( - new ArgumentNullException(TestPathResources.PathIsNullOrEmptyCollection), - "NullPathNotPermitted", - ErrorCategory.InvalidArgument, - Path)); - continue; - } - if (string.IsNullOrWhiteSpace(path)) { - WriteObject(result); + if (path is null) + { + WriteError(new ErrorRecord( + new ArgumentNullException(TestPathResources.PathIsNullOrEmptyCollection), + "NullPathNotPermitted", + ErrorCategory.InvalidArgument, + Path)); + } + else + { + WriteObject(result); + } + continue; } @@ -210,19 +212,15 @@ protected override void ProcessRecord() } else { + result = InvokeProvider.Item.Exists(path, currentContext); + if (this.PathType == TestPathType.Container) { - result = InvokeProvider.Item.IsContainer(path, currentContext); + result &= InvokeProvider.Item.IsContainer(path, currentContext); } else if (this.PathType == TestPathType.Leaf) { - result = - InvokeProvider.Item.Exists(path, currentContext) && - !InvokeProvider.Item.IsContainer(path, currentContext); - } - else - { - result = InvokeProvider.Item.Exists(path, currentContext); + result &= !InvokeProvider.Item.IsContainer(path, currentContext); } } } @@ -245,6 +243,5 @@ protected override void ProcessRecord() } } #endregion Command code - } } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TimeZoneCommands.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TimeZoneCommands.cs index 69e8860f3ab..8fdb2c55acd 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TimeZoneCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TimeZoneCommands.cs @@ -17,6 +17,7 @@ namespace Microsoft.PowerShell.Commands /// [Cmdlet(VerbsCommon.Get, "TimeZone", DefaultParameterSetName = "Name", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096904")] + [OutputType(typeof(TimeZoneInfo))] [Alias("gtz")] public class GetTimeZoneCommand : PSCmdlet { @@ -121,6 +122,7 @@ protected override void ProcessRecord() SupportsShouldProcess = true, DefaultParameterSetName = "Name", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2097056")] + [OutputType(typeof(TimeZoneInfo))] [Alias("stz")] public class SetTimeZoneCommand : PSCmdlet { @@ -159,7 +161,7 @@ public class SetTimeZoneCommand : PSCmdlet #endregion Parameters /// - /// Implementation of the ProcessRecord method for Get-TimeZone. + /// Implementation of the ProcessRecord method for Set-TimeZone. /// [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "Since Name is not a parameter of this method, it confuses FXCop. It is the appropriate value for the exception.")] protected override void ProcessRecord() diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/WMIHelper.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/WMIHelper.cs index eed6efff76a..31670a7d632 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/WMIHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/WMIHelper.cs @@ -182,9 +182,9 @@ internal void RaiseOperationCompleteEvent(EventArgs baseEventArgs, OperationStat OperationComplete.SafeInvoke(this, operationStateEventArgs); } - /// + /// /// Raise WMI state changed event - /// + /// internal void RaiseWmiOperationState(EventArgs baseEventArgs, WmiState state) { WmiJobStateEventArgs wmiJobStateEventArgs = new WmiJobStateEventArgs(); @@ -992,16 +992,16 @@ private void ConnectGetWMI() } } - /// + /// /// Event which will be triggered when WMI state is changed. /// Currently it is to notify Jobs that state has changed to running. /// Other states are notified via OperationComplete. - /// + /// internal sealed class WmiJobStateEventArgs : EventArgs { - /// + /// /// WMI state - /// + /// internal WmiState WmiState { get; set; } } @@ -1042,7 +1042,7 @@ internal static string GetScopeString(string computer, string namespaceParameter { StringBuilder returnValue = new StringBuilder("\\\\"); returnValue.Append(computer); - returnValue.Append("\\"); + returnValue.Append('\\'); returnValue.Append(namespaceParameter); return returnValue.ToString(); } @@ -1120,7 +1120,7 @@ public class WmiBaseCmdlet : Cmdlet [Parameter(ParameterSetName = "WQLQuery")] [Parameter(ParameterSetName = "query")] [Parameter(ParameterSetName = "list")] - [Credential()] + [Credential] public PSCredential Credential { get; set; } /// @@ -1659,7 +1659,7 @@ private string ConstructLocation() foreach (PSWmiChildJob job in ChildJobs) { location.Append(job.Location); - location.Append(","); + location.Append(','); } location.Remove(location.Length - 1, 1); diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/WebServiceProxy.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/WebServiceProxy.cs index 547b121c571..3fc313fe222 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/WebServiceProxy.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/WebServiceProxy.cs @@ -475,7 +475,10 @@ private object InstantiateWebServiceProxy(Assembly assembly) break; } - if (proxyType != null) break; + if (proxyType != null) + { + break; + } } System.Management.Automation.Diagnostics.Assert( diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/WriteContentCommandBase.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/WriteContentCommandBase.cs index 25ecd672c4c..a2a6387e9da 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/WriteContentCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/WriteContentCommandBase.cs @@ -95,10 +95,7 @@ protected override void ProcessRecord() // Initialize the content - if (_content == null) - { - _content = Array.Empty(); - } + _content ??= Array.Empty(); if (_pipingPaths) { diff --git a/src/Microsoft.PowerShell.Commands.Management/resources/ComputerResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/ComputerResources.resx index 63a671c0248..95685239fd7 100644 --- a/src/Microsoft.PowerShell.Commands.Management/resources/ComputerResources.resx +++ b/src/Microsoft.PowerShell.Commands.Management/resources/ComputerResources.resx @@ -387,4 +387,7 @@ The {0} parameter is not supported for CoreCLR. + + The required native command 'shutdown' was not found. + diff --git a/src/Microsoft.PowerShell.Commands.Management/resources/ProcessResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/ProcessResources.resx index 65513e55f5f..b4c1ddbccf8 100644 --- a/src/Microsoft.PowerShell.Commands.Management/resources/ProcessResources.resx +++ b/src/Microsoft.PowerShell.Commands.Management/resources/ProcessResources.resx @@ -189,6 +189,9 @@ This command cannot be run completely because the system cannot find all the information required. + + Failed to retrieve the new process handle: "{0}". The Process object outputted may have some properties and methods that do not work properly. + This command cannot be run due to error 1783. The possible cause of this error can be using of a non-existing user "{0}". Please give a valid user and run your command again. @@ -204,9 +207,6 @@ Parameters "{0}" and "{1}" cannot be specified at the same time. - - The 'IncludeUserName' parameter requires elevated user rights. Try running the command again in a session that has been opened with elevated user rights (that is, Run as Administrator). - Cannot debug process "{0} ({1})" because of the following error: {2} diff --git a/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx index 3792ce8db99..e1213c3fb05 100644 --- a/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx +++ b/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx @@ -159,9 +159,6 @@ Service '{1} ({0})' cannot be configured due to the following error: {2} - - Service '{1} ({0})' cannot be queried due to the following error: {2} - Service '{1} ({0})' description cannot be configured due to the following error: {2} @@ -211,7 +208,7 @@ Service '{1} ({0})' resume failed. - Failed to configure the service '{1} ({0})' due to the following error: {2}. Run PowerShell as admin and run your command again. + Failed to open SCManager due to the following error: {0}. Run PowerShell as admin and run your command again. The startup type '{0}' is not supported by {1}. diff --git a/src/Microsoft.PowerShell.Commands.Management/singleshell/installer/MshManagementMshSnapin.cs b/src/Microsoft.PowerShell.Commands.Management/singleshell/installer/MshManagementMshSnapin.cs index 0b0239053ea..ed3495dd334 100644 --- a/src/Microsoft.PowerShell.Commands.Management/singleshell/installer/MshManagementMshSnapin.cs +++ b/src/Microsoft.PowerShell.Commands.Management/singleshell/installer/MshManagementMshSnapin.cs @@ -7,8 +7,8 @@ namespace Microsoft.PowerShell { /// - /// MshManagementMshSnapin (or MshManagementMshSnapinInstaller) is a class for facilitating registry - /// of necessary information for monad management mshsnapin. + /// PSManagementPSSnapIn is a class for facilitating registry + /// of necessary information for PowerShell management PSSnapin. /// /// This class will be built with monad management dll. /// @@ -24,7 +24,7 @@ public PSManagementPSSnapIn() } /// - /// Get name of this mshsnapin. + /// Get name of this PSSnapin. /// public override string Name { @@ -35,7 +35,7 @@ public override string Name } /// - /// Get the default vendor string for this mshsnapin. + /// Get the default vendor string for this PSSnapin. /// public override string Vendor { @@ -57,7 +57,7 @@ public override string VendorResource } /// - /// Get the default description string for this mshsnapin. + /// Get the default description string for this PSSnapin. /// public override string Description { 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 e923f6aa810..e989bb17e72 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,8 @@ - + + @@ -31,10 +32,10 @@ - - - - + + + + diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddMember.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddMember.cs index efb035b7785..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. /// [Parameter(Mandatory = true, Position = 0, ParameterSetName = NotePropertySingleMemberSet)] - [ValidateNotePropertyNameAttribute()] - [NotePropertyTransformationAttribute()] + [ValidateNotePropertyName] + [NotePropertyTransformation] [ValidateNotNullOrEmpty] public string NotePropertyName { @@ -524,7 +524,10 @@ private void UpdateTypeNames() // Respect the type shortcut Type type; string typeNameInUse = _typeName; - if (LanguagePrimitives.TryConvertTo(_typeName, out type)) { typeNameInUse = type.FullName; } + if (LanguagePrimitives.TryConvertTo(_typeName, out type)) + { + typeNameInUse = type.FullName; + } _inputObject.TypeNames.Insert(0, typeNameInUse); } @@ -557,9 +560,8 @@ private sealed class ValidateNotePropertyNameAttribute : ValidateArgumentsAttrib { protected override void Validate(object arguments, EngineIntrinsics engineIntrinsics) { - string notePropertyName = arguments as string; PSMemberTypes memberType; - if (notePropertyName != null && LanguagePrimitives.TryConvertTo(notePropertyName, out memberType)) + if (arguments is string notePropertyName && LanguagePrimitives.TryConvertTo(notePropertyName, out memberType)) { switch (memberType) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs index b86cadc1eff..7dc0a9c3556 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs @@ -11,7 +11,9 @@ using System.Linq; using System.Management.Automation; using System.Management.Automation.Internal; +using System.Management.Automation.Security; using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.Loader; using System.Security; using System.Text; @@ -108,7 +110,7 @@ public string[] MemberDefinition if (value != null) { - _sourceCode = string.Join("\n", value); + _sourceCode = string.Join('\n', value); } } } @@ -127,7 +129,7 @@ public string[] MemberDefinition /// Any using statements required by the auto-generated type. /// [Parameter(ParameterSetName = FromMemberParameterSetName)] - [ValidateNotNull()] + [ValidateNotNull] [Alias("Using")] public string[] UsingNamespace { get; set; } = Array.Empty(); @@ -299,7 +301,10 @@ public string[] ReferencedAssemblies set { - if (value != null) { _referencedAssemblies = value; } + if (value != null) + { + _referencedAssemblies = value; + } } } @@ -400,7 +405,7 @@ public string OutputAssembly /// /// Flag to pass the resulting types along. /// - [Parameter()] + [Parameter] public SwitchParameter PassThru { get; set; } /// @@ -549,15 +554,26 @@ private string GetUsingSet(Language language) /// protected override void BeginProcessing() { - // Prevent code compilation in ConstrainedLanguage mode - if (SessionState.LanguageMode == PSLanguageMode.ConstrainedLanguage) + // Prevent code compilation in ConstrainedLanguage mode, or NoLanguage mode under system lock down. + if (SessionState.LanguageMode == PSLanguageMode.ConstrainedLanguage || + (SessionState.LanguageMode == PSLanguageMode.NoLanguage && SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce)) { - ThrowTerminatingError( - new ErrorRecord( - new PSNotSupportedException(AddTypeStrings.CannotDefineNewType), - nameof(AddTypeStrings.CannotDefineNewType), - ErrorCategory.PermissionDenied, - targetObject: null)); + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + ThrowTerminatingError( + new ErrorRecord( + new PSNotSupportedException(AddTypeStrings.CannotDefineNewType), + nameof(AddTypeStrings.CannotDefineNewType), + ErrorCategory.PermissionDenied, + targetObject: null)); + } + + SystemPolicy.LogWDACAuditMessage( + context: Context, + title: AddTypeStrings.AddTypeLogTitle, + message: AddTypeStrings.AddTypeLogMessage, + fqid: "AddTypeCmdletDisabled", + dropIntoDebugger: true); } // 'ConsoleApplication' and 'WindowsApplication' types are currently not working in .NET Core @@ -646,8 +662,8 @@ protected override void EndProcessing() // These dictionaries prevent reloading already loaded and unchanged code. // We don't worry about unbounded growing of the cache because in .Net Core 2.0 we can not unload assemblies. // TODO: review if we will be able to unload assemblies after migrating to .Net Core 2.1. - private static readonly HashSet s_sourceTypesCache = new(); - private static readonly Dictionary s_sourceAssemblyCache = new(); + private static readonly ConcurrentDictionary s_sourceTypesCache = new(); + private static readonly ConcurrentDictionary s_sourceAssemblyCache = new(); private static readonly string s_defaultSdkDirectory = Utils.DefaultPowerShellAppBase; @@ -668,6 +684,7 @@ private void LoadAssemblies(IEnumerable 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) @@ -682,11 +699,10 @@ private void LoadAssemblies(IEnumerable assemblies) /// private static IEnumerable InitDefaultRefAssemblies() { - // Define number of reference assemblies distributed with PowerShell. - const int maxPowershellRefAssemblies = 160; - - const int capacity = maxPowershellRefAssemblies + 1; - var defaultRefAssemblies = new List(capacity); + // Default reference assemblies consist of .NET reference assemblies and the 'S.M.A' assembly. + // Today, there are 161 .NET reference assemblies, so the needed capacity is 162, but we use 200 + // as the initial capacity to cover the possible increase of .NET reference assemblies in future. + var defaultRefAssemblies = new List(capacity: 200); foreach (string file in Directory.EnumerateFiles(s_netcoreAppRefFolder, "*.dll", SearchOption.TopDirectoryOnly)) { @@ -696,11 +712,6 @@ private static IEnumerable InitDefaultRefAssemblies // Add System.Management.Automation.dll defaultRefAssemblies.Add(MetadataReference.CreateFromFile(typeof(PSObject).Assembly.Location)); - // We want to avoid reallocating the internal array, so we assert if the list capacity has increased. - Diagnostics.Assert( - defaultRefAssemblies.Capacity <= capacity, - $"defaultRefAssemblies was resized because of insufficient initial capacity! A capacity of {defaultRefAssemblies.Count} is required."); - return defaultRefAssemblies; } @@ -869,7 +880,10 @@ private IEnumerable GetPortableExecutableReferences var tempReferences = new List(s_autoReferencedAssemblies.Value); foreach (string assembly in ReferencedAssemblies) { - if (string.IsNullOrWhiteSpace(assembly)) { continue; } + if (string.IsNullOrWhiteSpace(assembly)) + { + continue; + } string resolvedAssemblyPath = ResolveAssemblyName(assembly, true); @@ -894,7 +908,14 @@ private IEnumerable GetPortableExecutableReferences private void WriteTypes(Assembly assembly) { - WriteObject(assembly.GetTypes(), true); + foreach (Type type in assembly.GetTypes()) + { + // We only write out types that are not auto-generated by compiler. + if (type.GetCustomAttribute() is null) + { + WriteObject(type); + } + } } #endregion LoadAssembly @@ -952,7 +973,7 @@ private CompilationOptions GetDefaultCompilationOptions() } } - private bool isSourceCodeUpdated(List syntaxTrees, out Assembly assembly) + private bool IsSourceCodeUpdated(List syntaxTrees, out Assembly assembly) { Diagnostics.Assert(syntaxTrees.Count != 0, "syntaxTrees should contains a source code."); @@ -1037,7 +1058,7 @@ private void SourceCodeProcessing() { // if the source code was already compiled and loaded and not changed // we get the assembly from the cache. - if (isSourceCodeUpdated(syntaxTrees, out Assembly assembly)) + if (IsSourceCodeUpdated(syntaxTrees, out Assembly assembly)) { CompileToAssembly(syntaxTrees, compilationOptions, emitOptions); } @@ -1107,7 +1128,7 @@ private void CheckDuplicateTypes(Compilation compilation, out ConcurrentBag DuplicateSymbols = new(); public readonly ConcurrentBag UniqueSymbols = new(); @@ -1127,7 +1148,7 @@ public override void VisitNamedType(INamedTypeSymbol symbol) // It is namespace-fully-qualified name var symbolFullName = symbol.ToString(); - if (s_sourceTypesCache.TryGetValue(symbolFullName, out _)) + if (s_sourceTypesCache.ContainsKey(symbolFullName)) { DuplicateSymbols.Add(symbolFullName); } @@ -1142,13 +1163,13 @@ private static void CacheNewTypes(ConcurrentBag newTypes) { foreach (var typeName in newTypes) { - s_sourceTypesCache.Add(typeName); + s_sourceTypesCache.TryAdd(typeName, null); } } private void CacheAssembly(Assembly assembly) { - s_sourceAssemblyCache.Add(_syntaxTreesHash, assembly); + s_sourceAssemblyCache.TryAdd(_syntaxTreesHash, assembly); } private void DoEmitAndLoadAssembly(Compilation compilation, EmitOptions emitOptions) @@ -1167,8 +1188,6 @@ private void DoEmitAndLoadAssembly(Compilation compilation, EmitOptions emitOpti if (emitResult.Success) { - // TODO: We could use Assembly.LoadFromStream() in future. - // See https://github.com/dotnet/corefx/issues/26994 ms.Seek(0, SeekOrigin.Begin); Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(ms); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Compare-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Compare-Object.cs index 83f78ce77a2..54a5b07cf5d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Compare-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Compare-Object.cs @@ -32,8 +32,8 @@ public sealed class CompareObjectCommand : ObjectCmdletBase /// /// [Parameter] - [ValidateRange(0, Int32.MaxValue)] - public int SyncWindow { get; set; } = Int32.MaxValue; + [ValidateRange(0, int.MaxValue)] + public int SyncWindow { get; set; } = int.MaxValue; /// /// diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-SddlString.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-SddlString.cs index 22c1e5ef46b..48bda12cd8a 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-SddlString.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-SddlString.cs @@ -70,7 +70,7 @@ private static List GetApplicableAccessRights(int accessMask, AccessRigh } else { - foreach (AccessRightTypeNames member in Enum.GetValues(typeof(AccessRightTypeNames))) + foreach (AccessRightTypeNames member in Enum.GetValues()) { typesToExamine.Add(GetRealAccessRightType(member)); } @@ -81,9 +81,8 @@ private static List GetApplicableAccessRights(int accessMask, AccessRigh foreach (string memberName in Enum.GetNames(accessRightType)) { int memberValue = (int)Enum.Parse(accessRightType, memberName); - if (!foundAccessRightValues.Contains(memberValue)) + if (foundAccessRightValues.Add(memberValue)) { - foundAccessRightValues.Add(memberValue); if ((accessMask & memberValue) == memberValue) { foundAccessRightNames.Add(memberName); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-StringData.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-StringData.cs index 16bb4be012d..9272e85c05d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-StringData.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-StringData.cs @@ -55,16 +55,14 @@ protected override void ProcessRecord() return; } - string[] lines = _stringData.Split('\n'); + string[] lines = _stringData.Split('\n', StringSplitOptions.TrimEntries); foreach (string line in lines) { - string s = line.Trim(); - - if (string.IsNullOrEmpty(s) || s[0] == '#') + if (string.IsNullOrEmpty(line) || line[0] == '#') continue; - int index = s.IndexOf(Delimiter); + int index = line.IndexOf(Delimiter); if (index <= 0) { throw PSTraceSource.NewInvalidOperationException( @@ -72,7 +70,7 @@ protected override void ProcessRecord() line); } - string name = s.Substring(0, index); + string name = line.Substring(0, index); name = name.Trim(); if (result.ContainsKey(name)) @@ -83,7 +81,7 @@ protected override void ProcessRecord() name); } - string value = s.Substring(index + 1); + string value = line.Substring(index + 1); value = value.Trim(); value = Regex.Unescape(value); 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 e72fc825e7c..2c530a8ab93 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertTo-Html.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertTo-Html.cs @@ -19,6 +19,7 @@ namespace Microsoft.PowerShell.Commands /// [Cmdlet(VerbsData.ConvertTo, "Html", DefaultParameterSetName = "Page", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096595", RemotingCapability = RemotingCapability.None)] + [OutputType(typeof(string))] public sealed class ConvertToHtmlCommand : PSCmdlet { @@ -343,10 +344,7 @@ private List ProcessParameter(object[] properties) TerminatingErrorContext invocationContext = new(this); ParameterProcessor processor = new(new ConvertHTMLExpressionParameterDefinition()); - if (properties == null) - { - properties = new object[] { "*" }; - } + properties ??= new object[] { "*" }; return processor.ProcessParameters(properties, invocationContext); } @@ -504,9 +502,8 @@ protected override void BeginProcessing() MshCommandRuntime mshCommandRuntime = this.CommandRuntime as MshCommandRuntime; string Message = StringUtil.Format(ConvertHTMLStrings.MetaPropertyNotFound, s, _meta[s]); WarningRecord record = new(Message); - InvocationInfo invocationInfo = GetVariableValue(SpecialVariables.MyInvocation) as InvocationInfo; - if (invocationInfo != null) + if (GetVariableValue(SpecialVariables.MyInvocation) is InvocationInfo invocationInfo) { record.SetInvocationInfo(invocationInfo); } @@ -555,16 +552,14 @@ private void WriteColumns(List mshParams) foreach (MshParameter p in mshParams) { COLTag.Append(" - ///Culture switch for csv conversion - /// + /// + /// Culture switch for csv conversion + /// [Parameter(ParameterSetName = "UseCulture")] public SwitchParameter UseCulture { get; set; } @@ -71,6 +72,12 @@ public abstract class BaseCsvWritingCommand : PSCmdlet [Alias("UQ")] public QuoteKind UseQuotes { get; set; } = QuoteKind.Always; + /// + /// Gets or sets property that writes csv file with no headers. + /// + [Parameter] + public SwitchParameter NoHeader { get; set; } + #endregion Command Line Parameters /// @@ -211,8 +218,8 @@ public string LiteralPath /// Gets or sets encoding optional flag. /// [Parameter] - [ArgumentToEncodingTransformationAttribute] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] public Encoding Encoding { @@ -228,7 +235,7 @@ public Encoding Encoding } } - private Encoding _encoding = ClrFacade.GetDefaultEncoding(); + private Encoding _encoding = Encoding.Default; /// /// Gets or sets property that sets append parameter. @@ -300,7 +307,7 @@ protected override void ProcessRecord() } // write headers (row1: typename + row2: column names) - if (!_isActuallyAppending) + if (!_isActuallyAppending && !NoHeader.IsPresent) { if (NoTypeInformation == false) { @@ -313,7 +320,6 @@ protected override void ProcessRecord() string csv = _helper.ConvertPSObjectToCSV(InputObject, _propertyNames); WriteCsvLine(csv); - _sw.Flush(); } /// @@ -415,7 +421,6 @@ private void CleanUp() { if (_sw != null) { - _sw.Flush(); _sw.Dispose(); _sw = null; } @@ -428,10 +433,7 @@ private void CleanUp() _readOnlyFileInfo.Attributes |= FileAttributes.ReadOnly; } - if (_helper != null) - { - _helper.Dispose(); - } + _helper?.Dispose(); } private void ReconcilePreexistingPropertyNames() @@ -588,9 +590,9 @@ public string[] LiteralPath [ValidateNotNull] public SwitchParameter UseCulture { get; set; } - /// + /// /// Gets or sets header property to customize the names. - /// + /// [Parameter(Mandatory = false)] [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] @@ -600,8 +602,8 @@ public string[] LiteralPath /// Gets or sets encoding optional flag. /// [Parameter] - [ArgumentToEncodingTransformationAttribute] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] public Encoding Encoding { @@ -617,7 +619,7 @@ public Encoding Encoding } } - private Encoding _encoding = ClrFacade.GetDefaultEncoding(); + private Encoding _encoding = Encoding.Default; /// /// Avoid writing out duplicate warning messages when there are one or more unspecified names. @@ -729,22 +731,30 @@ protected override void ProcessRecord() if (_propertyNames == null) { _propertyNames = ExportCsvHelper.BuildPropertyNames(InputObject, _propertyNames); - if (NoTypeInformation == false) + + if (!NoHeader.IsPresent) { - WriteCsvLine(ExportCsvHelper.GetTypeString(InputObject)); - } + if (NoTypeInformation == false) + { + WriteCsvLine(ExportCsvHelper.GetTypeString(InputObject)); + } - // Write property information - string properties = _helper.ConvertPropertyNamesCSV(_propertyNames); - if (!properties.Equals(string.Empty)) - WriteCsvLine(properties); + // Write property information + string properties = _helper.ConvertPropertyNamesCSV(_propertyNames); + if (!properties.Equals(string.Empty)) + { + WriteCsvLine(properties); + } + } } string csv = _helper.ConvertPSObjectToCSV(InputObject, _propertyNames); - // write to the console + // Write to the output stream if (csv != string.Empty) + { WriteCsvLine(csv); + } } #endregion Overrides @@ -783,9 +793,9 @@ public sealed class ConvertFromCsvCommand : PSCmdlet [ValidateNotNullOrEmpty] public char Delimiter { get; set; } - /// - ///Culture switch for csv conversion - /// + /// + /// Culture switch for csv conversion + /// [Parameter(ParameterSetName = "UseCulture", Mandatory = true)] [ValidateNotNull] [ValidateNotNullOrEmpty] @@ -800,9 +810,9 @@ public sealed class ConvertFromCsvCommand : PSCmdlet [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public PSObject[] InputObject { get; set; } - /// + /// /// Gets or sets header property to customize the names. - /// + /// [Parameter(Mandatory = false)] [ValidateNotNull] [ValidateNotNullOrEmpty] @@ -908,16 +918,36 @@ internal static IList BuildPropertyNames(PSObject source, IList throw new InvalidOperationException(CsvCommandStrings.BuildPropertyNamesMethodShouldBeCalledOnlyOncePerCmdletInstance); } - // serialize only Extended and Adapted properties.. - PSMemberInfoCollection srcPropertiesToSearch = - new PSMemberInfoIntegratingCollection( + propertyNames = new Collection(); + if (source.BaseObject is IDictionary dictionary) + { + foreach (var key in dictionary.Keys) + { + propertyNames.Add(LanguagePrimitives.ConvertTo(key)); + } + + // Add additional extended members added to the dictionary object, if any + var propertiesToSearch = new PSMemberInfoIntegratingCollection( source, - PSObject.GetPropertyCollection(PSMemberViewTypes.Extended | PSMemberViewTypes.Adapted)); + PSObject.GetPropertyCollection(PSMemberViewTypes.Extended)); - propertyNames = new Collection(); - foreach (PSPropertyInfo prop in srcPropertiesToSearch) + foreach (var prop in propertiesToSearch) + { + propertyNames.Add(prop.Name); + } + } + else { - propertyNames.Add(prop.Name); + // serialize only Extended and Adapted properties. + PSMemberInfoCollection srcPropertiesToSearch = + new PSMemberInfoIntegratingCollection( + source, + PSObject.GetPropertyCollection(PSMemberViewTypes.Extended | PSMemberViewTypes.Adapted)); + + foreach (PSPropertyInfo prop in srcPropertiesToSearch) + { + propertyNames.Add(prop.Name); + } } return propertyNames; @@ -929,10 +959,7 @@ internal static IList BuildPropertyNames(PSObject source, IList /// Converted string. internal string ConvertPropertyNamesCSV(IList propertyNames) { - if (propertyNames == null) - { - throw new ArgumentNullException(nameof(propertyNames)); - } + ArgumentNullException.ThrowIfNull(propertyNames); _outputString.Clear(); bool first = true; @@ -967,7 +994,8 @@ internal string ConvertPropertyNamesCSV(IList propertyNames) AppendStringWithEscapeAlways(_outputString, propertyName); break; case BaseCsvWritingCommand.QuoteKind.AsNeeded: - if (propertyName.Contains(_delimiter)) + + if (propertyName.AsSpan().IndexOfAny(_delimiter, '\n', '"') != -1) { AppendStringWithEscapeAlways(_outputString, propertyName); } @@ -995,10 +1023,7 @@ internal string ConvertPropertyNamesCSV(IList propertyNames) /// internal string ConvertPSObjectToCSV(PSObject mshObject, IList propertyNames) { - if (propertyNames == null) - { - throw new ArgumentNullException(nameof(propertyNames)); - } + ArgumentNullException.ThrowIfNull(propertyNames); _outputString.Clear(); bool first = true; @@ -1014,11 +1039,26 @@ internal string ConvertPSObjectToCSV(PSObject mshObject, IList propertyN _outputString.Append(_delimiter); } - // If property is not present, assume value is null and skip it. - if (mshObject.Properties[propertyName] is PSPropertyInfo property) + string value = null; + if (mshObject.BaseObject is IDictionary dictionary) + { + if (dictionary.Contains(propertyName)) + { + value = dictionary[propertyName].ToString(); + } + else if (mshObject.Properties[propertyName] is PSPropertyInfo property) + { + value = GetToStringValueForProperty(property); + } + } + else if (mshObject.Properties[propertyName] is PSPropertyInfo property) { - var value = GetToStringValueForProperty(property); + value = GetToStringValueForProperty(property); + } + // If value is null, assume property is not present and skip it. + if (value != null) + { if (_quoteFields != null) { if (_quoteFields.TryGetValue(propertyName, out _)) @@ -1038,7 +1078,7 @@ internal string ConvertPSObjectToCSV(PSObject mshObject, IList propertyN AppendStringWithEscapeAlways(_outputString, value); break; case BaseCsvWritingCommand.QuoteKind.AsNeeded: - if (value != null && value.Contains(_delimiter)) + if (value != null && value.AsSpan().IndexOfAny(_delimiter, '\n', '"') != -1) { AppendStringWithEscapeAlways(_outputString, value); } @@ -1069,10 +1109,7 @@ internal string ConvertPSObjectToCSV(PSObject mshObject, IList propertyN /// ToString() value. internal static string GetToStringValueForProperty(PSPropertyInfo property) { - if (property == null) - { - throw new ArgumentNullException(nameof(property)); - } + ArgumentNullException.ThrowIfNull(property); string value = null; try @@ -1122,7 +1159,7 @@ internal static string GetTypeString(PSObject source) temp = temp.Substring(4); } - type = string.Format(System.Globalization.CultureInfo.InvariantCulture, "#TYPE {0}", temp); + type = string.Create(System.Globalization.CultureInfo.InvariantCulture, $"#TYPE {temp}"); } return type; @@ -1234,15 +1271,8 @@ internal class ImportCsvHelper internal ImportCsvHelper(PSCmdlet cmdlet, char delimiter, IList header, string typeName, StreamReader streamReader) { - if (cmdlet == null) - { - throw new ArgumentNullException(nameof(cmdlet)); - } - - if (streamReader == null) - { - throw new ArgumentNullException(nameof(streamReader)); - } + ArgumentNullException.ThrowIfNull(cmdlet); + ArgumentNullException.ThrowIfNull(streamReader); _cmdlet = cmdlet; _delimiter = delimiter; @@ -1389,11 +1419,7 @@ private static void ValidatePropertyNames(IList names) { if (!string.IsNullOrEmpty(currentHeader)) { - if (!headers.Contains(currentHeader)) - { - headers.Add(currentHeader); - } - else + if (!headers.Add(currentHeader)) { // throw a terminating error as there are duplicate headers in the input. string memberAlreadyPresentMsg = diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs index 6ad8b02a558..da4e4a1262a 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs @@ -170,10 +170,7 @@ internal void DoneAsStream() internal void Stop() { CustomInternalSerializer serializer = _serializer; - if (serializer != null) - { - serializer.Stop(); - } + serializer?.Stop(); } #endregion @@ -341,8 +338,7 @@ private bool HandlePrimitiveKnownTypePSObject(object source, string property, in Dbg.Assert(source != null, "caller should validate the parameter"); bool sourceHandled = false; - PSObject moSource = source as PSObject; - if (moSource != null && !moSource.ImmediateBaseObjectIsEmpty) + if (source is PSObject moSource && !moSource.ImmediateBaseObjectIsEmpty) { // Check if baseObject is primitive known type object baseObject = moSource.ImmediateBaseObject; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs index aa370c12ba1..f5d84dfd195 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs @@ -103,7 +103,6 @@ public Guid InstanceId /// /// Gets or sets a flag that tells PowerShell to automatically perform a BreakAll when the debugger is attached to the remote target. /// - [Experimental("Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace", ExperimentAction.Show)] [Parameter] public SwitchParameter BreakAll { get; set; } @@ -237,10 +236,7 @@ protected override void StopProcessing() // Unblock the data collection. PSDataCollection debugCollection = _debugBlockingCollection; - if (debugCollection != null) - { - debugCollection.Complete(); - } + debugCollection?.Complete(); // Unblock any new command wait. _newRunningScriptEvent.Set(); @@ -274,7 +270,10 @@ private void WaitAndReceiveRunspaceOutput() // Wait for running script. _newRunningScriptEvent.Wait(); - if (!_debugging) { return; } + if (!_debugging) + { + return; + } AddDataEventHandlers(); @@ -335,9 +334,8 @@ private void HostWriteLine(string line) private void AddDataEventHandlers() { // Create new collection objects. - if (_debugBlockingCollection != null) { _debugBlockingCollection.Dispose(); } - - if (_debugAccumulateCollection != null) { _debugAccumulateCollection.Dispose(); } + _debugBlockingCollection?.Dispose(); + _debugAccumulateCollection?.Dispose(); _debugBlockingCollection = new PSDataCollection(); _debugBlockingCollection.BlockingEnumerator = true; @@ -409,8 +407,7 @@ private void RemoveDataEventHandlers() private void HandleRunspaceAvailabilityChanged(object sender, RunspaceAvailabilityEventArgs e) { // Ignore nested commands. - LocalRunspace localRunspace = sender as LocalRunspace; - if (localRunspace != null) + if (sender is LocalRunspace localRunspace) { var basePowerShell = localRunspace.GetCurrentBasePowerShell(); if ((basePowerShell != null) && (basePowerShell.IsNested)) @@ -440,8 +437,7 @@ private void HandleDebuggerNestedDebuggingCancelledEvent(object sender, EventArg private void HandlePipelineOutputDataReady(object sender, EventArgs e) { - PipelineReader reader = sender as PipelineReader; - if (reader != null && reader.IsOpen) + if (sender is PipelineReader reader && reader.IsOpen) { WritePipelineCollection(reader.NonBlockingRead(), PSStreamObjectType.Output); } @@ -449,8 +445,7 @@ private void HandlePipelineOutputDataReady(object sender, EventArgs e) private void HandlePipelineErrorDataReady(object sender, EventArgs e) { - PipelineReader reader = sender as PipelineReader; - if (reader != null && reader.IsOpen) + if (sender is PipelineReader reader && reader.IsOpen) { WritePipelineCollection(reader.NonBlockingRead(), PSStreamObjectType.Error); } @@ -508,7 +503,10 @@ private void HandlePowerShellPStreamItem(PSStreamObject streamItem) private void AddToDebugBlockingCollection(PSStreamObject streamItem) { - if (!_debugBlockingCollection.IsOpen) { return; } + if (!_debugBlockingCollection.IsOpen) + { + return; + } if (streamItem != null) { @@ -537,8 +535,7 @@ private void EnableHostDebugger(Runspace runspace, bool enabled) // Only enable and disable the host's runspace if we are in process attach mode. if (_debugger is ServerRemoteDebugger) { - LocalRunspace localRunspace = runspace as LocalRunspace; - if ((localRunspace != null) && (localRunspace.ExecutionContext != null) && (localRunspace.ExecutionContext.EngineHostInterface != null)) + if ((runspace is LocalRunspace localRunspace) && (localRunspace.ExecutionContext != null) && (localRunspace.ExecutionContext.EngineHostInterface != null)) { try { @@ -551,8 +548,7 @@ private void EnableHostDebugger(Runspace runspace, bool enabled) private static void SetLocalMode(System.Management.Automation.Debugger debugger, bool localMode) { - ServerRemoteDebugger remoteDebugger = debugger as ServerRemoteDebugger; - if (remoteDebugger != null) + if (debugger is ServerRemoteDebugger remoteDebugger) { remoteDebugger.LocalDebugMode = localMode; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs index c37d83ecf84..47b83c78770 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs @@ -59,7 +59,10 @@ public sealed class PSRunspaceDebug /// Runspace local Id. public PSRunspaceDebug(bool enabled, bool breakAll, string runspaceName, int runspaceId) { - if (string.IsNullOrEmpty(runspaceName)) { throw new PSArgumentNullException(nameof(runspaceName)); } + if (string.IsNullOrEmpty(runspaceName)) + { + throw new PSArgumentNullException(nameof(runspaceName)); + } this.Enabled = enabled; this.BreakAll = breakAll; @@ -115,7 +118,7 @@ public abstract class CommonRunspaceCommandBase : PSCmdlet /// [Parameter(Position = 0, ParameterSetName = CommonRunspaceCommandBase.RunspaceNameParameterSet)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] RunspaceName { @@ -131,7 +134,7 @@ public string[] RunspaceName ValueFromPipelineByPropertyName = true, ValueFromPipeline = true, ParameterSetName = CommonRunspaceCommandBase.RunspaceParameterSet)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public Runspace[] Runspace { @@ -145,7 +148,7 @@ public Runspace[] Runspace [Parameter(Position = 0, Mandatory = true, ParameterSetName = CommonRunspaceCommandBase.RunspaceIdParameterSet)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public int[] RunspaceId { @@ -158,7 +161,7 @@ public int[] RunspaceId [Parameter(Position = 0, Mandatory = true, ParameterSetName = CommonRunspaceCommandBase.RunspaceInstanceIdParameterSet)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public System.Guid[] RunspaceInstanceId { @@ -170,7 +173,7 @@ public System.Guid[] RunspaceInstanceId /// Gets or Sets the ProcessName for which runspace debugging has to be enabled or disabled. /// [Parameter(Position = 0, ParameterSetName = CommonRunspaceCommandBase.ProcessNameParameterSet)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public string ProcessName { get; @@ -181,7 +184,7 @@ public string ProcessName /// Gets or Sets the AppDomain Names for which runspace debugging has to be enabled or disabled. /// [Parameter(Position = 1, ParameterSetName = CommonRunspaceCommandBase.ProcessNameParameterSet)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Scope = "member", Target = "Microsoft.PowerShell.Commands.CommonRunspaceCommandBase.#AppDomainName")] public string[] AppDomainName @@ -275,10 +278,7 @@ protected void SetDebugPreferenceHelper(string processName, string[] appDomainNa { if (!string.IsNullOrEmpty(currentAppDomainName)) { - if (appDomainNames == null) - { - appDomainNames = new List(); - } + appDomainNames ??= new List(); appDomainNames.Add(currentAppDomainName.ToLowerInvariant()); } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ExportAliasCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ExportAliasCommand.cs index e01d8c28e0c..d6f2c661ad0 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ExportAliasCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ExportAliasCommand.cs @@ -21,7 +21,7 @@ public enum ExportAliasFormat Csv, /// - /// Aliases will be exported as an MSH script. + /// Aliases will be exported as a script. /// Script } @@ -117,7 +117,7 @@ public SwitchParameter PassThru /// /// Property that sets append parameter. /// - [Parameter()] + [Parameter] public SwitchParameter Append { get @@ -136,7 +136,7 @@ public SwitchParameter Append /// /// Property that sets force parameter. /// - [Parameter()] + [Parameter] public SwitchParameter Force { get @@ -155,7 +155,7 @@ public SwitchParameter Force /// /// Property that prevents file overwrite. /// - [Parameter()] + [Parameter] [Alias("NoOverwrite")] public SwitchParameter NoClobber { @@ -184,6 +184,7 @@ public SwitchParameter NoClobber /// which scope the aliases are retrieved from. /// [Parameter] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } #endregion Parameters @@ -291,8 +292,7 @@ protected override void EndProcessing() line = GetAliasLine(alias, "set-alias -Name:\"{0}\" -Value:\"{1}\" -Description:\"{2}\" -Option:\"{3}\""); } - if (writer != null) - writer.WriteLine(line); + writer?.WriteLine(line); if (PassThru) { @@ -302,8 +302,7 @@ protected override void EndProcessing() } finally { - if (writer != null) - writer.Dispose(); + writer?.Dispose(); // reset the read-only attribute if (readOnlyFileInfo != null) readOnlyFileInfo.Attributes |= FileAttributes.ReadOnly; 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 3db37f3528e..974188c2f74 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 @@ -33,15 +33,13 @@ internal override object GetValue(PSObject liveObject) // The live object has the liveObjectPropertyName property. object liveObjectValue = propertyInfo.Value; - ICollection collectionValue = liveObjectValue as ICollection; - if (collectionValue != null) + if (liveObjectValue is ICollection collectionValue) { liveObjectValue = _parentCmdlet.ConvertToString(PSObjectHelper.AsPSObject(propertyInfo.Value)); } else { - PSObject psObjectValue = liveObjectValue as PSObject; - if (psObjectValue != null) + if (liveObjectValue is PSObject psObjectValue) { // Since PSObject implements IComparable there is a need to verify if its BaseObject actually implements IComparable. if (psObjectValue.BaseObject is IComparable) 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 e606e58e9e4..43e24e8b5b6 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 @@ -145,7 +145,7 @@ protected override void EndProcessing() // The pipeline will be blocked while we don't return if (this.Wait || this.OutputMode != OutputModeOption.None) { - _windowProxy.BlockUntillClosed(); + _windowProxy.BlockUntilClosed(); } // Output selected items to pipeline. @@ -180,8 +180,7 @@ protected override void ProcessRecord() return; } - IDictionary dictionary = InputObject.BaseObject as IDictionary; - if (dictionary != null) + if (InputObject.BaseObject is IDictionary dictionary) { // Dictionaries should be enumerated through because the pipeline does not enumerate through them. foreach (DictionaryEntry entry in dictionary) 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 3f22caf1e7b..b406c2bc78c 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 @@ -58,20 +58,11 @@ internal OutWindowProxy(string title, OutputModeOption outPutMode, OutGridViewCo /// An array of types to add. internal void AddColumns(string[] propertyNames, string[] displayNames, Type[] types) { - if (propertyNames == null) - { - throw new ArgumentNullException(nameof(propertyNames)); - } + ArgumentNullException.ThrowIfNull(propertyNames); - if (displayNames == null) - { - throw new ArgumentNullException(nameof(displayNames)); - } + ArgumentNullException.ThrowIfNull(displayNames); - if (types == null) - { - throw new ArgumentNullException(nameof(types)); - } + ArgumentNullException.ThrowIfNull(types); try { @@ -80,8 +71,7 @@ internal void AddColumns(string[] propertyNames, string[] displayNames, Type[] t catch (TargetInvocationException ex) { // Verify if this is an error loading the System.Core dll. - FileNotFoundException fileNotFoundEx = ex.InnerException as FileNotFoundException; - if (fileNotFoundEx != null && fileNotFoundEx.FileName.Contains("System.Core")) + if (ex.InnerException is FileNotFoundException fileNotFoundEx && fileNotFoundEx.FileName.Contains("System.Core")) { _parentCmdlet.ThrowTerminatingError( new ErrorRecord(new InvalidOperationException( @@ -177,10 +167,7 @@ private void AddExtraProperties(PSObject staleObject, PSObject liveObject) /// internal void AddItem(PSObject livePSObject) { - if (livePSObject == null) - { - throw new ArgumentNullException(nameof(livePSObject)); - } + ArgumentNullException.ThrowIfNull(livePSObject); if (_headerInfo == null) { @@ -203,10 +190,7 @@ internal void AddItem(PSObject livePSObject) /// internal void AddHeteroViewItem(PSObject livePSObject) { - if (livePSObject == null) - { - throw new ArgumentNullException(nameof(livePSObject)); - } + ArgumentNullException.ThrowIfNull(livePSObject); if (_headerInfo == null) { @@ -230,13 +214,7 @@ internal void ShowWindow() } } - internal void BlockUntillClosed() - { - if (_closedEvent != null) - { - _closedEvent.WaitOne(); - } - } + internal void BlockUntilClosed() => _closedEvent?.WaitOne(); /// /// Implements IDisposable logic. 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 216d9121d54..9213484d332 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 @@ -69,14 +69,10 @@ internal HeaderInfo GenerateHeaderInfo(PSObject input, TableControlBody tableBod if (token != null) { - FieldPropertyToken fpt = token as FieldPropertyToken; - if (fpt != null) + if (token is FieldPropertyToken fpt) { - if (displayName == null) - { - // Database does not provide a label(DisplayName) for the current property, use the expression value instead. - displayName = fpt.expression.expressionValue; - } + // If Database does not provide a label(DisplayName) for the current property, use the expression value instead. + displayName ??= fpt.expression.expressionValue; if (fpt.expression.isScriptBlock) { @@ -101,8 +97,7 @@ internal HeaderInfo GenerateHeaderInfo(PSObject input, TableControlBody tableBod } else { - TextToken tt = token as TextToken; - if (tt != null) + if (token is TextToken tt) { displayName = _typeInfoDatabase.displayResourceManagerCache.GetTextTokenString(tt); columnInfo = new OriginalColumnInfo(tt.text, displayName, tt.text, parentCmdlet); @@ -170,10 +165,7 @@ internal HeaderInfo GenerateHeaderInfo(PSObject input, OutGridViewCommand parent propertyName = (string)key; } - if (propertyName == null) - { - propertyName = association.ResolvedExpression.ToString(); - } + propertyName ??= association.ResolvedExpression.ToString(); ColumnInfo columnInfo = new OriginalColumnInfo(propertyName, propertyName, propertyName, parentCmdlet); @@ -233,10 +225,7 @@ private List GetActiveTableRowDefinition(TableControlBod } } - if (matchingRowDefinition == null) - { - matchingRowDefinition = match.BestMatch as TableRowDefinition; - } + matchingRowDefinition ??= match.BestMatch as TableRowDefinition; if (matchingRowDefinition == null) { @@ -254,10 +243,7 @@ private List GetActiveTableRowDefinition(TableControlBod } } - if (matchingRowDefinition == null) - { - matchingRowDefinition = match.BestMatch as TableRowDefinition; - } + matchingRowDefinition ??= match.BestMatch as TableRowDefinition; } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/common/GetFormatDataCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/common/GetFormatDataCommand.cs index 70967aa897b..16d4325f68a 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/common/GetFormatDataCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/common/GetFormatDataCommand.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; +using System.Globalization; using System.Management.Automation; using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; @@ -85,7 +85,7 @@ private static Dictionary> GetTypeGroupMap(IEnumerable typeReference.name).ToList(); + var typesInGroup = typeGroup.typeReferenceList.ConvertAll(static typeReference => typeReference.name); typeGroupMap.Add(typeGroup.name, typesInGroup); } } @@ -98,7 +98,7 @@ private static Dictionary> GetTypeGroupMap(IEnumerable protected override void ProcessRecord() { - // Remoting detection: + // Remoting detection: // * Automatic variable $PSSenderInfo is defined in true remoting contexts as well as in background jobs. // * $PSSenderInfo.ApplicationArguments.PSVersionTable.PSVersion contains the client version, as a [version] instance. // Note: Even though $PSVersionTable.PSVersion is of type [semver] in PowerShell 6+, it is of type [version] here, @@ -130,6 +130,7 @@ protected override void ProcessRecord() foreach (ViewDefinition definition in viewdefinitions) { + this.WriteVerbose(string.Format(CultureInfo.CurrentCulture, GetFormatDataStrings.ProcessViewDefinition, definition.name)); if (definition.isHelpFormatter) continue; @@ -140,22 +141,19 @@ protected override void ProcessRecord() PSControl control; - var tableControlBody = definition.mainControl as TableControlBody; - if (tableControlBody != null) + if (definition.mainControl is TableControlBody tableControlBody) { control = new TableControl(tableControlBody, definition); } else { - var listControlBody = definition.mainControl as ListControlBody; - if (listControlBody != null) + if (definition.mainControl is ListControlBody listControlBody) { control = new ListControl(listControlBody, definition); } else { - var wideControlBody = definition.mainControl as WideControlBody; - if (wideControlBody != null) + if (definition.mainControl is WideControlBody wideControlBody) { control = new WideControl(wideControlBody, definition); if (writeOldWay) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/common/WriteFormatDataCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/common/WriteFormatDataCommand.cs index 3ccbe942829..c07539aa25a 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/common/WriteFormatDataCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/common/WriteFormatDataCommand.cs @@ -81,7 +81,7 @@ public string LiteralPath /// /// Force writing a file. /// - [Parameter()] + [Parameter] public SwitchParameter Force { get @@ -98,7 +98,7 @@ public SwitchParameter Force /// /// Do not overwrite file if exists. /// - [Parameter()] + [Parameter] [Alias("NoOverwrite")] public SwitchParameter NoClobber { @@ -118,7 +118,7 @@ public SwitchParameter NoClobber /// /// Include scriptblocks for export. /// - [Parameter()] + [Parameter] public SwitchParameter IncludeScriptBlock { get 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 091903f011a..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 @@ -24,7 +24,7 @@ public sealed class FormatHex : PSCmdlet private const int BUFFERSIZE = 16; /// - /// For cases where a homogenous collection of bytes or other items are directly piped in, we collect all the + /// For cases where a homogeneous collection of bytes or other items are directly piped in, we collect all the /// bytes in a List<byte> and then output the formatted result all at once in EndProcessing(). /// private readonly List _inputBuffer = new(); @@ -37,7 +37,7 @@ public sealed class FormatHex : PSCmdlet private bool _groupInput = true; /// - /// Keep track of prior input types to determine if we're given a heterogenous collection. + /// Keep track of prior input types to determine if we're given a heterogeneous collection. /// private Type _lastInputType; @@ -47,14 +47,14 @@ public sealed class FormatHex : PSCmdlet /// Gets or sets the path of file(s) to process. /// [Parameter(Mandatory = true, Position = 0, ParameterSetName = "Path")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public string[] Path { get; set; } /// /// Gets or sets the literal path of file to process. /// [Parameter(Mandatory = true, ParameterSetName = "LiteralPath")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [Alias("PSPath", "LP")] public string[] LiteralPath { get; set; } @@ -68,8 +68,8 @@ public sealed class FormatHex : PSCmdlet /// Gets or sets the type of character encoding for InputObject. /// [Parameter(ParameterSetName = "ByInputObject")] - [ArgumentToEncodingTransformationAttribute()] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] public Encoding Encoding { @@ -85,7 +85,7 @@ public Encoding Encoding } } - private Encoding _encoding = ClrFacade.GetDefaultEncoding(); + private Encoding _encoding = Encoding.Default; /// /// Gets or sets count of bytes to read from the input stream. @@ -391,7 +391,6 @@ private byte[] ConvertToBytes(object inputObject) byte[] result = null; int elements = 1; bool isArray = false; - bool isBool = false; bool isEnum = false; if (baseType.IsArray) { @@ -424,11 +423,6 @@ private byte[] ConvertToBytes(object inputObject) _lastInputType = baseType; } - if (baseType == typeof(bool)) - { - isBool = true; - } - var elementSize = Marshal.SizeOf(baseType); result = new byte[elementSize * elements]; if (!isArray) @@ -450,11 +444,6 @@ private byte[] ConvertToBytes(object inputObject) { toBytes = Convert.ChangeType(obj, baseType); } - else if (isBool) - { - // bool is 1 byte apparently - toBytes = Convert.ToByte(obj); - } else { toBytes = obj; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-list/Format-List.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-list/Format-List.cs index 7237cd46834..182ea53e258 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-list/Format-List.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-list/Format-List.cs @@ -8,9 +8,10 @@ namespace Microsoft.PowerShell.Commands { /// - /// Implementation for the format-table command. + /// Implementation for the Format-List command. /// [Cmdlet(VerbsCommon.Format, "List", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096928")] + [OutputType(typeof(FormatStartData), typeof(FormatEntryData), typeof(FormatEndData), typeof(GroupStartData), typeof(GroupEndData))] public class FormatListCommand : OuterFormatTableAndListBase { /// 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 9e064ba37db..2179243279d 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 @@ -8,9 +8,10 @@ namespace Microsoft.PowerShell.Commands { /// - /// Implementation for the format-custom command. It just calls the formatting engine on complex shape. + /// Implementation for the Format-Custom command. It just calls the formatting engine on complex shape. /// [Cmdlet(VerbsCommon.Format, "Custom", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096929")] + [OutputType(typeof(FormatStartData), typeof(FormatEntryData), typeof(FormatEndData), typeof(GroupStartData), typeof(GroupEndData))] public class FormatCustomCommand : OuterFormatShapeCommandBase { /// @@ -43,7 +44,7 @@ public object[] Property /// /// /// - [ValidateRangeAttribute(1, int.MaxValue)] + [ValidateRange(1, int.MaxValue)] [Parameter] public int Depth { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-table/Format-Table.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-table/Format-Table.cs index e831981568e..a9e35fcdbc3 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-table/Format-Table.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-table/Format-Table.cs @@ -8,9 +8,10 @@ namespace Microsoft.PowerShell.Commands { /// - /// Implementation for the format-table command. + /// Implementation for the Format-Table command. /// [Cmdlet(VerbsCommon.Format, "Table", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096703")] + [OutputType(typeof(FormatStartData), typeof(FormatEntryData), typeof(FormatEndData), typeof(GroupStartData), typeof(GroupEndData))] public class FormatTableCommand : OuterFormatTableBase { /// 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 8d80462c12f..aeddf498f44 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 @@ -10,9 +10,10 @@ namespace Microsoft.PowerShell.Commands { /// - /// Implementation for the format-table command. + /// Implementation for the Format-Wide command. /// [Cmdlet(VerbsCommon.Format, "Wide", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096930")] + [OutputType(typeof(FormatStartData), typeof(FormatEntryData), typeof(FormatEndData), typeof(GroupStartData), typeof(GroupEndData))] public class FormatWideCommand : OuterFormatShapeCommandBase { /// @@ -47,17 +48,8 @@ public object Property [Parameter] public SwitchParameter AutoSize { - get - { - if (_autosize.HasValue) - return _autosize.Value; - return false; - } - - set - { - _autosize = value; - } + get => _autosize.GetValueOrDefault(); + set => _autosize = value; } private bool? _autosize = null; @@ -67,20 +59,11 @@ public SwitchParameter AutoSize /// /// [Parameter] - [ValidateRangeAttribute(1, int.MaxValue)] + [ValidateRange(1, int.MaxValue)] public int Column { - get - { - if (_column.HasValue) - return _column.Value; - return -1; - } - - set - { - _column = value; - } + get => _column.GetValueOrDefault(-1); + set => _column = value; } private int? _column = null; @@ -110,22 +93,19 @@ internal override FormattingCommandLineParameters GetCommandLineParameters() } // we cannot specify -column and -autosize, they are mutually exclusive - if (_autosize.HasValue && _column.HasValue) + if (AutoSize && _column.HasValue) { - if (_autosize.Value) - { - // the user specified -autosize:true AND a column number - string msg = StringUtil.Format(FormatAndOut_format_xxx.CannotSpecifyAutosizeAndColumnsError); + // the user specified -autosize:true AND a column number + string msg = StringUtil.Format(FormatAndOut_format_xxx.CannotSpecifyAutosizeAndColumnsError); - ErrorRecord errorRecord = new( - new InvalidDataException(), - "FormatCannotSpecifyAutosizeAndColumns", - ErrorCategory.InvalidArgument, - null); + ErrorRecord errorRecord = new( + new InvalidDataException(), + "FormatCannotSpecifyAutosizeAndColumns", + ErrorCategory.InvalidArgument, + null); - errorRecord.ErrorDetails = new ErrorDetails(msg); - this.ThrowTerminatingError(errorRecord); - } + errorRecord.ErrorDetails = new ErrorDetails(msg); + this.ThrowTerminatingError(errorRecord); } parameters.groupByParameter = this.ProcessGroupByParameter(); 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 2fbdb73dc9c..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. /// [Parameter(Position = 1)] - [ArgumentToEncodingTransformationAttribute()] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] public Encoding Encoding { @@ -91,12 +91,12 @@ public Encoding Encoding } } - private Encoding _encoding = ClrFacade.GetDefaultEncoding(); + private Encoding _encoding = Encoding.Default; /// /// Property that sets append parameter. /// - [Parameter()] + [Parameter] public SwitchParameter Append { get { return _append; } @@ -109,7 +109,7 @@ public SwitchParameter Append /// /// Property that sets force parameter. /// - [Parameter()] + [Parameter] public SwitchParameter Force { get { return _force; } @@ -122,7 +122,7 @@ public SwitchParameter Force /// /// Property that prevents file overwrite. /// - [Parameter()] + [Parameter] [Alias("NoOverwrite")] public SwitchParameter NoClobber { @@ -136,7 +136,7 @@ public SwitchParameter NoClobber /// /// Optional, number of columns to use when writing to device. /// - [ValidateRangeAttribute(2, int.MaxValue)] + [ValidateRange(2, int.MaxValue)] [Parameter] public int Width { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/out-printer/PrinterLineOutput.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/out-printer/PrinterLineOutput.cs index cbd19a17f4f..94f3fe6a50e 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/out-printer/PrinterLineOutput.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/out-printer/PrinterLineOutput.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Drawing; using System.Drawing.Printing; +using System.Management.Automation; +using System.Management.Automation.Internal; namespace Microsoft.PowerShell.Commands.Internal.Format { @@ -62,11 +64,25 @@ internal override int RowNumber internal override void WriteLine(string s) { CheckStopProcessing(); - // delegate the action to the helper, - // that will properly break the string into - // screen lines - _writeLineHelper.WriteLine(s, this.ColumnNumber); + + // Remove all ANSI escape sequences before sending out to the printer. + s = new ValueStringDecorated(s).ToString(OutputRendering.PlainText); + WriteRawText(s); } + + /// + /// Write a raw text by delegating to the writer underneath, with no change to the text. + /// For example, keeping VT escape sequences intact in it. + /// + /// The raw text to be written to the device. + internal override void WriteRawText(string s) + { + CheckStopProcessing(); + + // Delegate the action to the helper, that will properly break the string into screen lines. + _writeLineHelper.WriteLine(s, ColumnNumber); + } + #endregion /// 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 /// /// Optional, number of columns to use when writing to device. /// - [ValidateRangeAttribute(2, int.MaxValue)] + [ValidateRange(2, int.MaxValue)] [Parameter] public int Width { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-Error.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-Error.cs index 053f99f897d..3af01087ad7 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-Error.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-Error.cs @@ -12,7 +12,7 @@ namespace Microsoft.PowerShell.Commands /// Class for Get-Error implementation. /// [Cmdlet(VerbsCommon.Get, "Error", - HelpUri = "https://docs.microsoft.com/powershell/module/microsoft.powershell.utility/get-error", + HelpUri = "https://go.microsoft.com/fwlink/?linkid=2241804", DefaultParameterSetName = NewestParameterSetName)] [OutputType("System.Management.Automation.ErrorRecord#PSExtendedError", "System.Exception#PSExtendedError")] public sealed class GetErrorCommand : PSCmdlet 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 469e18926d8..85bb8fa53b7 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-PSBreakpoint.cs @@ -16,8 +16,7 @@ public enum BreakpointType /// Breakpoint on a line within a script Line, - /// - /// Breakpoint on a variable + /// Breakpoint on a variable Variable, /// Breakpoint on a command @@ -107,7 +106,7 @@ protected override void ProcessRecord() breakpoints = Filter( breakpoints, Id, - (Breakpoint breakpoint, int id) => breakpoint.Id == id); + static (Breakpoint breakpoint, int id) => breakpoint.Id == id); } else if (ParameterSetName.Equals(CommandParameterSetName, StringComparison.OrdinalIgnoreCase)) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetAliasCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetAliasCommand.cs index fee8b3ab27c..7b93b555c5c 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetAliasCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetAliasCommand.cs @@ -51,6 +51,7 @@ public string[] Exclude /// which scope the aliases are retrieved from. /// [Parameter] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } /// @@ -181,7 +182,7 @@ private void WriteMatches(string value, string parametersetname) } results.Sort( - (AliasInfo left, AliasInfo right) => StringComparer.CurrentCultureIgnoreCase.Compare(left.Name, right.Name)); + static (AliasInfo left, AliasInfo right) => StringComparer.CurrentCultureIgnoreCase.Compare(left.Name, right.Name)); foreach (AliasInfo alias in results) { this.WriteObject(alias); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetDateCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetDateCommand.cs index 40e0bbeefd7..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. /// [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. /// [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. /// [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. /// [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. /// [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. /// [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. /// [Parameter] - [ValidateRangeAttribute(0, 999)] + [ValidateRange(0, 999)] public int Millisecond { get @@ -376,8 +376,6 @@ protected override void ProcessRecord() } } - private static readonly DateTime s_epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - /// /// This is more an implementation of the UNIX strftime. /// @@ -501,7 +499,7 @@ private string UFormatDateString(DateTime dateTime) break; case 's': - sb.Append(StringUtil.Format("{0:0}", dateTime.ToUniversalTime().Subtract(s_epoch).TotalSeconds)); + sb.Append(StringUtil.Format("{0:0}", dateTime.ToUniversalTime().Subtract(DateTime.UnixEpoch).TotalSeconds)); break; case 'T': diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetEventCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetEventCommand.cs index 42360983c4e..f4ab18ac75a 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetEventCommand.cs @@ -20,7 +20,7 @@ public class GetEventCommand : PSCmdlet /// An identifier for this event subscription. /// [Parameter(Position = 0, ValueFromPipelineByPropertyName = true, ParameterSetName = "BySource")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public string SourceIdentifier { get diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetEventSubscriberCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetEventSubscriberCommand.cs index b3bd99b7bdf..f95c708fa50 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetEventSubscriberCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetEventSubscriberCommand.cs @@ -20,7 +20,7 @@ public class GetEventSubscriberCommand : PSCmdlet /// An identifier for this event subscription. /// [Parameter(Position = 0, ValueFromPipelineByPropertyName = true, ParameterSetName = "BySource")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public string SourceIdentifier { get diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetHash.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetHash.cs index 40ea1d187d5..6f4b2f53cef 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetHash.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetHash.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; using System.IO; using System.Management.Automation; using System.Security.Cryptography; @@ -68,15 +69,6 @@ public string[] LiteralPath [Parameter(Mandatory = true, ParameterSetName = StreamParameterSet, Position = 0)] public Stream InputStream { get; set; } - /// - /// BeginProcessing() override. - /// This is for hash function init. - /// - protected override void BeginProcessing() - { - InitHasher(Algorithm); - } - /// /// ProcessRecord() override. /// This is for paths collecting from pipe. @@ -133,6 +125,26 @@ protected override void ProcessRecord() } } + private byte[] ComputeHash(Stream stream) + { + switch (Algorithm) + { + case HashAlgorithmNames.SHA1: + return SHA1.HashData(stream); + case HashAlgorithmNames.SHA256: + return SHA256.HashData(stream); + case HashAlgorithmNames.SHA384: + return SHA384.HashData(stream); + case HashAlgorithmNames.SHA512: + return SHA512.HashData(stream); + case HashAlgorithmNames.MD5: + return MD5.HashData(stream); + } + + Debug.Assert(false, "invalid hash algorithm"); + return SHA256.HashData(stream); + } + /// /// Perform common error checks. /// Populate source code. @@ -141,12 +153,9 @@ protected override void EndProcessing() { if (ParameterSetName == StreamParameterSet) { - byte[] bytehash = null; - string hash = null; + byte[] bytehash = ComputeHash(InputStream); - bytehash = hasher.ComputeHash(InputStream); - - hash = BitConverter.ToString(bytehash).Replace("-", string.Empty); + string hash = Convert.ToHexString(bytehash); WriteHashResult(Algorithm, hash, string.Empty); } } @@ -159,7 +168,6 @@ protected override void EndProcessing() /// Boolean value indicating whether the hash calculation succeeded or failed. private bool ComputeFileHash(string path, out string hash) { - byte[] bytehash = null; Stream openfilestream = null; hash = null; @@ -167,9 +175,9 @@ private bool ComputeFileHash(string path, out string hash) try { openfilestream = File.OpenRead(path); + byte[] bytehash = ComputeHash(openfilestream); - bytehash = hasher.ComputeHash(openfilestream); - hash = BitConverter.ToString(bytehash).Replace("-", string.Empty); + hash = Convert.ToHexString(bytehash); } catch (FileNotFoundException ex) { @@ -259,11 +267,6 @@ public string Algorithm private string _Algorithm = HashAlgorithmNames.SHA256; - /// - /// Hash algorithm is used. - /// - protected HashAlgorithm hasher; - /// /// Hash algorithm names. /// @@ -275,40 +278,6 @@ internal static class HashAlgorithmNames public const string SHA384 = "SHA384"; public const string SHA512 = "SHA512"; } - - /// - /// Init a hash algorithm. - /// - protected void InitHasher(string Algorithm) - { - try - { - switch (Algorithm) - { - case HashAlgorithmNames.SHA1: - hasher = SHA1.Create(); - break; - case HashAlgorithmNames.SHA256: - hasher = SHA256.Create(); - break; - case HashAlgorithmNames.SHA384: - hasher = SHA384.Create(); - break; - case HashAlgorithmNames.SHA512: - hasher = SHA512.Create(); - break; - case HashAlgorithmNames.MD5: - hasher = MD5.Create(); - break; - } - } - catch - { - // Seems it will never throw! Remove? - Exception exc = new NotSupportedException(UtilityCommonStrings.AlgorithmTypeNotSupported); - ThrowTerminatingError(new ErrorRecord(exc, "AlgorithmTypeNotSupported", ErrorCategory.NotImplemented, null)); - } - } } /// diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetMember.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetMember.cs index 2fd9a499012..0b11af84200 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetMember.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetMember.cs @@ -230,8 +230,7 @@ protected override void ProcessRecord() { if (!Force) { - PSMethod memberAsPSMethod = member as PSMethod; - if ((memberAsPSMethod != null) && (memberAsPSMethod.IsSpecial)) + if ((member is PSMethod memberAsPSMethod) && (memberAsPSMethod.IsSpecial)) { continue; } @@ -249,7 +248,7 @@ protected override void ProcessRecord() } } - private class MemberComparer : System.Collections.Generic.IComparer + private sealed class MemberComparer : System.Collections.Generic.IComparer { public int Compare(MemberDefinition first, MemberDefinition second) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommand.cs index 7d02713d801..c77c25c5f4f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommand.cs @@ -1,199 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Numerics; -using System.Security.Cryptography; -using System.Threading; - -using Debug = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Commands { /// - /// This class implements get-random cmdlet. + /// This class implements `Get-Random` cmdlet. /// - /// - [Cmdlet(VerbsCommon.Get, "Random", DefaultParameterSetName = GetRandomCommand.RandomNumberParameterSet, + [Cmdlet(VerbsCommon.Get, "Random", DefaultParameterSetName = GetRandomCommandBase.RandomNumberParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097016", RemotingCapability = RemotingCapability.None)] - [OutputType(typeof(Int32), typeof(Int64), typeof(double))] - public class GetRandomCommand : PSCmdlet + [OutputType(typeof(int), typeof(long), typeof(double))] + public sealed class GetRandomCommand : GetRandomCommandBase { - #region Parameter set handling - - private const string RandomNumberParameterSet = "RandomNumberParameterSet"; - private const string RandomListItemParameterSet = "RandomListItemParameterSet"; - private const string ShuffleParameterSet = "ShuffleParameterSet"; - - private static readonly object[] _nullInArray = new object[] { null }; - - private enum MyParameterSet - { - Unknown, - RandomNumber, - RandomListItem - } - - private MyParameterSet _effectiveParameterSet; - - private MyParameterSet EffectiveParameterSet - { - get - { - // cache MyParameterSet enum instead of doing string comparison every time - if (_effectiveParameterSet == MyParameterSet.Unknown) - { - if ((MyInvocation.ExpectingInput) && (Maximum == null) && (Minimum == null)) - { - _effectiveParameterSet = MyParameterSet.RandomListItem; - } - else if (ParameterSetName == GetRandomCommand.RandomListItemParameterSet - || ParameterSetName == GetRandomCommand.ShuffleParameterSet) - { - _effectiveParameterSet = MyParameterSet.RandomListItem; - } - else if (ParameterSetName.Equals(GetRandomCommand.RandomNumberParameterSet, StringComparison.OrdinalIgnoreCase)) - { - if ((Maximum != null) && (Maximum.GetType().IsArray)) - { - InputObject = (object[])Maximum; - _effectiveParameterSet = MyParameterSet.RandomListItem; - } - else - { - _effectiveParameterSet = MyParameterSet.RandomNumber; - } - } - else - { - Debug.Assert(false, "Unrecognized parameter set"); - } - } - - return _effectiveParameterSet; - } - } - - #endregion Parameter set handling - - #region Error handling - - private void ThrowMinGreaterThanOrEqualMax(object minValue, object maxValue) - { - if (minValue == null) - { - throw PSTraceSource.NewArgumentNullException("min"); - } - - if (maxValue == null) - { - throw PSTraceSource.NewArgumentNullException("max"); - } - - ErrorRecord errorRecord = new( - new ArgumentException(string.Format( - CultureInfo.InvariantCulture, GetRandomCommandStrings.MinGreaterThanOrEqualMax, minValue, maxValue)), - "MinGreaterThanOrEqualMax", - ErrorCategory.InvalidArgument, - null); - - ThrowTerminatingError(errorRecord); - } - - #endregion - - #region Random generator state - - private static readonly ReaderWriterLockSlim s_runspaceGeneratorMapLock = new(); - - // 1-to-1 mapping of runspaces and random number generators - private static readonly Dictionary s_runspaceGeneratorMap = new(); - - private static void CurrentRunspace_StateChanged(object sender, RunspaceStateEventArgs e) - { - switch (e.RunspaceStateInfo.State) - { - case RunspaceState.Broken: - case RunspaceState.Closed: - try - { - GetRandomCommand.s_runspaceGeneratorMapLock.EnterWriteLock(); - GetRandomCommand.s_runspaceGeneratorMap.Remove(((Runspace)sender).InstanceId); - } - finally - { - GetRandomCommand.s_runspaceGeneratorMapLock.ExitWriteLock(); - } - - break; - } - } - - private PolymorphicRandomNumberGenerator _generator; - - /// - /// Gets and sets generator associated with the current runspace. - /// - private PolymorphicRandomNumberGenerator Generator - { - get - { - if (_generator == null) - { - Guid runspaceId = Context.CurrentRunspace.InstanceId; - - bool needToInitialize = false; - try - { - GetRandomCommand.s_runspaceGeneratorMapLock.EnterReadLock(); - needToInitialize = !GetRandomCommand.s_runspaceGeneratorMap.TryGetValue(runspaceId, out _generator); - } - finally - { - GetRandomCommand.s_runspaceGeneratorMapLock.ExitReadLock(); - } - - if (needToInitialize) - { - Generator = new PolymorphicRandomNumberGenerator(); - } - } - - return _generator; - } - - set - { - _generator = value; - Runspace myRunspace = Context.CurrentRunspace; - - try - { - GetRandomCommand.s_runspaceGeneratorMapLock.EnterWriteLock(); - if (!GetRandomCommand.s_runspaceGeneratorMap.ContainsKey(myRunspace.InstanceId)) - { - // make sure we won't leave the generator around after runspace exits - myRunspace.StateChanged += CurrentRunspace_StateChanged; - } - - GetRandomCommand.s_runspaceGeneratorMap[myRunspace.InstanceId] = _generator; - } - finally - { - GetRandomCommand.s_runspaceGeneratorMapLock.ExitWriteLock(); - } - } - } - - #endregion - - #region Common parameters - /// /// Seed used to reinitialize random numbers generator. /// @@ -201,194 +20,6 @@ private PolymorphicRandomNumberGenerator Generator [ValidateNotNull] public int? SetSeed { get; set; } - #endregion Common parameters - - #region Parameters for RandomNumberParameterSet - - /// - /// Maximum number to generate. - /// - [Parameter(ParameterSetName = RandomNumberParameterSet, Position = 0)] - public object Maximum { get; set; } - - /// - /// Minimum number to generate. - /// - [Parameter(ParameterSetName = RandomNumberParameterSet)] - public object Minimum { get; set; } - - private static bool IsInt(object o) - { - if (o == null || o is int) - { - return true; - } - - return false; - } - - private static bool IsInt64(object o) - { - if (o == null || o is Int64) - { - return true; - } - - return false; - } - - private static object ProcessOperand(object o) - { - if (o == null) - { - return null; - } - - PSObject pso = PSObject.AsPSObject(o); - object baseObject = pso.BaseObject; - - if (baseObject is string) - { - // The type argument passed in does not decide the number type we want to convert to. ScanNumber will return - // int/long/double based on the string form number passed in. - baseObject = System.Management.Automation.Language.Parser.ScanNumber((string)baseObject, typeof(int)); - } - - return baseObject; - } - - private static double ConvertToDouble(object o, double defaultIfNull) - { - if (o == null) - { - return defaultIfNull; - } - - double result = (double)LanguagePrimitives.ConvertTo(o, typeof(double), CultureInfo.InvariantCulture); - return result; - } - - #endregion - - #region Parameters and variables for RandomListItemParameterSet - - private List _chosenListItems; - private int _numberOfProcessedListItems; - - /// - /// List from which random elements are chosen. - /// - [Parameter(ParameterSetName = RandomListItemParameterSet, ValueFromPipeline = true, Position = 0, Mandatory = true)] - [Parameter(ParameterSetName = ShuffleParameterSet, ValueFromPipeline = true, Position = 0, Mandatory = true)] - [System.Management.Automation.AllowNull] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public object[] InputObject { get; set; } - - /// - /// Number of items to output (number of list items or of numbers). - /// - [Parameter(ParameterSetName = RandomNumberParameterSet)] - [Parameter(ParameterSetName = RandomListItemParameterSet)] - [ValidateRange(1, int.MaxValue)] - public int Count { get; set; } = 1; - - #endregion - - #region Shuffle parameter - - /// - /// Gets or sets whether the command should return all input objects in randomized order. - /// - [Parameter(ParameterSetName = ShuffleParameterSet, Mandatory = true)] - public SwitchParameter Shuffle { get; set; } - - #endregion - - #region Cmdlet processing methods - - private double GetRandomDouble(double minValue, double maxValue) - { - double randomNumber; - double diff = maxValue - minValue; - - // I couldn't find a better fix for bug #216893 then - // to test and retry if a random number falls outside the bounds - // because of floating-point-arithmetic inaccuracies. - // - // Performance in the normal case is not impacted much. - // In low-precision situations we should converge to a solution quickly - // (diff gets smaller at a quick pace). - - if (double.IsInfinity(diff)) - { - do - { - double r = Generator.NextDouble(); - randomNumber = minValue + r * maxValue - r * minValue; - } - while (randomNumber >= maxValue); - } - else - { - do - { - double r = Generator.NextDouble(); - randomNumber = minValue + r * diff; - diff *= r; - } - while (randomNumber >= maxValue); - } - - return randomNumber; - } - - /// - /// Get a random Int64 type number. - /// - /// - /// - /// - private Int64 GetRandomInt64(Int64 minValue, Int64 maxValue) - { - // Randomly generate eight bytes and convert the byte array to UInt64 - var buffer = new byte[sizeof(UInt64)]; - UInt64 randomUint64; - - BigInteger bigIntegerDiff = (BigInteger)maxValue - (BigInteger)minValue; - - // When the difference is less than int.MaxValue, use Random.Next(int, int) - if (bigIntegerDiff <= int.MaxValue) - { - int randomDiff = Generator.Next(0, (int)(maxValue - minValue)); - return minValue + randomDiff; - } - - // The difference of two Int64 numbers would not exceed UInt64.MaxValue, so it can be represented by a UInt64 number. - UInt64 uint64Diff = (UInt64)bigIntegerDiff; - - // Calculate the number of bits to represent the diff in type UInt64 - int bitsToRepresentDiff = 0; - UInt64 diffCopy = uint64Diff; - for (; diffCopy != 0; bitsToRepresentDiff++) - { - diffCopy >>= 1; - } - // Get the mask for the number of bits - UInt64 mask = (0xffffffffffffffff >> (64 - bitsToRepresentDiff)); - do - { - // Randomly fill the buffer - Generator.NextBytes(buffer); - randomUint64 = BitConverter.ToUInt64(buffer, 0); - - // Get the last 'bitsToRepresentDiff' number of random bits - randomUint64 &= mask; - } while (uint64Diff <= randomUint64); - - double randomNumber = minValue * 1.0 + randomUint64 * 1.0; - return (Int64)randomNumber; - } - /// /// This method implements the BeginProcessing method for get-random command. /// @@ -399,335 +30,7 @@ protected override void BeginProcessing() Generator = new PolymorphicRandomNumberGenerator(SetSeed.Value); } - if (EffectiveParameterSet == MyParameterSet.RandomNumber) - { - object maxOperand = ProcessOperand(Maximum); - object minOperand = ProcessOperand(Minimum); - - if (IsInt(maxOperand) && IsInt(minOperand)) - { - int minValue = minOperand != null ? (int)minOperand : 0; - int maxValue = maxOperand != null ? (int)maxOperand : int.MaxValue; - - if (minValue >= maxValue) - { - ThrowMinGreaterThanOrEqualMax(minValue, maxValue); - } - - for (int i = 0; i < Count; i++) - { - int randomNumber = Generator.Next(minValue, maxValue); - Debug.Assert(minValue <= randomNumber, "lower bound <= random number"); - Debug.Assert(randomNumber < maxValue, "random number < upper bound"); - - WriteObject(randomNumber); - } - } - else if ((IsInt64(maxOperand) || IsInt(maxOperand)) && (IsInt64(minOperand) || IsInt(minOperand))) - { - Int64 minValue = minOperand != null ? ((minOperand is Int64) ? (Int64)minOperand : (int)minOperand) : 0; - Int64 maxValue = maxOperand != null ? ((maxOperand is Int64) ? (Int64)maxOperand : (int)maxOperand) : Int64.MaxValue; - - if (minValue >= maxValue) - { - ThrowMinGreaterThanOrEqualMax(minValue, maxValue); - } - - for (int i = 0; i < Count; i++) - { - Int64 randomNumber = GetRandomInt64(minValue, maxValue); - Debug.Assert(minValue <= randomNumber, "lower bound <= random number"); - Debug.Assert(randomNumber < maxValue, "random number < upper bound"); - - WriteObject(randomNumber); - } - } - else - { - double minValue = (minOperand is double) ? (double)minOperand : ConvertToDouble(Minimum, 0.0); - double maxValue = (maxOperand is double) ? (double)maxOperand : ConvertToDouble(Maximum, double.MaxValue); - - if (minValue >= maxValue) - { - ThrowMinGreaterThanOrEqualMax(minValue, maxValue); - } - - for (int i = 0; i < Count; i++) - { - double randomNumber = GetRandomDouble(minValue, maxValue); - Debug.Assert(minValue <= randomNumber, "lower bound <= random number"); - Debug.Assert(randomNumber < maxValue, "random number < upper bound"); - - WriteObject(randomNumber); - } - } - } - else if (EffectiveParameterSet == MyParameterSet.RandomListItem) - { - _chosenListItems = new List(); - _numberOfProcessedListItems = 0; - } - } - - // rough proof that when choosing random K items out of N items - // each item has got K/N probability of being included in the final list - // - // probability that a particular item in chosenListItems is NOT going to be replaced - // when processing I-th input item [assumes I > K]: - // P_one_step(I) = 1 - ((K / I) * ((K - 1) / K) + ((I - K) / I) = (I - 1) / I - // <--A--> <-----B-----> <-----C-----> - // A - probability that I-th element is going to be replacing an element from chosenListItems - // (see (1) in the code below) - // B - probability that a particular element from chosenListItems is NOT going to be replaced - // (see (2) in the code below) - // C - probability that I-th element is NOT going to be replacing an element from chosenListItems - // (see (1) in the code below) - // - // probability that a particular item in chosenListItems is NOT going to be replaced - // when processing input items J through N [assumes J > K] - // P_removal(J) = Multiply(for I = J to N) P(I) = - // = ((J - 1) / J) * (J / (J + 1)) * ... * ((N - 2) / (N - 1)) * ((N - 1) / N) = - // = (J - 1) / N - // - // probability that when processing an element it is going to be put into chosenListItems - // P_insertion(I) = 1.0 when I <= K - see (3) in the code below - // P_insertion(I) = K/N otherwise - see (1) in the code below - // - // probability that a given element is going to be a part of the final list - // P_final(I) = P_insertion(I) * P_removal(max(I + 1, K + 1)) - // [for I <= K] = 1.0 * ((K + 1) - 1) / N = K / N - // [otherwise] = (K / I) * ((I + 1) - 1) / N = K / N - // - // which proves that P_final(I) = K / N for all values of I. QED. - - /// - /// This method implements the ProcessRecord method for get-random command. - /// - protected override void ProcessRecord() - { - if (EffectiveParameterSet == MyParameterSet.RandomListItem) - { - if (ParameterSetName == ShuffleParameterSet) - { - // this allows for $null to be in an array passed to InputObject - foreach (object item in InputObject ?? _nullInArray) - { - _chosenListItems.Add(item); - } - } - else - { - foreach (object item in InputObject ?? _nullInArray) - { - // (3) - if (_numberOfProcessedListItems < Count) - { - Debug.Assert(_chosenListItems.Count == _numberOfProcessedListItems, "Initial K elements should all be included in chosenListItems"); - _chosenListItems.Add(item); - } - else - { - Debug.Assert(_chosenListItems.Count == Count, "After processing K initial elements, the length of chosenItems should stay equal to K"); - - // (1) - if (Generator.Next(_numberOfProcessedListItems + 1) < Count) - { - // (2) - int indexToReplace = Generator.Next(_chosenListItems.Count); - _chosenListItems[indexToReplace] = item; - } - } - - _numberOfProcessedListItems++; - } - } - } - } - - /// - /// This method implements the EndProcessing method for get-random command. - /// - protected override void EndProcessing() - { - if (EffectiveParameterSet == MyParameterSet.RandomListItem) - { - // make sure the order is truly random - // (all permutations with the same probability) - // O(n) time - int n = _chosenListItems.Count; - for (int i = 0; i < n; i++) - { - // randomly choose j from [i...n) - int j = Generator.Next(i, n); - - WriteObject(_chosenListItems[j]); - - // remove the output object from consideration in the next iteration. - if (i != j) - { - _chosenListItems[j] = _chosenListItems[i]; - } - } - } - } - - #endregion Processing methods - } - - /// - /// Provides an adapter API for random numbers that may be either cryptographically random, or - /// generated with the regular pseudo-random number generator. Re-implementations of - /// methods using the NextBytes() primitive based on the CLR implementation: - /// https://referencesource.microsoft.com/#mscorlib/system/random.cs. - /// - internal class PolymorphicRandomNumberGenerator - { - /// - /// Initializes a new instance of the class. - /// - public PolymorphicRandomNumberGenerator() - { - _cryptographicGenerator = RandomNumberGenerator.Create(); - _pseudoGenerator = null; - } - - internal PolymorphicRandomNumberGenerator(int seed) - { - _cryptographicGenerator = null; - _pseudoGenerator = new Random(seed); - } - - private readonly Random _pseudoGenerator = null; - private readonly RandomNumberGenerator _cryptographicGenerator = null; - - /// - /// Generates a random floating-point number that is greater than or equal to 0.0, and less than 1.0. - /// - /// A random floating-point number that is greater than or equal to 0.0, and less than 1.0. - internal double NextDouble() - { - // According to the CLR source: - // "Including this division at the end gives us significantly improved random number distribution." - return Next() * (1.0 / Int32.MaxValue); - } - - /// - /// Generates a non-negative random integer. - /// - /// A non-negative random integer. - internal int Next() - { - int randomNumber; - - // The CLR implementation just fudges - // Int32.MaxValue down to (Int32.MaxValue - 1). This implementation - // errs on the side of correctness. - do - { - randomNumber = InternalSample(); - } - while (randomNumber == Int32.MaxValue); - - if (randomNumber < 0) - { - randomNumber += Int32.MaxValue; - } - - return randomNumber; - } - - /// - /// Returns a random integer that is within a specified range. - /// - /// The exclusive upper bound of the random number returned. - /// - internal int Next(int maxValue) - { - if (maxValue < 0) - { - throw new ArgumentOutOfRangeException(nameof(maxValue), GetRandomCommandStrings.MaxMustBeGreaterThanZeroApi); - } - - return Next(0, maxValue); - } - - /// - /// Returns a random integer that is within a specified range. - /// - /// The inclusive lower bound of the random number returned. - /// The exclusive upper bound of the random number returned. maxValue must be greater than or equal to minValue. - /// - public int Next(int minValue, int maxValue) - { - if (minValue > maxValue) - { - throw new ArgumentOutOfRangeException(nameof(minValue), GetRandomCommandStrings.MinGreaterThanOrEqualMaxApi); - } - - int randomNumber = 0; - - long range = (long)maxValue - (long)minValue; - if (range <= int.MaxValue) - { - randomNumber = ((int)(NextDouble() * range) + minValue); - } - else - { - double largeSample = InternalSampleLargeRange() * (1.0 / (2 * ((uint)Int32.MaxValue))); - randomNumber = (int)((long)(largeSample * range) + minValue); - } - - return randomNumber; - } - - /// - /// Fills the elements of a specified array of bytes with random numbers. - /// - /// The array to be filled. - internal void NextBytes(byte[] buffer) - { - if (_cryptographicGenerator != null) - { - _cryptographicGenerator.GetBytes(buffer); - } - else - { - _pseudoGenerator.NextBytes(buffer); - } - } - - /// - /// Samples a random integer. - /// - /// A random integer, using the full range of Int32. - private int InternalSample() - { - int randomNumber; - byte[] data = new byte[sizeof(int)]; - - NextBytes(data); - randomNumber = BitConverter.ToInt32(data, 0); - - return randomNumber; - } - - /// - /// Samples a random int when the range is large. This does - /// not need to be in the range of -Double.MaxValue .. Double.MaxValue, - /// just 0.. (2 * Int32.MaxValue) - 1 . - /// - /// - private double InternalSampleLargeRange() - { - double randomNumber; - - do - { - randomNumber = InternalSample(); - } while (randomNumber == Int32.MaxValue); - - randomNumber += Int32.MaxValue; - return randomNumber; + base.BeginProcessing(); } } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommandBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommandBase.cs new file mode 100644 index 00000000000..7d6e4e71e67 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommandBase.cs @@ -0,0 +1,719 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Numerics; +using System.Reflection; +using System.Security.Cryptography; +using System.Threading; + +using Debug = System.Management.Automation.Diagnostics; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// This class implements base class for `Get-Random` and `Get-SecureRandom` cmdlets. + /// + public class GetRandomCommandBase : PSCmdlet + { + #region Parameter set handling + + internal const string RandomNumberParameterSet = "RandomNumberParameterSet"; + private const string RandomListItemParameterSet = "RandomListItemParameterSet"; + private const string ShuffleParameterSet = "ShuffleParameterSet"; + + private static readonly object[] _nullInArray = new object[] { null }; + + private enum MyParameterSet + { + Unknown, + RandomNumber, + RandomListItem + } + + private MyParameterSet _effectiveParameterSet; + + private MyParameterSet EffectiveParameterSet + { + get + { + // cache MyParameterSet enum instead of doing string comparison every time + if (_effectiveParameterSet == MyParameterSet.Unknown) + { + if (MyInvocation.ExpectingInput && (Maximum == null) && (Minimum == null)) + { + _effectiveParameterSet = MyParameterSet.RandomListItem; + } + else if (ParameterSetName == GetRandomCommandBase.RandomListItemParameterSet + || ParameterSetName == GetRandomCommandBase.ShuffleParameterSet) + { + _effectiveParameterSet = MyParameterSet.RandomListItem; + } + else if (ParameterSetName.Equals(GetRandomCommandBase.RandomNumberParameterSet, StringComparison.OrdinalIgnoreCase)) + { + if ((Maximum != null) && Maximum.GetType().IsArray) + { + InputObject = (object[])Maximum; + _effectiveParameterSet = MyParameterSet.RandomListItem; + } + else + { + _effectiveParameterSet = MyParameterSet.RandomNumber; + } + } + else + { + Debug.Assert(false, "Unrecognized parameter set"); + } + } + + return _effectiveParameterSet; + } + } + + #endregion Parameter set handling + + #region Error handling + + private void ThrowMinGreaterThanOrEqualMax(object minValue, object maxValue) + { + if (minValue == null) + { + throw PSTraceSource.NewArgumentNullException("min"); + } + + if (maxValue == null) + { + throw PSTraceSource.NewArgumentNullException("max"); + } + + ErrorRecord errorRecord = new( + new ArgumentException(string.Format( + CultureInfo.InvariantCulture, GetRandomCommandStrings.MinGreaterThanOrEqualMax, minValue, maxValue)), + "MinGreaterThanOrEqualMax", + ErrorCategory.InvalidArgument, + null); + + ThrowTerminatingError(errorRecord); + } + + #endregion + + #region Random generator state + + private static readonly ReaderWriterLockSlim s_runspaceGeneratorMapLock = new(); + + // 1-to-1 mapping of cmdlet + runspacesId and random number generators + private static readonly Dictionary s_runspaceGeneratorMap = new(); + + private static void CurrentRunspace_StateChanged(object sender, RunspaceStateEventArgs e) + { + switch (e.RunspaceStateInfo.State) + { + case RunspaceState.Broken: + case RunspaceState.Closed: + try + { + GetRandomCommandBase.s_runspaceGeneratorMapLock.EnterWriteLock(); + GetRandomCommandBase.s_runspaceGeneratorMap.Remove(MethodBase.GetCurrentMethod().DeclaringType.Name + ((Runspace)sender).InstanceId.ToString()); + } + finally + { + GetRandomCommandBase.s_runspaceGeneratorMapLock.ExitWriteLock(); + } + + break; + } + } + + private PolymorphicRandomNumberGenerator _generator; + + /// + /// Gets and sets generator associated with the current cmdlet and runspace. + /// + internal PolymorphicRandomNumberGenerator Generator + { + get + { + if (_generator == null) + { + string runspaceId = Context.CurrentRunspace.InstanceId.ToString(); + + bool needToInitialize = false; + try + { + GetRandomCommandBase.s_runspaceGeneratorMapLock.EnterReadLock(); + needToInitialize = !GetRandomCommandBase.s_runspaceGeneratorMap.TryGetValue(this.GetType().Name + runspaceId, out _generator); + } + finally + { + GetRandomCommandBase.s_runspaceGeneratorMapLock.ExitReadLock(); + } + + if (needToInitialize) + { + Generator = new PolymorphicRandomNumberGenerator(); + } + } + + return _generator; + } + + set + { + _generator = value; + Runspace myRunspace = Context.CurrentRunspace; + + try + { + GetRandomCommandBase.s_runspaceGeneratorMapLock.EnterWriteLock(); + if (!GetRandomCommandBase.s_runspaceGeneratorMap.ContainsKey(this.GetType().Name + myRunspace.InstanceId.ToString())) + { + // make sure we won't leave the generator around after runspace exits + myRunspace.StateChanged += CurrentRunspace_StateChanged; + } + + GetRandomCommandBase.s_runspaceGeneratorMap[this.GetType().Name + myRunspace.InstanceId.ToString()] = _generator; + } + finally + { + GetRandomCommandBase.s_runspaceGeneratorMapLock.ExitWriteLock(); + } + } + } + + #endregion + + #region Parameters for RandomNumberParameterSet + + /// + /// Gets or sets the maximum number to generate. + /// + [Parameter(ParameterSetName = RandomNumberParameterSet, Position = 0)] + public object Maximum { get; set; } + + /// + /// Gets or sets the minimum number to generate. + /// + [Parameter(ParameterSetName = RandomNumberParameterSet)] + public object Minimum { get; set; } + + private static bool IsInt(object o) + { + if (o == null || o is int) + { + return true; + } + + return false; + } + + private static bool IsInt64(object o) + { + if (o == null || o is long) + { + return true; + } + + return false; + } + + private static object ProcessOperand(object o) + { + if (o == null) + { + return null; + } + + PSObject pso = PSObject.AsPSObject(o); + object baseObject = pso.BaseObject; + + if (baseObject is string) + { + // The type argument passed in does not decide the number type we want to convert to. ScanNumber will return + // int/long/double based on the string form number passed in. + baseObject = System.Management.Automation.Language.Parser.ScanNumber((string)baseObject, typeof(int)); + } + + return baseObject; + } + + private static double ConvertToDouble(object o, double defaultIfNull) + { + if (o == null) + { + return defaultIfNull; + } + + double result = (double)LanguagePrimitives.ConvertTo(o, typeof(double), CultureInfo.InvariantCulture); + return result; + } + + #endregion + + #region Parameters and variables for RandomListItemParameterSet + + private List _chosenListItems; + private int _numberOfProcessedListItems; + + /// + /// Gets or sets the list from which random elements are chosen. + /// + [Parameter(ParameterSetName = RandomListItemParameterSet, ValueFromPipeline = true, Position = 0, Mandatory = true)] + [Parameter(ParameterSetName = ShuffleParameterSet, ValueFromPipeline = true, Position = 0, Mandatory = true)] + [System.Management.Automation.AllowNull] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public object[] InputObject { get; set; } + + /// + /// Gets or sets the number of items to output (number of list items or of numbers). + /// + [Parameter(ParameterSetName = RandomNumberParameterSet)] + [Parameter(ParameterSetName = RandomListItemParameterSet)] + [ValidateRange(1, int.MaxValue)] + public int Count { get; set; } = 1; + + #endregion + + #region Shuffle parameter + + /// + /// Gets or sets whether the command should return all input objects in randomized order. + /// + [Parameter(ParameterSetName = ShuffleParameterSet, Mandatory = true)] + public SwitchParameter Shuffle { get; set; } + + #endregion + + #region Cmdlet processing methods + + private double GetRandomDouble(double minValue, double maxValue) + { + double randomNumber; + double diff = maxValue - minValue; + + // I couldn't find a better fix for bug #216893 then + // to test and retry if a random number falls outside the bounds + // because of floating-point-arithmetic inaccuracies. + // + // Performance in the normal case is not impacted much. + // In low-precision situations we should converge to a solution quickly + // (diff gets smaller at a quick pace). + if (double.IsInfinity(diff)) + { + do + { + double r = Generator.NextDouble(); + randomNumber = minValue + (r * maxValue) - (r * minValue); + } + while (randomNumber >= maxValue); + } + else + { + do + { + double r = Generator.NextDouble(); + randomNumber = minValue + (r * diff); + diff *= r; + } + while (randomNumber >= maxValue); + } + + return randomNumber; + } + + /// + /// Get a random Int64 type number. + /// + /// Minimum value. + /// Maximum value. + /// Rnadom long. + private long GetRandomInt64(long minValue, long maxValue) + { + // Randomly generate eight bytes and convert the byte array to UInt64 + var buffer = new byte[sizeof(ulong)]; + ulong randomUint64; + + BigInteger bigIntegerDiff = (BigInteger)maxValue - (BigInteger)minValue; + + // When the difference is less than int.MaxValue, use Random.Next(int, int) + if (bigIntegerDiff <= int.MaxValue) + { + int randomDiff = Generator.Next(0, (int)(maxValue - minValue)); + return minValue + randomDiff; + } + + // The difference of two Int64 numbers would not exceed UInt64.MaxValue, so it can be represented by a UInt64 number. + ulong uint64Diff = (ulong)bigIntegerDiff; + + // Calculate the number of bits to represent the diff in type UInt64 + int bitsToRepresentDiff = 0; + ulong diffCopy = uint64Diff; + for (; diffCopy != 0; bitsToRepresentDiff++) + { + diffCopy >>= 1; + } + + // Get the mask for the number of bits + ulong mask = 0xffffffffffffffff >> (64 - bitsToRepresentDiff); + do + { + // Randomly fill the buffer + Generator.NextBytes(buffer); + randomUint64 = BitConverter.ToUInt64(buffer, 0); + + // Get the last 'bitsToRepresentDiff' number of random bits + randomUint64 &= mask; + } while (uint64Diff <= randomUint64); + + double randomNumber = (minValue * 1.0) + (randomUint64 * 1.0); + return (long)randomNumber; + } + + /// + /// This method implements the BeginProcessing method for derived cmdlets. + /// + protected override void BeginProcessing() + { + if (EffectiveParameterSet == MyParameterSet.RandomNumber) + { + object maxOperand = ProcessOperand(Maximum); + object minOperand = ProcessOperand(Minimum); + + if (IsInt(maxOperand) && IsInt(minOperand)) + { + int minValue = minOperand != null ? (int)minOperand : 0; + int maxValue = maxOperand != null ? (int)maxOperand : int.MaxValue; + + if (minValue >= maxValue) + { + ThrowMinGreaterThanOrEqualMax(minValue, maxValue); + } + + for (int i = 0; i < Count; i++) + { + int randomNumber = Generator.Next(minValue, maxValue); + Debug.Assert(minValue <= randomNumber, "lower bound <= random number"); + Debug.Assert(randomNumber < maxValue, "random number < upper bound"); + + WriteObject(randomNumber); + } + } + else if ((IsInt64(maxOperand) || IsInt(maxOperand)) && (IsInt64(minOperand) || IsInt(minOperand))) + { + long minValue = minOperand != null ? ((minOperand is long) ? (long)minOperand : (int)minOperand) : 0; + long maxValue = maxOperand != null ? ((maxOperand is long) ? (long)maxOperand : (int)maxOperand) : long.MaxValue; + + if (minValue >= maxValue) + { + ThrowMinGreaterThanOrEqualMax(minValue, maxValue); + } + + for (int i = 0; i < Count; i++) + { + long randomNumber = GetRandomInt64(minValue, maxValue); + Debug.Assert(minValue <= randomNumber, "lower bound <= random number"); + Debug.Assert(randomNumber < maxValue, "random number < upper bound"); + + WriteObject(randomNumber); + } + } + else + { + double minValue = (minOperand is double) ? (double)minOperand : ConvertToDouble(Minimum, 0.0); + double maxValue = (maxOperand is double) ? (double)maxOperand : ConvertToDouble(Maximum, double.MaxValue); + + if (minValue >= maxValue) + { + ThrowMinGreaterThanOrEqualMax(minValue, maxValue); + } + + for (int i = 0; i < Count; i++) + { + double randomNumber = GetRandomDouble(minValue, maxValue); + Debug.Assert(minValue <= randomNumber, "lower bound <= random number"); + Debug.Assert(randomNumber < maxValue, "random number < upper bound"); + + WriteObject(randomNumber); + } + } + } + else if (EffectiveParameterSet == MyParameterSet.RandomListItem) + { + _chosenListItems = new List(); + _numberOfProcessedListItems = 0; + } + } + + // rough proof that when choosing random K items out of N items + // each item has got K/N probability of being included in the final list + // + // probability that a particular item in chosenListItems is NOT going to be replaced + // when processing I-th input item [assumes I > K]: + // P_one_step(I) = 1 - ((K / I) * ((K - 1) / K) + ((I - K) / I) = (I - 1) / I + // <--A--> <-----B-----> <-----C-----> + // A - probability that I-th element is going to be replacing an element from chosenListItems + // (see (1) in the code below) + // B - probability that a particular element from chosenListItems is NOT going to be replaced + // (see (2) in the code below) + // C - probability that I-th element is NOT going to be replacing an element from chosenListItems + // (see (1) in the code below) + // + // probability that a particular item in chosenListItems is NOT going to be replaced + // when processing input items J through N [assumes J > K] + // P_removal(J) = Multiply(for I = J to N) P(I) = + // = ((J - 1) / J) * (J / (J + 1)) * ... * ((N - 2) / (N - 1)) * ((N - 1) / N) = + // = (J - 1) / N + // + // probability that when processing an element it is going to be put into chosenListItems + // P_insertion(I) = 1.0 when I <= K - see (3) in the code below + // P_insertion(I) = K/N otherwise - see (1) in the code below + // + // probability that a given element is going to be a part of the final list + // P_final(I) = P_insertion(I) * P_removal(max(I + 1, K + 1)) + // [for I <= K] = 1.0 * ((K + 1) - 1) / N = K / N + // [otherwise] = (K / I) * ((I + 1) - 1) / N = K / N + // + // which proves that P_final(I) = K / N for all values of I. QED. + + /// + /// This method implements the ProcessRecord method for derived cmdlets. + /// + protected override void ProcessRecord() + { + if (EffectiveParameterSet == MyParameterSet.RandomListItem) + { + if (ParameterSetName == ShuffleParameterSet) + { + // this allows for $null to be in an array passed to InputObject + foreach (object item in InputObject ?? _nullInArray) + { + _chosenListItems.Add(item); + } + } + else + { + foreach (object item in InputObject ?? _nullInArray) + { + // (3) + if (_numberOfProcessedListItems < Count) + { + Debug.Assert(_chosenListItems.Count == _numberOfProcessedListItems, "Initial K elements should all be included in chosenListItems"); + _chosenListItems.Add(item); + } + else + { + Debug.Assert(_chosenListItems.Count == Count, "After processing K initial elements, the length of chosenItems should stay equal to K"); + + // (1) + if (Generator.Next(_numberOfProcessedListItems + 1) < Count) + { + // (2) + int indexToReplace = Generator.Next(_chosenListItems.Count); + _chosenListItems[indexToReplace] = item; + } + } + + _numberOfProcessedListItems++; + } + } + } + } + + /// + /// This method implements the EndProcessing method for derived cmdlets. + /// + protected override void EndProcessing() + { + if (EffectiveParameterSet == MyParameterSet.RandomListItem) + { + // make sure the order is truly random + // (all permutations with the same probability) + // O(n) time + int n = _chosenListItems.Count; + for (int i = 0; i < n; i++) + { + // randomly choose j from [i...n) + int j = Generator.Next(i, n); + + WriteObject(_chosenListItems[j]); + + // remove the output object from consideration in the next iteration. + if (i != j) + { + _chosenListItems[j] = _chosenListItems[i]; + } + } + } + } + + #endregion Processing methods + } + + /// + /// Provides an adapter API for random numbers that may be either cryptographically random, or + /// generated with the regular pseudo-random number generator. Re-implementations of + /// methods using the NextBytes() primitive based on the CLR implementation: + /// https://referencesource.microsoft.com/#mscorlib/system/random.cs. + /// + internal class PolymorphicRandomNumberGenerator + { + /// + /// Initializes a new instance of the class. + /// + public PolymorphicRandomNumberGenerator() + { + _cryptographicGenerator = RandomNumberGenerator.Create(); + _pseudoGenerator = null; + } + + /// + /// Initializes a new instance of the using pseudorandom generator instead of the cryptographic one. + /// + /// The seed value. + internal PolymorphicRandomNumberGenerator(int seed) + { + _cryptographicGenerator = null; + _pseudoGenerator = new Random(seed); + } + + private readonly Random _pseudoGenerator = null; + private readonly RandomNumberGenerator _cryptographicGenerator = null; + + /// + /// Generates a random floating-point number that is greater than or equal to 0.0, and less than 1.0. + /// + /// A random floating-point number that is greater than or equal to 0.0, and less than 1.0. + internal double NextDouble() + { + // According to the CLR source: + // "Including this division at the end gives us significantly improved random number distribution." + return Next() * (1.0 / int.MaxValue); + } + + /// + /// Generates a non-negative random integer. + /// + /// A non-negative random integer. + internal int Next() + { + int randomNumber; + + // The CLR implementation just fudges + // Int32.MaxValue down to (Int32.MaxValue - 1). This implementation + // errs on the side of correctness. + do + { + randomNumber = InternalSample(); + } + while (randomNumber == int.MaxValue); + + if (randomNumber < 0) + { + randomNumber += int.MaxValue; + } + + return randomNumber; + } + + /// + /// Returns a random integer that is within a specified range. + /// + /// The exclusive upper bound of the random number returned. + /// Next random integer. + internal int Next(int maxValue) + { + if (maxValue < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxValue), GetRandomCommandStrings.MaxMustBeGreaterThanZeroApi); + } + + return Next(0, maxValue); + } + + /// + /// Returns a random integer that is within a specified range. + /// + /// The inclusive lower bound of the random number returned. + /// The exclusive upper bound of the random number returned. maxValue must be greater than or equal to minValue. + /// Next random integer. + public int Next(int minValue, int maxValue) + { + if (minValue > maxValue) + { + throw new ArgumentOutOfRangeException(nameof(minValue), GetRandomCommandStrings.MinGreaterThanOrEqualMaxApi); + } + + int randomNumber = 0; + + long range = (long)maxValue - (long)minValue; + if (range <= int.MaxValue) + { + randomNumber = (int)(NextDouble() * range) + minValue; + } + else + { + double largeSample = InternalSampleLargeRange() * (1.0 / (2 * ((uint)int.MaxValue))); + randomNumber = (int)((long)(largeSample * range) + minValue); + } + + return randomNumber; + } + + /// + /// Fills the elements of a specified array of bytes with random numbers. + /// + /// The array to be filled. + internal void NextBytes(byte[] buffer) + { + if (_cryptographicGenerator != null) + { + _cryptographicGenerator.GetBytes(buffer); + } + else + { + _pseudoGenerator.NextBytes(buffer); + } + } + + /// + /// Samples a random integer. + /// + /// A random integer, using the full range of Int32. + private int InternalSample() + { + int randomNumber; + byte[] data = new byte[sizeof(int)]; + + NextBytes(data); + randomNumber = BitConverter.ToInt32(data, 0); + + return randomNumber; + } + + /// + /// Samples a random int when the range is large. This does + /// not need to be in the range of -Double.MaxValue .. Double.MaxValue, + /// just 0.. (2 * Int32.MaxValue) - 1 . + /// + /// A random double. + private double InternalSampleLargeRange() + { + double randomNumber; + + do + { + randomNumber = InternalSample(); + } + while (randomNumber == int.MaxValue); + + randomNumber += int.MaxValue; + return randomNumber; + } + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetSecureRandomCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetSecureRandomCommand.cs new file mode 100644 index 00000000000..e0ea7e68dbf --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetSecureRandomCommand.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Management.Automation; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// This class implements `Get-SecureRandom` cmdlet. + /// + [Cmdlet(VerbsCommon.Get, "SecureRandom", DefaultParameterSetName = GetRandomCommandBase.RandomNumberParameterSet, + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2235055", RemotingCapability = RemotingCapability.None)] + [OutputType(typeof(int), typeof(long), typeof(double))] + public sealed class GetSecureRandomCommand : GetRandomCommandBase + { + // nothing unique from base class + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUnique.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUnique.cs index b43a870dccb..09bc78d9693 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUnique.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUnique.cs @@ -50,6 +50,13 @@ public SwitchParameter OnType } private bool _onType = false; + + /// + /// Gets or sets case insensitive switch for string comparison. + /// + [Parameter] + public SwitchParameter CaseInsensitive { get; set; } + #endregion Parameters #region Overrides @@ -72,15 +79,12 @@ protected override void ProcessRecord() else if (AsString) { string inputString = InputObject.ToString(); - if (_lastObjectAsString == null) - { - _lastObjectAsString = _lastObject.ToString(); - } + _lastObjectAsString ??= _lastObject.ToString(); if (string.Equals( inputString, _lastObjectAsString, - StringComparison.CurrentCulture)) + CaseInsensitive.IsPresent ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture)) { isUnique = false; } @@ -91,13 +95,10 @@ protected override void ProcessRecord() } else // compare as objects { - if (_comparer == null) - { - _comparer = new ObjectCommandComparer( - true, // ascending (doesn't matter) - CultureInfo.CurrentCulture, - true); // case-sensitive - } + _comparer ??= new ObjectCommandComparer( + ascending: true, + CultureInfo.CurrentCulture, + caseSensitive: !CaseInsensitive.IsPresent); isUnique = (_comparer.Compare(InputObject, _lastObject) != 0); } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetVerbCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetVerbCommand.cs index 1ec93b0f1d7..61a0fe1a390 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetVerbCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetVerbCommand.cs @@ -1,10 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.ObjectModel; using System.Management.Automation; -using System.Reflection; +using static System.Management.Automation.Verbs; namespace Microsoft.PowerShell.Commands { @@ -19,6 +17,7 @@ public class GetVerbCommand : Cmdlet /// Optional Verb filter. /// [Parameter(ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0)] + [ArgumentCompleter(typeof(VerbArgumentCompleter))] public string[] Verb { get; set; @@ -39,45 +38,9 @@ public string[] Group /// protected override void ProcessRecord() { - Type[] verbTypes = new Type[] { typeof(VerbsCommon), typeof(VerbsCommunications), typeof(VerbsData), - typeof(VerbsDiagnostic), typeof(VerbsLifecycle), typeof(VerbsOther), typeof(VerbsSecurity) }; - - Collection matchingVerbs = SessionStateUtilities.CreateWildcardsFromStrings( - this.Verb, - WildcardOptions.IgnoreCase - ); - - foreach (Type type in verbTypes) + foreach (VerbInfo verb in FilterByVerbsAndGroups(Verb, Group)) { - string groupName = type.Name.Substring(5); - if (this.Group != null) - { - if (!SessionStateUtilities.CollectionContainsValue(this.Group, groupName, StringComparer.OrdinalIgnoreCase)) - { - continue; - } - } - - foreach (FieldInfo field in type.GetFields()) - { - if (field.IsLiteral) - { - if (this.Verb != null) - { - if (!SessionStateUtilities.MatchesAnyWildcardPattern(field.Name, matchingVerbs, false)) - { - continue; - } - } - - VerbInfo verb = new(); - verb.Verb = field.Name; - verb.AliasPrefix = VerbAliasPrefixes.GetVerbAliasPrefix(field.Name); - verb.Group = groupName; - verb.Description = VerbDescriptions.GetVerbDescription(field.Name); - WriteObject(verb); - } - } + WriteObject(verb); } } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Group-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Group-Object.cs index d63ff694281..1f527258939 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Group-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Group-Object.cs @@ -153,7 +153,7 @@ private static string BuildName(List propValues) foreach (object item in propertyValueItems) { - sb.AppendFormat(CultureInfo.InvariantCulture, "{0}, ", item.ToString()); + sb.Append(CultureInfo.CurrentCulture, $"{item}, "); } sb = sb.Length > length ? sb.Remove(sb.Length - 2, 2) : sb; @@ -161,7 +161,7 @@ private static string BuildName(List propValues) } else { - sb.AppendFormat(CultureInfo.InvariantCulture, "{0}, ", propValuePropertyValue.ToString()); + sb.Append(CultureInfo.CurrentCulture, $"{propValuePropertyValue}, "); } } } @@ -392,10 +392,7 @@ protected override void ProcessRecord() if (!_hasProcessedFirstInputObject) { - if (Property == null) - { - Property = OrderByProperty.GetDefaultKeyPropertySet(InputObject); - } + Property ??= OrderByProperty.GetDefaultKeyPropertySet(InputObject); _orderByProperty.ProcessExpressionParameter(this, Property); @@ -443,7 +440,7 @@ private void UpdateOrderPropertyTypeInfo(List curren { if (_propertyTypesCandidate == null) { - _propertyTypesCandidate = currentEntryOrderValues.Select(c => PSObject.Base(c.PropertyValue)?.GetType()).ToArray(); + _propertyTypesCandidate = currentEntryOrderValues.Select(static c => PSObject.Base(c.PropertyValue)?.GetType()).ToArray(); return; } @@ -491,7 +488,7 @@ protected override void EndProcessing() { // using OrderBy to get stable sort. // fast path when we only have the same object types to group - foreach (var entry in _entriesToOrder.OrderBy(e => e, _orderByPropertyComparer)) + foreach (var entry in _entriesToOrder.Order(_orderByPropertyComparer)) { DoOrderedGrouping(entry, NoElement, _groups, _tupleToGroupInfoMappingDictionary, _orderByPropertyComparer); if (Stopping) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs index d772258227b..663ff015556 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. /// [Parameter] - [ArgumentToEncodingTransformationAttribute()] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] public Encoding Encoding { @@ -90,7 +90,7 @@ public Encoding Encoding } } - private Encoding _encoding = ClrFacade.GetDefaultEncoding(); + private Encoding _encoding = Encoding.Default; #endregion Parameters @@ -443,10 +443,7 @@ public string[] Module set { - if (value == null) - { - value = Array.Empty(); - } + value ??= Array.Empty(); _PSSnapins = value; _commandParameterSpecified = true; @@ -729,8 +726,7 @@ private ErrorRecord GetErrorFromRemoteCommand(string commandName, RuntimeExcepti // // handle recognized types of exceptions first // - RemoteException remoteException = runtimeException as RemoteException; - if ((remoteException != null) && (remoteException.SerializedRemoteException != null)) + if ((runtimeException is RemoteException remoteException) && (remoteException.SerializedRemoteException != null)) { if (Deserializer.IsInstanceOfType(remoteException.SerializedRemoteException, typeof(CommandNotFoundException))) { @@ -1044,10 +1040,7 @@ private List RehydrateList(string commandName, PSObject deserializedObject private List RehydrateList(string commandName, object deserializedList, Func itemRehydrator) { - if (itemRehydrator == null) - { - itemRehydrator = (PSObject pso) => ConvertTo(commandName, pso); - } + itemRehydrator ??= (PSObject pso) => ConvertTo(commandName, pso); List result = null; @@ -1068,17 +1061,14 @@ private List RehydrateList(string commandName, object deserializedList, Fu private Dictionary RehydrateDictionary( string commandName, - PSObject deserializedObject, + PSObject deserializedObject, string propertyName, Func valueRehydrator) { Dbg.Assert(deserializedObject != null, "deserializedObject parameter != null"); Dbg.Assert(!string.IsNullOrEmpty(propertyName), "propertyName parameter != null"); - if (valueRehydrator == null) - { - valueRehydrator = (PSObject pso) => ConvertTo(commandName, pso); - } + valueRehydrator ??= (PSObject pso) => ConvertTo(commandName, pso); Dictionary result = new(); PSPropertyInfo deserializedDictionaryProperty = deserializedObject.Properties[propertyName]; @@ -1310,13 +1300,23 @@ private ParameterMetadata RehydrateParameterMetadata(PSObject deserializedParame parameterType); } - private static bool IsProxyForCmdlet(Dictionary parameters) + private bool IsProxyForCmdlet(Dictionary parameters) { // we are not sending CmdletBinding/DefaultParameterSet over the wire anymore // we need to infer IsProxyForCmdlet from presence of all common parameters - foreach (string commonParameterName in Cmdlet.CommonParameters) + // need to exclude `ProgressAction` which may not exist for downlevel platforms + bool isDownLevelRemote = Session.Runspace is RemoteRunspace remoteRunspace + && remoteRunspace.ServerVersion is not null + && remoteRunspace.ServerVersion <= new Version(7, 3); + + foreach (string commonParameterName in CommonParameters) { + if (isDownLevelRemote && commonParameterName == "ProgressAction") + { + continue; + } + if (!parameters.ContainsKey(commonParameterName)) { return false; @@ -1584,8 +1584,7 @@ private PowerShell BuildPowerShellForGetFormatData() powerShell.AddParameter("TypeName", this.FormatTypeName); // For remote PS version 5.1 and greater, we need to include the new -PowerShellVersion parameter - RemoteRunspace remoteRunspace = Session.Runspace as RemoteRunspace; - if ((remoteRunspace != null) && (remoteRunspace.ServerVersion != null) && + if ((Session.Runspace is RemoteRunspace remoteRunspace) && (remoteRunspace.ServerVersion != null) && (remoteRunspace.ServerVersion >= new Version(5, 1))) { powerShell.AddParameter("PowerShellVersion", PSVersionInfo.PSVersion); @@ -1951,20 +1950,17 @@ internal ImplicitRemotingCodeGenerator( /// Connection URI associated with the remote runspace. private string GetConnectionString() { - WSManConnectionInfo connectionInfo = _remoteRunspaceInfo.Runspace.ConnectionInfo as WSManConnectionInfo; - if (connectionInfo != null) + if (_remoteRunspaceInfo.Runspace.ConnectionInfo is WSManConnectionInfo connectionInfo) { return connectionInfo.ConnectionUri.ToString(); } - VMConnectionInfo vmConnectionInfo = _remoteRunspaceInfo.Runspace.ConnectionInfo as VMConnectionInfo; - if (vmConnectionInfo != null) + if (_remoteRunspaceInfo.Runspace.ConnectionInfo is VMConnectionInfo vmConnectionInfo) { return vmConnectionInfo.ComputerName; } - ContainerConnectionInfo containerConnectionInfo = _remoteRunspaceInfo.Runspace.ConnectionInfo as ContainerConnectionInfo; - if (containerConnectionInfo != null) + if (_remoteRunspaceInfo.Runspace.ConnectionInfo is ContainerConnectionInfo containerConnectionInfo) { return containerConnectionInfo.ComputerName; } @@ -2024,7 +2020,6 @@ private static void GenerateSectionSeparator(TextWriter writer) PrivateData = @{{ ImplicitRemoting = $true - ImplicitSessionId = '{4}' }} }} "; @@ -2043,8 +2038,7 @@ private void GenerateManifest(TextWriter writer, string psm1fileName, string for CodeGeneration.EscapeSingleQuotedStringContent(_moduleGuid.ToString()), CodeGeneration.EscapeSingleQuotedStringContent(StringUtil.Format(ImplicitRemotingStrings.ProxyModuleDescription, this.GetConnectionString())), CodeGeneration.EscapeSingleQuotedStringContent(Path.GetFileName(psm1fileName)), - CodeGeneration.EscapeSingleQuotedStringContent(Path.GetFileName(formatPs1xmlFileName)), - _remoteRunspaceInfo.InstanceId); + CodeGeneration.EscapeSingleQuotedStringContent(Path.GetFileName(formatPs1xmlFileName))); } #endregion @@ -2111,7 +2105,9 @@ private void GenerateModuleHeader(TextWriter writer) // In Win8, we are no longer loading all assemblies by default. // So we need to use the fully qualified name when accessing a type in that assembly - string versionOfScriptGenerator = "[" + typeof(ExportPSSessionCommand).AssemblyQualifiedName + "]" + "::VersionOfScriptGenerator"; + Type type = typeof(ExportPSSessionCommand); + string asmName = type.Assembly.GetName().Name; + string versionOfScriptGenerator = $"[{type.FullName}, {asmName}]::VersionOfScriptGenerator"; GenerateTopComment(writer); writer.Write( HeaderTemplate, @@ -2253,64 +2249,68 @@ private string GenerateNewPSSessionOption() { StringBuilder result = new("& $script:NewPSSessionOption "); - RunspaceConnectionInfo runspaceConnectionInfo = _remoteRunspaceInfo.Runspace.ConnectionInfo as RunspaceConnectionInfo; - if (runspaceConnectionInfo != null) + if (_remoteRunspaceInfo.Runspace.ConnectionInfo is RunspaceConnectionInfo runspaceConnectionInfo) { - result.AppendFormat(null, "-Culture '{0}' ", CodeGeneration.EscapeSingleQuotedStringContent(runspaceConnectionInfo.Culture.ToString())); - result.AppendFormat(null, "-UICulture '{0}' ", CodeGeneration.EscapeSingleQuotedStringContent(runspaceConnectionInfo.UICulture.ToString())); + result.Append(null, $"-Culture '{CodeGeneration.EscapeSingleQuotedStringContent(runspaceConnectionInfo.Culture.ToString())}' "); + result.Append(null, $"-UICulture '{CodeGeneration.EscapeSingleQuotedStringContent(runspaceConnectionInfo.UICulture.ToString())}' "); - result.AppendFormat(null, "-CancelTimeOut {0} ", runspaceConnectionInfo.CancelTimeout); - result.AppendFormat(null, "-IdleTimeOut {0} ", runspaceConnectionInfo.IdleTimeout); - result.AppendFormat(null, "-OpenTimeOut {0} ", runspaceConnectionInfo.OpenTimeout); - result.AppendFormat(null, "-OperationTimeOut {0} ", runspaceConnectionInfo.OperationTimeout); + result.Append(null, $"-CancelTimeOut {runspaceConnectionInfo.CancelTimeout} "); + result.Append(null, $"-IdleTimeOut {runspaceConnectionInfo.IdleTimeout} "); + result.Append(null, $"-OpenTimeOut {runspaceConnectionInfo.OpenTimeout} "); + result.Append(null, $"-OperationTimeOut {runspaceConnectionInfo.OperationTimeout} "); } - WSManConnectionInfo wsmanConnectionInfo = _remoteRunspaceInfo.Runspace.ConnectionInfo as WSManConnectionInfo; - if (wsmanConnectionInfo != null) + if (_remoteRunspaceInfo.Runspace.ConnectionInfo is WSManConnectionInfo wsmanConnectionInfo) { - if (!wsmanConnectionInfo.UseCompression) { result.Append("-NoCompression "); } + if (!wsmanConnectionInfo.UseCompression) + { + result.Append("-NoCompression "); + } - if (wsmanConnectionInfo.NoEncryption) { result.Append("-NoEncryption "); } + if (wsmanConnectionInfo.NoEncryption) + { + result.Append("-NoEncryption "); + } - if (wsmanConnectionInfo.NoMachineProfile) { result.Append("-NoMachineProfile "); } + if (wsmanConnectionInfo.NoMachineProfile) + { + result.Append("-NoMachineProfile "); + } - if (wsmanConnectionInfo.UseUTF16) { result.Append("-UseUTF16 "); } + if (wsmanConnectionInfo.UseUTF16) + { + result.Append("-UseUTF16 "); + } - if (wsmanConnectionInfo.SkipCACheck) { result.Append("-SkipCACheck "); } + if (wsmanConnectionInfo.SkipCACheck) + { + result.Append("-SkipCACheck "); + } - if (wsmanConnectionInfo.SkipCNCheck) { result.Append("-SkipCNCheck "); } + if (wsmanConnectionInfo.SkipCNCheck) + { + result.Append("-SkipCNCheck "); + } - if (wsmanConnectionInfo.SkipRevocationCheck) { result.Append("-SkipRevocationCheck "); } + if (wsmanConnectionInfo.SkipRevocationCheck) + { + result.Append("-SkipRevocationCheck "); + } if (wsmanConnectionInfo.MaximumReceivedDataSizePerCommand.HasValue) { - result.AppendFormat( - CultureInfo.InvariantCulture, - "-MaximumReceivedDataSizePerCommand {0} ", - wsmanConnectionInfo.MaximumReceivedDataSizePerCommand.Value); + result.Append(CultureInfo.InvariantCulture, $"-MaximumReceivedDataSizePerCommand {wsmanConnectionInfo.MaximumReceivedDataSizePerCommand.Value} "); } if (wsmanConnectionInfo.MaximumReceivedObjectSize.HasValue) { - result.AppendFormat( - CultureInfo.InvariantCulture, - "-MaximumReceivedObjectSize {0} ", - wsmanConnectionInfo.MaximumReceivedObjectSize.Value); + result.Append(CultureInfo.InvariantCulture, $"-MaximumReceivedObjectSize {wsmanConnectionInfo.MaximumReceivedObjectSize.Value} "); } - result.AppendFormat( - CultureInfo.InvariantCulture, - "-MaximumRedirection {0} ", - wsmanConnectionInfo.MaximumConnectionRedirectionCount); + result.Append(CultureInfo.InvariantCulture, $"-MaximumRedirection {wsmanConnectionInfo.MaximumConnectionRedirectionCount} "); - result.AppendFormat( - CultureInfo.InvariantCulture, - "-ProxyAccessType {0} ", - wsmanConnectionInfo.ProxyAccessType.ToString()); - result.AppendFormat( - CultureInfo.InvariantCulture, - "-ProxyAuthentication {0} ", - wsmanConnectionInfo.ProxyAuthentication.ToString()); + result.Append(CultureInfo.InvariantCulture, $"-ProxyAccessType {wsmanConnectionInfo.ProxyAccessType} "); + result.Append(CultureInfo.InvariantCulture, $"-ProxyAuthentication {wsmanConnectionInfo.ProxyAuthentication} "); result.Append(this.GenerateProxyCredentialParameter(wsmanConnectionInfo)); } @@ -2525,8 +2525,7 @@ private string GenerateReimportingOfModules() private string GenerateNewRunspaceExpression() { - VMConnectionInfo vmConnectionInfo = _remoteRunspaceInfo.Runspace.ConnectionInfo as VMConnectionInfo; - if (vmConnectionInfo != null) + if (_remoteRunspaceInfo.Runspace.ConnectionInfo is VMConnectionInfo vmConnectionInfo) { string vmConfigurationName = vmConnectionInfo.ConfigurationName; return string.Format( @@ -2538,8 +2537,7 @@ private string GenerateNewRunspaceExpression() } else { - ContainerConnectionInfo containerConnectionInfo = _remoteRunspaceInfo.Runspace.ConnectionInfo as ContainerConnectionInfo; - if (containerConnectionInfo != null) + if (_remoteRunspaceInfo.Runspace.ConnectionInfo is ContainerConnectionInfo containerConnectionInfo) { string containerConfigurationName = containerConnectionInfo.ContainerProc.ConfigurationName; return string.Format( @@ -2581,19 +2579,16 @@ private string GenerateNewRunspaceExpression() /// private string GenerateConnectionStringForNewRunspace() { - WSManConnectionInfo connectionInfo = _remoteRunspaceInfo.Runspace.ConnectionInfo as WSManConnectionInfo; - if (connectionInfo == null) + if (_remoteRunspaceInfo.Runspace.ConnectionInfo is not WSManConnectionInfo connectionInfo) { - VMConnectionInfo vmConnectionInfo = _remoteRunspaceInfo.Runspace.ConnectionInfo as VMConnectionInfo; - if (vmConnectionInfo != null) + if (_remoteRunspaceInfo.Runspace.ConnectionInfo is VMConnectionInfo vmConnectionInfo) { return string.Format(CultureInfo.InvariantCulture, VMIdParameterTemplate, CodeGeneration.EscapeSingleQuotedStringContent(vmConnectionInfo.VMGuid.ToString())); } - ContainerConnectionInfo containerConnectionInfo = _remoteRunspaceInfo.Runspace.ConnectionInfo as ContainerConnectionInfo; - if (containerConnectionInfo != null) + if (_remoteRunspaceInfo.Runspace.ConnectionInfo is ContainerConnectionInfo containerConnectionInfo) { return string.Format(CultureInfo.InvariantCulture, ContainerIdParameterTemplate, @@ -2613,15 +2608,13 @@ private string GenerateConnectionStringForNewRunspace() CodeGeneration.EscapeSingleQuotedStringContent(connectionInfo.AppName), connectionInfo.UseDefaultWSManPort ? string.Empty : - string.Format(CultureInfo.InvariantCulture, - "-Port {0} ", connectionInfo.Port), + string.Create(CultureInfo.InvariantCulture, $"-Port {connectionInfo.Port} "), isSSLSpecified ? "-useSSL" : string.Empty); } else { - return string.Format(CultureInfo.InvariantCulture, - "-connectionUri '{0}'", - CodeGeneration.EscapeSingleQuotedStringContent(GetConnectionString())); + string connectionString = CodeGeneration.EscapeSingleQuotedStringContent(GetConnectionString()); + return string.Create(CultureInfo.InvariantCulture, $"-connectionUri '{connectionString}'"); } } @@ -2749,7 +2742,7 @@ function Get-PSImplicitRemotingClientSideParameters $clientSideParameters = @{} - $parametersToLeaveRemote = 'ErrorAction', 'WarningAction', 'InformationAction' + $parametersToLeaveRemote = 'ErrorAction', 'WarningAction', 'InformationAction', 'ProgressAction' Modify-PSImplicitRemotingParameters $clientSideParameters $PSBoundParameters 'AsJob' if ($proxyForCmdlet) @@ -2825,13 +2818,14 @@ private void GenerateHelperFunctions(TextWriter writer) $clientSideParameters = Get-PSImplicitRemotingClientSideParameters $PSBoundParameters ${8} - $scriptCmd = {{ & $script:InvokeCommand ` - @clientSideParameters ` - -HideComputerName ` - -Session (Get-PSImplicitRemotingSession -CommandName '{0}') ` - -Arg ('{0}', $PSBoundParameters, $positionalArguments) ` - -Script {{ param($name, $boundParams, $unboundParams) & $name @boundParams @unboundParams }} ` - }} + $scriptCmd = {{ + & $script:InvokeCommand ` + @clientSideParameters ` + -HideComputerName ` + -Session (Get-PSImplicitRemotingSession -CommandName '{0}') ` + -Arg ('{0}', $PSBoundParameters, $positionalArguments) ` + -Script {{ param($name, $boundParams, $unboundParams) & $name @boundParams @unboundParams }} ` + }} $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) $steppablePipeline.Begin($myInvocation.ExpectingInput, $ExecutionContext) @@ -3059,10 +3053,7 @@ internal List GenerateProxyModule( FileShare.None); using (TextWriter writer = new StreamWriter(psm1, encoding)) { - if (listOfCommandMetadata == null) - { - listOfCommandMetadata = new List(); - } + listOfCommandMetadata ??= new List(); GenerateModuleHeader(writer); GenerateHelperFunctions(writer); @@ -3080,10 +3071,7 @@ internal List GenerateProxyModule( FileShare.None); using (TextWriter writer = new StreamWriter(formatPs1xml, encoding)) { - if (listOfFormatData == null) - { - listOfFormatData = new List(); - } + listOfFormatData ??= new List(); GenerateFormatFile(writer, listOfFormatData); formatPs1xml.SetLength(formatPs1xml.Position); 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 53ab8d32c2f..1f43670531d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs @@ -2,11 +2,13 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Management.Automation; using System.Management.Automation.Internal; +using System.Management.Automation.Security; namespace Microsoft.PowerShell.Commands { @@ -146,9 +148,9 @@ protected override void ProcessRecord() } // Prevent additional commands in ConstrainedLanguage mode - if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage) + if (_setSupportedCommand && Context.LanguageMode == PSLanguageMode.ConstrainedLanguage) { - if (_setSupportedCommand) + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) { NotSupportedException nse = PSTraceSource.NewNotSupportedException( @@ -156,6 +158,13 @@ protected override void ProcessRecord() ThrowTerminatingError( new ErrorRecord(nse, "CannotDefineSupportedCommand", ErrorCategory.PermissionDenied, null)); } + + SystemPolicy.LogWDACAuditMessage( + context: Context, + title: ImportLocalizedDataStrings.WDACLogTitle, + message: ImportLocalizedDataStrings.WDACLogMessage, + fqid: "SupportedCommandsDisabled", + dropIntoDebugger: true); } string script = GetScript(path); @@ -282,7 +291,7 @@ private string GetFilePath() fileName = Path.GetFileNameWithoutExtension(fileName); - CultureInfo culture = null; + CultureInfo culture; if (_uiculture == null) { culture = CultureInfo.CurrentUICulture; @@ -299,19 +308,33 @@ private string GetFilePath() } } - CultureInfo currentCulture = culture; + List cultureList = new List { culture }; + if (_uiculture == null && culture.Name != "en-US") + { + // .NET 4.8 presents en-US as a parent of any current culture when accessed via the CurrentUICulture + // property. + // + // This feature is not present when GetCultureInfo is called, therefore this fallback change only + // applies when the UICulture parameter is not supplied. + cultureList.Add(CultureInfo.GetCultureInfo("en-US")); + } + string filePath; string fullFileName = fileName + ".psd1"; - while (currentCulture != null && !string.IsNullOrEmpty(currentCulture.Name)) + foreach (CultureInfo cultureToTest in cultureList) { - filePath = Path.Combine(dir, currentCulture.Name, fullFileName); - - if (File.Exists(filePath)) + CultureInfo currentCulture = cultureToTest; + while (currentCulture != null && !string.IsNullOrEmpty(currentCulture.Name)) { - return filePath; - } + filePath = Path.Combine(dir, currentCulture.Name, fullFileName); - currentCulture = currentCulture.Parent; + if (File.Exists(filePath)) + { + return filePath; + } + + currentCulture = currentCulture.Parent; + } } filePath = Path.Combine(dir, fullFileName); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportAliasCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportAliasCommand.cs index 4252a1623d6..657d00b4fc5 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportAliasCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportAliasCommand.cs @@ -55,6 +55,7 @@ public string LiteralPath /// [Parameter] [ValidateNotNullOrEmpty] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } /// @@ -296,7 +297,7 @@ private Collection GetAliasesFromFile(bool isLiteralPath) { CSVHelper csvHelper = new(','); - Int64 lineNumber = 0; + long lineNumber = 0; string line = null; while ((line = reader.ReadLine()) != null) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportPowerShellDataFile.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportPowerShellDataFile.cs index 53dc461dfb2..5661df24fa9 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportPowerShellDataFile.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportPowerShellDataFile.cs @@ -42,7 +42,6 @@ public string[] LiteralPath /// /// Gets or sets switch that determines if built-in limits are applied to the data. /// - [Experimental("Microsoft.PowerShell.Utility.PSImportPSDataFileSkipLimitCheck", ExperimentAction.Show)] [Parameter] public SwitchParameter SkipLimitCheck { get; set; } @@ -65,7 +64,7 @@ protected override void ProcessRecord() } else { - var data = ast.Find(a => a is HashtableAst, false); + var data = ast.Find(static a => a is HashtableAst, false); if (data != null) { WriteObject(data.SafeGetValue(SkipLimitCheck)); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/InvokeExpressionCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/InvokeExpressionCommand.cs index d51f20f8704..acbffeb66f4 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/InvokeExpressionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/InvokeExpressionCommand.cs @@ -4,6 +4,7 @@ using System; using System.Management.Automation; using System.Management.Automation.Internal; +using System.Management.Automation.Security; namespace Microsoft.PowerShell.Commands { @@ -43,6 +44,16 @@ protected override void ProcessRecord() myScriptBlock.LanguageMode = PSLanguageMode.ConstrainedLanguage; } + if (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Audit) + { + SystemPolicy.LogWDACAuditMessage( + context: Context, + title: UtilityCommonStrings.IEXWDACLogTitle, + message: UtilityCommonStrings.IEXWDACLogMessage, + fqid: "InvokeExpressionCmdletConstrained", + dropIntoDebugger: true); + } + var emptyArray = Array.Empty(); myScriptBlock.InvokeUsingCmdlet( contextCmdlet: this, 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 16243fcf6ae..80a04b07f17 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Join-String.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Join-String.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Management.Automation; @@ -40,7 +41,7 @@ public sealed class JoinStringCommand : PSCmdlet /// Gets or sets the delimiter to join the output with. /// [Parameter(Position = 1)] - [ArgumentCompleter(typeof(JoinItemCompleter))] + [ArgumentCompleter(typeof(SeparatorArgumentCompleter))] [AllowEmptyString] public string Separator { @@ -78,7 +79,7 @@ public string Separator /// Gets or sets a format string that is applied to each input object. /// [Parameter(ParameterSetName = "Format")] - [ArgumentCompleter(typeof(JoinItemCompleter))] + [ArgumentCompleter(typeof(FormatStringArgumentCompleter))] public string FormatString { get; set; } /// @@ -159,75 +160,115 @@ protected override void EndProcessing() } } - internal class JoinItemCompleter : IArgumentCompleter + /// + /// Provides completion for the Separator parameter of the Join-String cmdlet. + /// + 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 s_separatorValues = new List(capacity: 7) + { + ",", + ", ", + ";", + "; ", + NewLineText, + "-", + " ", + }; + + /// + /// Returns completion results for Separator parameter. + /// + /// The command name. + /// The parameter name. + /// The word to complete. + /// The command AST. + /// The fake bound parameters. + /// List of Completion Results. public IEnumerable 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 CompleteFormatString(string wordToComplete) - { - var res = new List(); - 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 CompleteSeparator(string wordToComplete) - { - var res = new List(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 + /// + /// Provides completion for the FormatString parameter of the Join-String cmdlet. + /// + public sealed class FormatStringArgumentCompleter : IArgumentCompleter + { + private static readonly IReadOnlyList s_formatStringValues = new List(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 - } - } + }; + + /// + /// Returns completion results for FormatString parameter. + /// + /// The command name. + /// The parameter name. + /// The word to complete. + /// The command AST. + /// The fake bound parameters. + /// List of Completion Results. + public IEnumerable 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 new file mode 100644 index 00000000000..ba664ccef1b --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/JsonSchemaReferenceResolutionException.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; + +namespace Microsoft.PowerShell.Commands; + +/// +/// Thrown during evaluation of when an attempt +/// to resolve a $ref or $dynamicRef fails. +/// +internal class JsonSchemaReferenceResolutionException : Exception +{ + /// + /// Initializes a new instance of the class. + /// + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public JsonSchemaReferenceResolutionException(Exception innerException) + : base(message: null, innerException) + { + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs index 6271e6f57b6..262fd44b30f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs @@ -80,7 +80,7 @@ public class MatchInfo /// Gets or sets the number of the matching line. /// /// The number of the matching line. - public int LineNumber { get; set; } + public ulong LineNumber { get; set; } /// /// Gets or sets the text of the matching line. @@ -127,11 +127,11 @@ public MatchInfo(IReadOnlyList matchIndexes, IReadOnlyList matchLength /// /// Gets the base name of the file containing the matching line. + /// /// /// It will be the string "InputStream" if the object came from the input stream. /// This is a readonly property calculated from the path . /// - /// /// The file name. public string Filename { @@ -150,10 +150,10 @@ public string Filename /// /// Gets or sets the full path of the file containing the matching line. + /// /// /// It will be "InputStream" if the object came from the input stream. /// - /// /// The path name. public string Path { @@ -182,11 +182,11 @@ public string Path /// /// Returns the path of the matching file truncated relative to the parameter. + /// /// /// For example, if the matching path was c:\foo\bar\baz.c and the directory argument was c:\foo /// the routine would return bar\baz.c . /// - /// /// The directory base the truncation on. /// The relative path that was produced. public string RelativePath(string directory) @@ -232,12 +232,12 @@ public string RelativePath(string directory) /// /// Returns the string representation of this object. The format /// depends on whether a path has been set for this object or not. + /// /// /// If the path component is set, as would be the case when matching /// in a file, ToString() would return the path, line number and line text. /// If path is not set, then just the line text is presented. /// - /// /// The string representation of the match object. public override string ToString() { @@ -277,7 +277,7 @@ private string ToString(string directory, string line) // Otherwise, render the full context. List lines = new(Context.DisplayPreContext.Length + Context.DisplayPostContext.Length + 1); - int displayLineNumber = this.LineNumber - Context.DisplayPreContext.Length; + ulong displayLineNumber = this.LineNumber - (ulong)Context.DisplayPreContext.Length; foreach (string contextLine in Context.DisplayPreContext) { lines.Add(FormatLine(contextLine, displayLineNumber++, displayPath, ContextPrefix)); @@ -315,8 +315,8 @@ public string ToEmphasizedString(string directory) /// The matched line with matched text inverted. private string EmphasizeLine() { - string invertColorsVT100 = VTUtility.GetEscapeSequence(VTUtility.VT.Inverse); - string resetVT100 = VTUtility.GetEscapeSequence(VTUtility.VT.Reset); + string invertColorsVT100 = PSStyle.Instance.Reverse; + string resetVT100 = PSStyle.Instance.Reset; char[] chars = new char[(_matchIndexes.Count * (invertColorsVT100.Length + resetVT100.Length)) + Line.Length]; int lineIndex = 0; @@ -356,7 +356,7 @@ private string EmphasizeLine() /// The file path, formatted for display. /// The match prefix. /// The formatted line as a string. - private string FormatLine(string lineStr, int displayLineNumber, string displayPath, string prefix) + private string FormatLine(string lineStr, ulong displayLineNumber, string displayPath, string prefix) { return _pathSet ? StringUtil.Format(MatchFormat, prefix, displayPath, displayLineNumber, lineStr) @@ -410,7 +410,7 @@ public sealed class SelectStringCommand : PSCmdlet /// A generic circular buffer. /// /// The type of items that are buffered. - private class CircularBuffer : ICollection + private sealed class CircularBuffer : ICollection { // Ring of items private readonly T[] _items; @@ -428,10 +428,7 @@ private class CircularBuffer : ICollection /// If is negative. public CircularBuffer(int capacity) { - if (capacity < 0) - { - throw new ArgumentOutOfRangeException(nameof(capacity)); - } + ArgumentOutOfRangeException.ThrowIfNegative(capacity); _items = new T[capacity]; Clear(); @@ -532,15 +529,8 @@ public bool Contains(T item) public void CopyTo(T[] array, int arrayIndex) { - if (array == null) - { - throw new ArgumentNullException(nameof(array)); - } - - if (arrayIndex < 0) - { - throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - } + ArgumentNullException.ThrowIfNull(array); + ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex); if (Count > (array.Length - arrayIndex)) { @@ -627,7 +617,7 @@ private interface IContextTracker /// /// A state machine to track display context for each match. /// - private class DisplayContextTracker : IContextTracker + private sealed class DisplayContextTracker : IContextTracker { private enum ContextState { @@ -784,12 +774,12 @@ private void Reset() /// and other matching lines (since they will appear /// as their own match entries.). /// - private class LogicalContextTracker : IContextTracker + private sealed class LogicalContextTracker : IContextTracker { // A union: string | MatchInfo. Needed since // context lines could be either proper matches // or non-matching lines. - private class ContextEntry + private sealed class ContextEntry { public readonly string Line; public readonly MatchInfo Match; @@ -989,7 +979,7 @@ private string[] CopyContext(int startIndex, int length) /// /// A class to track both logical and display contexts. /// - private class ContextTracker : IContextTracker + private sealed class ContextTracker : IContextTracker { private readonly IContextTracker _displayTracker; private readonly IContextTracker _logicalTracker; @@ -1058,7 +1048,7 @@ private void UpdateQueue() /// /// ContextTracker that does not work for the case when pre- and post context is 0. /// - private class NoContextTracker : IContextTracker + private sealed class NoContextTracker : IContextTracker { private readonly IList _matches = new List(1); @@ -1346,8 +1336,8 @@ public string[] Exclude /// Gets or sets the text encoding to process each file as. /// [Parameter] - [ArgumentToEncodingTransformationAttribute()] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] public Encoding Encoding { @@ -1363,7 +1353,7 @@ public Encoding Encoding } } - private Encoding _encoding = ClrFacade.GetDefaultEncoding(); + private Encoding _encoding = Encoding.Default; /// /// Gets or sets the number of context lines to collect. If set to a @@ -1376,7 +1366,7 @@ public Encoding Encoding [Parameter] [ValidateNotNullOrEmpty] [ValidateCount(1, 2)] - [ValidateRange(0, Int32.MaxValue)] + [ValidateRange(0, int.MaxValue)] public new int[] Context { get => _context; @@ -1427,7 +1417,7 @@ private IContextTracker GetContextTracker() => (Raw || (_preContext == 0 && _pos /// private bool _doneProcessing; - private int _inputRecordNumber; + private ulong _inputRecordNumber; /// /// Read command line parameters. @@ -1605,7 +1595,7 @@ private bool ProcessFile(string filename) using (StreamReader sr = new(fs, Encoding)) { string line; - int lineNo = 0; + ulong lineNo = 0; // Read and display lines from the file until the end of // the file is reached. @@ -1996,7 +1986,7 @@ private void WarnFilterContext() /// /// Magic class that works around the limitations on ToString() for FileInfo. /// - private class FileinfoToStringAttribute : ArgumentTransformationAttribute + private sealed class FileinfoToStringAttribute : ArgumentTransformationAttribute { public override object Transform(EngineIntrinsics engineIntrinsics, object inputData) { 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 452e8bfe09c..cf483b16a93 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs @@ -160,7 +160,7 @@ public sealed class MeasureObjectCommand : PSCmdlet /// Keys are strings. Keys are compared with OrdinalIgnoreCase. /// /// Value type. - private class MeasureObjectDictionary : Dictionary + private sealed class MeasureObjectDictionary : Dictionary where TValue : new() { /// @@ -200,7 +200,7 @@ public TValue EnsureEntry(string key) /// what mode we're in. /// [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] - private class Statistics + private sealed class Statistics { // Common properties internal int count = 0; @@ -561,8 +561,7 @@ private void AnalyzeObjectProperties(PSObject inObj) /// The value to analyze. private void AnalyzeValue(string propertyName, object objValue) { - if (propertyName == null) - propertyName = thisObject; + propertyName ??= thisObject; Statistics stat = _statistics.EnsureEntry(propertyName); @@ -792,9 +791,9 @@ private void WritePropertyNotFoundError(string propertyName, string errorId) { Diagnostics.Assert(Property != null, "no property and no InputObject should have been addressed"); ErrorRecord errorRecord = new( - PSTraceSource.NewArgumentException("Property"), + PSTraceSource.NewArgumentException(propertyName), errorId, - ErrorCategory.InvalidArgument, + ErrorCategory.ObjectNotFound, null); errorRecord.ErrorDetails = new ErrorDetails( this, "MeasureObjectStrings", "PropertyNotFound", propertyName); @@ -820,9 +819,12 @@ protected override void EndProcessing() Statistics stat = _statistics[propertyName]; if (stat.count == 0 && Property != null) { - // Why are there two different ids for this error? - string errorId = (IsMeasuringGeneric) ? "GenericMeasurePropertyNotFound" : "TextMeasurePropertyNotFound"; - WritePropertyNotFoundError(propertyName, errorId); + if (Context.IsStrictVersion(2)) + { + 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 90ef2ca4376..f2c31faf400 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-Object.cs @@ -187,18 +187,30 @@ protected override void BeginProcessing() targetObject: null)); } - if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage) - { - if (!CoreTypes.Contains(type)) - { - ThrowTerminatingError( - new ErrorRecord( - new PSNotSupportedException(NewObjectStrings.CannotCreateTypeConstrainedLanguage), "CannotCreateTypeConstrainedLanguage", ErrorCategory.PermissionDenied, null)); - } - } - switch (Context.LanguageMode) { + case PSLanguageMode.ConstrainedLanguage: + if (!CoreTypes.Contains(type)) + { + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + ThrowTerminatingError( + new ErrorRecord( + new PSNotSupportedException(NewObjectStrings.CannotCreateTypeConstrainedLanguage), + "CannotCreateTypeConstrainedLanguage", + ErrorCategory.PermissionDenied, + targetObject: null)); + } + + SystemPolicy.LogWDACAuditMessage( + context: Context, + title: NewObjectStrings.TypeWDACLogTitle, + message: StringUtil.Format(NewObjectStrings.TypeWDACLogMessage, type.FullName), + fqid: "NewObjectCmdletCannotCreateType", + dropIntoDebugger: true); + } + break; + case PSLanguageMode.NoLanguage: case PSLanguageMode.RestrictedLanguage: if (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce @@ -212,8 +224,7 @@ protected override void BeginProcessing() ErrorCategory.PermissionDenied, targetObject: null)); } - - break; + break; } // WinRT does not support creating instances of attribute & delegate WinRT types. @@ -238,7 +249,7 @@ protected override void BeginProcessing() WriteObject(_newObject); return; } - else if (type.GetTypeInfo().IsValueType) + else if (type.IsValueType) { // This is for default parameterless struct ctor which is not returned by // Type.GetConstructor(System.Type.EmptyTypes). @@ -301,21 +312,31 @@ protected override void BeginProcessing() bool isAllowed = false; // If it's a system-wide lockdown, we may allow additional COM types - if (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) + var systemLockdownPolicy = SystemPolicy.GetSystemLockdownPolicy(); + if (systemLockdownPolicy == SystemEnforcementMode.Enforce || systemLockdownPolicy == SystemEnforcementMode.Audit) { - if ((result >= 0) && - SystemPolicy.IsClassInApprovedList(_comObjectClsId)) - { - isAllowed = true; - } + isAllowed = (result >= 0) && SystemPolicy.IsClassInApprovedList(_comObjectClsId); } if (!isAllowed) { - ThrowTerminatingError( - new ErrorRecord( - new PSNotSupportedException(NewObjectStrings.CannotCreateTypeConstrainedLanguage), "CannotCreateComTypeConstrainedLanguage", ErrorCategory.PermissionDenied, null)); - return; + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + ThrowTerminatingError( + new ErrorRecord( + new PSNotSupportedException(NewObjectStrings.CannotCreateTypeConstrainedLanguage), + "CannotCreateComTypeConstrainedLanguage", + ErrorCategory.PermissionDenied, + targetObject: null)); + return; + } + + SystemPolicy.LogWDACAuditMessage( + context: Context, + title: NewObjectStrings.ComWDACLogTitle, + message: StringUtil.Format(NewObjectStrings.ComWDACLogMessage, ComObject ?? string.Empty), + fqid: "NewObjectCmdletCannotCreateCOM", + dropIntoDebugger: true); } } @@ -351,12 +372,12 @@ protected override void BeginProcessing() #if !UNIX #region Com - private object SafeCreateInstance(Type t, object[] args) + private object SafeCreateInstance(Type t) { object result = null; try { - result = Activator.CreateInstance(t, args); + result = Activator.CreateInstance(t); } // Does not catch InvalidComObjectException because ComObject is obtained from GetTypeFromProgID catch (ArgumentException e) @@ -416,7 +437,7 @@ private object SafeCreateInstance(Type t, object[] args) return result; } - private class ComCreateInfo + private sealed class ComCreateInfo { public object objectCreated; public bool success; @@ -430,13 +451,10 @@ private void STAComCreateThreadProc(object createstruct) ComCreateInfo info = (ComCreateInfo)createstruct; try { - Type type = null; - PSArgumentException mshArgE = null; - - type = Type.GetTypeFromCLSID(_comObjectClsId); + Type type = Type.GetTypeFromCLSID(_comObjectClsId); if (type == null) { - mshArgE = PSTraceSource.NewArgumentException( + PSArgumentException mshArgE = PSTraceSource.NewArgumentException( "ComObject", NewObjectStrings.CannotLoadComObjectType, ComObject); @@ -446,7 +464,7 @@ private void STAComCreateThreadProc(object createstruct) return; } - info.objectCreated = SafeCreateInstance(type, ArgumentList); + info.objectCreated = SafeCreateInstance(type); info.success = true; } catch (Exception e) @@ -458,20 +476,25 @@ private void STAComCreateThreadProc(object createstruct) private object CreateComObject() { - Type type = null; - PSArgumentException mshArgE = null; - try { - type = Marshal.GetTypeFromCLSID(_comObjectClsId); + Type type = Marshal.GetTypeFromCLSID(_comObjectClsId); if (type == null) { - mshArgE = PSTraceSource.NewArgumentException("ComObject", NewObjectStrings.CannotLoadComObjectType, ComObject); + PSArgumentException mshArgE = PSTraceSource.NewArgumentException( + "ComObject", + NewObjectStrings.CannotLoadComObjectType, + ComObject); + ThrowTerminatingError( - new ErrorRecord(mshArgE, "CannotLoadComObjectType", ErrorCategory.InvalidType, null)); + new ErrorRecord( + mshArgE, + "CannotLoadComObjectType", + ErrorCategory.InvalidType, + targetObject: null)); } - return SafeCreateInstance(type, ArgumentList); + return SafeCreateInstance(type); } catch (COMException e) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewEventCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewEventCommand.cs index 34c95a116f3..1e46241b2d8 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewEventCommand.cs @@ -121,7 +121,10 @@ protected override void EndProcessing() } object messageSender = null; - if (_sender != null) { messageSender = _sender.BaseObject; } + if (_sender != null) + { + messageSender = _sender.BaseObject; + } // And then generate the event WriteObject(Events.GenerateEvent(_sourceIdentifier, messageSender, baseEventArgs, _messageData, true, false)); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewGuidCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewGuidCommand.cs index 537d86c24ec..3aa070bcd62 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewGuidCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewGuidCommand.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System; using System.Management.Automation; @@ -9,16 +11,48 @@ namespace Microsoft.PowerShell.Commands /// /// The implementation of the "new-guid" cmdlet. /// - [Cmdlet(VerbsCommon.New, "Guid", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2097130")] + [Cmdlet(VerbsCommon.New, "Guid", DefaultParameterSetName = "Default", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2097130")] [OutputType(typeof(Guid))] - public class NewGuidCommand : Cmdlet + public class NewGuidCommand : PSCmdlet { /// - /// Returns a guid. + /// Gets or sets a value indicating that the cmdlet should return a Guid structure whose value is all zeros. /// - protected override void EndProcessing() + [Parameter(ParameterSetName = "Empty")] + public SwitchParameter Empty { get; set; } + + /// + /// Gets or sets the value to be converted to a Guid. + /// + [Parameter(Position = 0, ValueFromPipeline = true, ParameterSetName = "InputObject")] + [System.Diagnostics.CodeAnalysis.AllowNull] + public string InputObject { get; set; } + + /// + /// Returns a Guid. + /// + protected override void ProcessRecord() { - WriteObject(Guid.NewGuid()); + Guid? guid = null; + + if (ParameterSetName is "InputObject") + { + try + { + guid = new(InputObject); + } + catch (Exception ex) + { + ErrorRecord error = new(ex, "StringNotRecognizedAsGuid", ErrorCategory.InvalidArgument, null); + WriteError(error); + } + } + else + { + guid = ParameterSetName is "Empty" ? Guid.Empty : Guid.NewGuid(); + } + + WriteObject(guid); } } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewTimeSpanCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewTimeSpanCommand.cs index 53c39250763..a5d784da9bb 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewTimeSpanCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewTimeSpanCommand.cs @@ -88,6 +88,12 @@ public DateTime End [Parameter(ParameterSetName = "Time")] public int Seconds { get; set; } + /// + /// Allows the user to override the millisecond. + /// + [Parameter(ParameterSetName = "Time")] + public int Milliseconds { get; set; } + #endregion #region methods @@ -119,7 +125,7 @@ protected override void ProcessRecord() break; case "Time": - result = new TimeSpan(Days, Hours, Minutes, Seconds); + result = new TimeSpan(Days, Hours, Minutes, Seconds, Milliseconds); break; default: diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ObjectCommandComparer.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ObjectCommandComparer.cs index 39986943c99..bb71dfdbe1c 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ObjectCommandComparer.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ObjectCommandComparer.cs @@ -190,7 +190,7 @@ internal int Compare(ObjectCommandPropertyValue first, ObjectCommandPropertyValu /// /// /// 0 if they are the same, less than 0 if first is smaller, more than 0 if first is greater. - /// + /// public int Compare(object first, object second) { // This method will never throw exceptions, two null diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/OrderObjectBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/OrderObjectBase.cs index 40f06e58eca..519b472620b 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/OrderObjectBase.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/OrderObjectBase.cs @@ -259,10 +259,7 @@ internal void ProcessExpressionParameter( } else { - if (_unExpandedParametersWithWildCardPattern == null) - { - _unExpandedParametersWithWildCardPattern = new List(); - } + _unExpandedParametersWithWildCardPattern ??= new List(); _unExpandedParametersWithWildCardPattern.Add(unexpandedParameter); } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCommandBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCommandBase.cs index bc01f341861..61d7236977d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCommandBase.cs @@ -16,7 +16,6 @@ public abstract class PSBreakpointCommandBase : PSCmdlet /// /// Gets or sets the runspace where the breakpoints will be used. /// - [Experimental("Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace", ExperimentAction.Show)] [Parameter] [ValidateNotNull] [Runspace] @@ -31,10 +30,7 @@ public abstract class PSBreakpointCommandBase : PSCmdlet /// protected override void BeginProcessing() { - if (Runspace == null) - { - Runspace = Context.CurrentRunspace; - } + Runspace ??= Context.CurrentRunspace; } #endregion overrides diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveAliasCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveAliasCommand.cs index 7cec08e1b10..15c48efd847 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveAliasCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveAliasCommand.cs @@ -9,7 +9,7 @@ namespace Microsoft.PowerShell.Commands /// /// The implementation of the "Remove-Alias" cmdlet. /// - [Cmdlet(VerbsCommon.Remove, "Alias", DefaultParameterSetName = "Default", HelpUri = "")] + [Cmdlet(VerbsCommon.Remove, "Alias", DefaultParameterSetName = "Default", HelpUri = "https://go.microsoft.com/fwlink/?linkid=2097127")] [Alias("ral")] public class RemoveAliasCommand : PSCmdlet { @@ -25,6 +25,7 @@ public class RemoveAliasCommand : PSCmdlet /// The scope parameter for the command determines which scope the alias is removed from. /// [Parameter] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } /// 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 efdd82d79de..efd7007e131 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Select-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Select-Object.cs @@ -24,10 +24,7 @@ internal sealed class PSPropertyExpressionFilter /// Array of pattern strings to use. internal PSPropertyExpressionFilter(string[] wildcardPatternsStrings) { - if (wildcardPatternsStrings == null) - { - throw new ArgumentNullException(nameof(wildcardPatternsStrings)); - } + ArgumentNullException.ThrowIfNull(wildcardPatternsStrings); _wildcardPatterns = new WildcardPattern[wildcardPatternsStrings.Length]; for (int k = 0; k < wildcardPatternsStrings.Length; k++) @@ -113,6 +110,13 @@ public SwitchParameter Unique private bool _unique; + /// + /// Gets or sets case insensitive switch for string comparison. + /// Used in combination with Unique switch parameter. + /// + [Parameter] + public SwitchParameter CaseInsensitive { get; set; } + /// /// /// @@ -147,10 +151,11 @@ public int First private bool _firstOrLastSpecified; /// - /// Skips the specified number of items from top when used with First, from end when used with Last. + /// Skips the specified number of items from top when used with First, from end when used with Last or SkipLast. /// /// [Parameter(ParameterSetName = "DefaultParameter")] + [Parameter(ParameterSetName = "SkipLastParameter")] [ValidateRange(0, int.MaxValue)] public int Skip { get; set; } @@ -174,7 +179,7 @@ public int First /// /// [Parameter(ParameterSetName = "IndexParameter")] - [ValidateRangeAttribute(0, int.MaxValue)] + [ValidateRange(0, int.MaxValue)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public int[] Index { @@ -197,7 +202,7 @@ public int[] Index /// /// [Parameter(ParameterSetName = "SkipIndexParameter")] - [ValidateRangeAttribute(0, int.MaxValue)] + [ValidateRange(0, int.MaxValue)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public int[] SkipIndex { @@ -223,7 +228,7 @@ public int[] SkipIndex private SelectObjectQueue _selectObjectQueue; - private class SelectObjectQueue : Queue + private sealed class SelectObjectQueue : Queue { internal SelectObjectQueue(int first, int last, int skip, int skipLast, bool firstOrLastSpecified) { @@ -324,7 +329,7 @@ public PSObject StreamingDequeue() private PSPropertyExpressionFilter _exclusionFilter; - private class UniquePSObjectHelper + private sealed class UniquePSObjectHelper { internal UniquePSObjectHelper(PSObject o, int notePropertyCount) { @@ -437,7 +442,11 @@ private void ProcessParameter(MshParameter p, PSObject inputObject, List tempExprResults = resolvedName.GetValues(inputObject); - if (tempExprResults == null) continue; + if (tempExprResults == null) + { + continue; + } + foreach (PSPropertyExpressionResult mshExpRes in tempExprResults) { expressionResults.Add(mshExpRes); @@ -528,7 +537,10 @@ private void ProcessExpandParameter(MshParameter p, PSObject inputObject, if (r.Exception == null) { // ignore the property value if it's null - if (r.Result == null) { return; } + if (r.Result == null) + { + return; + } System.Collections.IEnumerable results = LanguagePrimitives.GetEnumerable(r.Result); if (results == null) @@ -548,7 +560,10 @@ private void ProcessExpandParameter(MshParameter p, PSObject inputObject, foreach (object expandedValue in results) { // ignore the element if it's null - if (expandedValue == null) { continue; } + if (expandedValue == null) + { + continue; + } // add NoteProperties if there is any // If expandedValue is a base object, we don't want to associate the NoteProperty @@ -624,7 +639,11 @@ private void FilteredWriteObject(PSObject obj, List addedNotePro bool isObjUnique = true; foreach (UniquePSObjectHelper uniqueObj in _uniques) { - ObjectCommandComparer comparer = new(true, CultureInfo.CurrentCulture, true); + ObjectCommandComparer comparer = new( + ascending: true, + CultureInfo.CurrentCulture, + caseSensitive: !CaseInsensitive.IsPresent); + if ((comparer.Compare(obj.BaseObject, uniqueObj.WrittenObject.BaseObject) == 0) && (uniqueObj.NotePropertyCount == addedNoteProperties.Count)) { 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 975fd68ebfa..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 @@ -165,7 +165,7 @@ public Encoding Encoding /// Value must be greater than zero. /// [Parameter(ValueFromPipelineByPropertyName = true)] - [ValidateRange(0, Int32.MaxValue)] + [ValidateRange(0, int.MaxValue)] public int Port { get; set; } #endregion diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/SetDateCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/SetDateCommand.cs index 77b0dce3470..5dfe40fa9d4 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/SetDateCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/SetDateCommand.cs @@ -48,7 +48,6 @@ public sealed class SetDateCommand : PSCmdlet /// /// Set the date. /// - [ArchitectureSensitive] protected override void ProcessRecord() { DateTime dateToUse; @@ -71,31 +70,44 @@ protected override void ProcessRecord() if (ShouldProcess(dateToUse.ToString())) { #if UNIX - if (!Platform.NonWindowsSetDate(dateToUse)) + // We are not validating the native call here. + // We just want to be sure that we're using the value the user provided us. + if (Dbg.Internal.InternalTestHooks.SetDate) + { + WriteObject(dateToUse); + } + else if (!Platform.NonWindowsSetDate(dateToUse)) { throw new Win32Exception(Marshal.GetLastWin32Error()); } #else // build up the SystemTime struct to pass to SetSystemTime NativeMethods.SystemTime systemTime = new(); - systemTime.Year = (UInt16)dateToUse.Year; - systemTime.Month = (UInt16)dateToUse.Month; - systemTime.Day = (UInt16)dateToUse.Day; - systemTime.Hour = (UInt16)dateToUse.Hour; - systemTime.Minute = (UInt16)dateToUse.Minute; - systemTime.Second = (UInt16)dateToUse.Second; - systemTime.Milliseconds = (UInt16)dateToUse.Millisecond; + systemTime.Year = (ushort)dateToUse.Year; + systemTime.Month = (ushort)dateToUse.Month; + systemTime.Day = (ushort)dateToUse.Day; + systemTime.Hour = (ushort)dateToUse.Hour; + systemTime.Minute = (ushort)dateToUse.Minute; + systemTime.Second = (ushort)dateToUse.Second; + systemTime.Milliseconds = (ushort)dateToUse.Millisecond; #pragma warning disable 56523 - if (!NativeMethods.SetLocalTime(ref systemTime)) + if (Dbg.Internal.InternalTestHooks.SetDate) { - throw new Win32Exception(Marshal.GetLastWin32Error()); + WriteObject(systemTime); } - - // MSDN says to call this twice to account for changes - // between DST - if (!NativeMethods.SetLocalTime(ref systemTime)) + else { - throw new Win32Exception(Marshal.GetLastWin32Error()); + if (!NativeMethods.SetLocalTime(ref systemTime)) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + // MSDN says to call this twice to account for changes + // between DST + if (!NativeMethods.SetLocalTime(ref systemTime)) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } } #pragma warning restore 56523 #endif @@ -106,7 +118,11 @@ protected override void ProcessRecord() PSNoteProperty note = new("DisplayHint", DisplayHint); outputObj.Properties.Add(note); - WriteObject(outputObj); + // If we've turned on the SetDate test hook, don't emit the output object here because we emitted it earlier. + if (!Dbg.Internal.InternalTestHooks.SetDate) + { + WriteObject(outputObj); + } } #endregion @@ -118,14 +134,14 @@ internal static class NativeMethods [StructLayout(LayoutKind.Sequential)] public struct SystemTime { - public UInt16 Year; - public UInt16 Month; - public UInt16 DayOfWeek; - public UInt16 Day; - public UInt16 Hour; - public UInt16 Minute; - public UInt16 Second; - public UInt16 Milliseconds; + public ushort Year; + public ushort Month; + public ushort DayOfWeek; + public ushort Day; + public ushort Hour; + public ushort Minute; + public ushort Second; + public ushort Milliseconds; } [DllImport(PinvokeDllNames.SetLocalTimeDllName, SetLastError = true)] diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommand.cs index 330716af151..60b05807d48 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommand.cs @@ -75,14 +75,14 @@ public class ShowCommandCommand : PSCmdlet, IDisposable /// Gets or sets the Width. /// [Parameter] - [ValidateRange(300, Int32.MaxValue)] + [ValidateRange(300, int.MaxValue)] public double Height { get; set; } /// /// Gets or sets the Width. /// [Parameter] - [ValidateRange(300, Int32.MaxValue)] + [ValidateRange(300, int.MaxValue)] public double Width { get; set; } /// diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandCommandInfo.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandCommandInfo.cs index e3de5cac296..e2ba41fb3fc 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandCommandInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandCommandInfo.cs @@ -22,10 +22,7 @@ public class ShowCommandCommandInfo /// public ShowCommandCommandInfo(CommandInfo other) { - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } + ArgumentNullException.ThrowIfNull(other); this.Name = other.Name; this.ModuleName = other.ModuleName; @@ -39,7 +36,7 @@ public ShowCommandCommandInfo(CommandInfo other) { this.ParameterSets = other.ParameterSets - .Select(x => new ShowCommandParameterSetInfo(x)) + .Select(static x => new ShowCommandParameterSetInfo(x)) .ToList() .AsReadOnly(); } @@ -71,10 +68,7 @@ public ShowCommandCommandInfo(CommandInfo other) /// public ShowCommandCommandInfo(PSObject other) { - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } + ArgumentNullException.ThrowIfNull(other); this.Name = other.Members["Name"].Value as string; this.ModuleName = other.Members["ModuleName"].Value as string; @@ -92,7 +86,7 @@ public ShowCommandCommandInfo(PSObject other) this.CommandType = (CommandTypes)((other.Members["CommandType"].Value as PSObject).BaseObject); var parameterSets = (other.Members["ParameterSets"].Value as PSObject).BaseObject as System.Collections.ArrayList; - this.ParameterSets = GetObjectEnumerable(parameterSets).Cast().Select(x => new ShowCommandParameterSetInfo(x)).ToList().AsReadOnly(); + this.ParameterSets = GetObjectEnumerable(parameterSets).Cast().Select(static x => new ShowCommandParameterSetInfo(x)).ToList().AsReadOnly(); if (other.Members["Module"]?.Value is PSObject) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandModuleInfo.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandModuleInfo.cs index b12cc5651f4..f31bc93525d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandModuleInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandModuleInfo.cs @@ -20,10 +20,7 @@ public class ShowCommandModuleInfo /// public ShowCommandModuleInfo(PSModuleInfo other) { - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } + ArgumentNullException.ThrowIfNull(other); this.Name = other.Name; } @@ -37,10 +34,7 @@ public ShowCommandModuleInfo(PSModuleInfo other) /// public ShowCommandModuleInfo(PSObject other) { - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } + ArgumentNullException.ThrowIfNull(other); this.Name = other.Members["Name"].Value as string; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterInfo.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterInfo.cs index 04a74b4fc45..9bf79c5bd76 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterInfo.cs @@ -22,10 +22,7 @@ public class ShowCommandParameterInfo /// public ShowCommandParameterInfo(CommandParameterInfo other) { - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } + ArgumentNullException.ThrowIfNull(other); this.Name = other.Name; this.IsMandatory = other.IsMandatory; @@ -33,7 +30,7 @@ public ShowCommandParameterInfo(CommandParameterInfo other) this.ParameterType = new ShowCommandParameterType(other.ParameterType); this.Position = other.Position; - var validateSetAttribute = other.Attributes.Where(x => typeof(ValidateSetAttribute).IsAssignableFrom(x.GetType())).Cast().LastOrDefault(); + var validateSetAttribute = other.Attributes.Where(static x => typeof(ValidateSetAttribute).IsAssignableFrom(x.GetType())).Cast().LastOrDefault(); if (validateSetAttribute != null) { this.HasParameterSet = true; @@ -50,10 +47,7 @@ public ShowCommandParameterInfo(CommandParameterInfo other) /// public ShowCommandParameterInfo(PSObject other) { - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } + ArgumentNullException.ThrowIfNull(other); this.Name = other.Members["Name"].Value as string; this.IsMandatory = (bool)(other.Members["IsMandatory"].Value); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterSetInfo.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterSetInfo.cs index fb25e2e4745..c5ec1c74c08 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterSetInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterSetInfo.cs @@ -22,14 +22,11 @@ public class ShowCommandParameterSetInfo /// public ShowCommandParameterSetInfo(CommandParameterSetInfo other) { - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } + ArgumentNullException.ThrowIfNull(other); this.Name = other.Name; this.IsDefault = other.IsDefault; - this.Parameters = other.Parameters.Select(x => new ShowCommandParameterInfo(x)).ToArray(); + this.Parameters = other.Parameters.Select(static x => new ShowCommandParameterInfo(x)).ToArray(); } /// @@ -41,15 +38,12 @@ public ShowCommandParameterSetInfo(CommandParameterSetInfo other) /// public ShowCommandParameterSetInfo(PSObject other) { - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } + ArgumentNullException.ThrowIfNull(other); this.Name = other.Members["Name"].Value as string; this.IsDefault = (bool)(other.Members["IsDefault"].Value); var parameters = (other.Members["Parameters"].Value as PSObject).BaseObject as System.Collections.ArrayList; - this.Parameters = ShowCommandCommandInfo.GetObjectEnumerable(parameters).Cast().Select(x => new ShowCommandParameterInfo(x)).ToArray(); + this.Parameters = ShowCommandCommandInfo.GetObjectEnumerable(parameters).Cast().Select(static x => new ShowCommandParameterInfo(x)).ToArray(); } /// diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterType.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterType.cs index 1daf0350dbc..01da285a1d7 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterType.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterType.cs @@ -21,10 +21,7 @@ public class ShowCommandParameterType /// public ShowCommandParameterType(Type other) { - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } + ArgumentNullException.ThrowIfNull(other); this.FullName = other.FullName; if (other.IsEnum) @@ -51,10 +48,7 @@ public ShowCommandParameterType(Type other) /// public ShowCommandParameterType(PSObject other) { - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } + ArgumentNullException.ThrowIfNull(other); this.IsEnum = (bool)(other.Members["IsEnum"].Value); this.FullName = other.Members["FullName"].Value as string; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowMarkdownCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowMarkdownCommand.cs index 63ee15f6f59..3f40ec3439e 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowMarkdownCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowMarkdownCommand.cs @@ -224,10 +224,7 @@ private void ProcessMarkdownInfo(MarkdownInfo markdownInfo) /// protected override void EndProcessing() { - if (_powerShell != null) - { - _powerShell.Dispose(); - } + _powerShell?.Dispose(); } } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Sort-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Sort-Object.cs index 06f5ebfffad..365a669c186 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Sort-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Sort-Object.cs @@ -147,7 +147,7 @@ private int Heapify(List dataToSort, OrderByPropertyCompar // Tracking the index is necessary so that unsortable items can be output at the end, in the order // in which they were received. - for (int dataIndex = 0, discardedDuplicates = 0; dataIndex < dataToSort.Count - discardedDuplicates; dataIndex++) + for (int dataIndex = 0, discardedDuplicates = 0; dataIndex + discardedDuplicates < dataToSort.Count; dataIndex++) { // Min-heap: if the heap is full and the root item is larger than the entry, discard the entry // Max-heap: if the heap is full and the root item is smaller than the entry, discard the entry @@ -157,20 +157,19 @@ private int Heapify(List dataToSort, OrderByPropertyCompar } // If we're doing a unique sort and the entry is not unique, discard the duplicate entry - if (Unique && !uniqueSet.Add(dataToSort[dataIndex])) + if (Unique && !uniqueSet.Add(dataToSort[dataIndex + discardedDuplicates])) { discardedDuplicates++; - if (dataIndex != dataToSort.Count - discardedDuplicates) - { - // When discarding duplicates, replace them with an item at the end of the list and - // adjust our counter so that we check the item we just swapped in next - dataToSort[dataIndex] = dataToSort[dataToSort.Count - discardedDuplicates]; - dataIndex--; - } - + dataIndex--; continue; } + // Shift next non-duplicate entry into place + if (discardedDuplicates > 0) + { + dataToSort[dataIndex] = dataToSort[dataIndex + discardedDuplicates]; + } + // Add the current item to the heap and bubble it up into the correct position int childIndex = dataIndex; while (childIndex > 0) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs index a57d635f3eb..df1c8b76fb8 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs @@ -44,17 +44,26 @@ public void Dispose() /// [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; } /// /// Allows sleep time to be specified in milliseconds. /// [Parameter(Mandatory = true, ParameterSetName = "Milliseconds", ValueFromPipelineByPropertyName = true)] - [ValidateRangeAttribute(0, int.MaxValue)] + [ValidateRange(0, int.MaxValue)] [Alias("ms")] public int Milliseconds { get; set; } + /// + /// Allows sleep time to be specified as a TimeSpan. + /// + [Parameter(Position = 0, Mandatory = true, ParameterSetName = "FromTimeSpan", ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + [ValidateRange(ValidateRangeKind.NonNegative)] + [Alias("ts")] + public TimeSpan Duration { get; set; } + #endregion #region methods @@ -82,10 +91,7 @@ private void Sleep(int milliSecondsToSleep) } } - if (_waitHandle != null) - { - _waitHandle.WaitOne(milliSecondsToSleep, true); - } + _waitHandle?.WaitOne(milliSecondsToSleep, true); } /// @@ -104,6 +110,26 @@ protected override void ProcessRecord() case "Milliseconds": sleepTime = Milliseconds; break; + + case "FromTimeSpan": + if (Duration.TotalMilliseconds > int.MaxValue) + { + PSArgumentException argumentException = PSTraceSource.NewArgumentException( + nameof(Duration), + StartSleepStrings.MaximumDurationExceeded, + TimeSpan.FromMilliseconds(int.MaxValue), + Duration); + + ThrowTerminatingError( + new ErrorRecord( + argumentException, + "MaximumDurationExceeded", + ErrorCategory.InvalidArgument, + targetObject: null)); + } + + sleepTime = (int)Math.Floor(Duration.TotalMilliseconds); + break; default: Dbg.Diagnostics.Assert(false, "Only one of the specified parameter sets should be called."); @@ -121,10 +147,7 @@ protected override void StopProcessing() lock (_syncObject) { _stopping = true; - if (_waitHandle != null) - { - _waitHandle.Set(); - } + _waitHandle?.Set(); } } 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 78ca964d408..27ea15406f7 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Tee-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Tee-Object.cs @@ -3,6 +3,7 @@ using System; using System.Management.Automation; +using System.Text; using Microsoft.PowerShell.Commands.Internal.Format; @@ -72,6 +73,16 @@ public SwitchParameter Append private bool _append; + /// + /// Gets or sets the Encoding. + /// + [Parameter(ParameterSetName = "File")] + [Parameter(ParameterSetName = "LiteralFile")] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] + [ValidateNotNullOrEmpty] + public Encoding Encoding { get; set; } = Encoding.Default; + /// /// Variable parameter. /// @@ -95,12 +106,14 @@ protected override void BeginProcessing() _commandWrapper.Initialize(Context, "out-file", typeof(OutFileCommand)); _commandWrapper.AddNamedParameter("filepath", _fileName); _commandWrapper.AddNamedParameter("append", _append); + _commandWrapper.AddNamedParameter("encoding", Encoding); } else if (string.Equals(ParameterSetName, "LiteralFile", StringComparison.OrdinalIgnoreCase)) { _commandWrapper.Initialize(Context, "out-file", typeof(OutFileCommand)); _commandWrapper.AddNamedParameter("LiteralPath", _fileName); _commandWrapper.AddNamedParameter("append", _append); + _commandWrapper.AddNamedParameter("encoding", Encoding); } else { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs index c8ba1e7ea48..32603f160f8 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs @@ -4,30 +4,83 @@ using System; using System.Globalization; using System.IO; +using System.Linq; using System.Management.Automation; -using System.Reflection; -using System.Runtime.ExceptionServices; +using System.Net.Http; using System.Security; -using Newtonsoft.Json.Linq; -using NJsonSchema; +using System.Text.Json; +using System.Text.Json.Nodes; +using Json.Schema; namespace Microsoft.PowerShell.Commands { /// /// This class implements Test-Json command. /// - [Cmdlet(VerbsDiagnostic.Test, "Json", DefaultParameterSetName = ParameterAttribute.AllParameterSets, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096609")] + [Cmdlet(VerbsDiagnostic.Test, "Json", DefaultParameterSetName = JsonStringParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096609")] + [OutputType(typeof(bool))] public class TestJsonCommand : PSCmdlet { - private const string SchemaFileParameterSet = "SchemaFile"; - private const string SchemaStringParameterSet = "SchemaString"; + #region Parameter Set Names + + private const string JsonStringParameterSet = "JsonString"; + private const string JsonStringWithSchemaStringParameterSet = "JsonStringWithSchemaString"; + private const string JsonStringWithSchemaFileParameterSet = "JsonStringWithSchemaFile"; + private const string JsonPathParameterSet = "JsonPath"; + private const string JsonPathWithSchemaStringParameterSet = "JsonPathWithSchemaString"; + private const string JsonPathWithSchemaFileParameterSet = "JsonPathWithSchemaFile"; + private const string JsonLiteralPathParameterSet = "JsonLiteralPath"; + private const string JsonLiteralPathWithSchemaStringParameterSet = "JsonLiteralPathWithSchemaString"; + private const string JsonLiteralPathWithSchemaFileParameterSet = "JsonLiteralPathWithSchemaFile"; + + #endregion + + #region Json Document Option Constants + + private const string IgnoreCommentsOption = "IgnoreComments"; + private const string AllowTrailingCommasOption = "AllowTrailingCommas"; + + #endregion + + #region Parameters /// /// Gets or sets JSON string to be validated. /// - [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)] + [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ParameterSetName = JsonStringParameterSet)] + [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ParameterSetName = JsonStringWithSchemaStringParameterSet)] + [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ParameterSetName = JsonStringWithSchemaFileParameterSet)] public string Json { get; set; } + /// + /// Gets or sets JSON file path to be validated. + /// + [Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = JsonPathParameterSet)] + [Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = JsonPathWithSchemaStringParameterSet)] + [Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = JsonPathWithSchemaFileParameterSet)] + public string Path { get; set; } + + /// + /// Gets or sets JSON literal file path to be validated. + /// + [Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = JsonLiteralPathParameterSet)] + [Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = JsonLiteralPathWithSchemaStringParameterSet)] + [Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = JsonLiteralPathWithSchemaFileParameterSet)] + [Alias("PSPath", "LP")] + public string LiteralPath + { + get + { + return _isLiteralPath ? Path : null; + } + + set + { + _isLiteralPath = true; + Path = value; + } + } + /// /// Gets or sets schema to validate the JSON against. /// This is optional parameter. @@ -36,46 +89,82 @@ public class TestJsonCommand : PSCmdlet /// then validates the JSON against the schema. Before testing the JSON string, /// the cmdlet parses the schema doing implicitly check the schema too. /// - [Parameter(Position = 1, ParameterSetName = SchemaStringParameterSet)] + [Parameter(Position = 1, Mandatory = true, ParameterSetName = JsonStringWithSchemaStringParameterSet)] + [Parameter(Position = 1, Mandatory = true, ParameterSetName = JsonPathWithSchemaStringParameterSet)] + [Parameter(Position = 1, Mandatory = true, ParameterSetName = JsonLiteralPathWithSchemaStringParameterSet)] [ValidateNotNullOrEmpty] public string Schema { get; set; } /// - /// Gets or sets path to the file containg schema to validate the JSON string against. + /// Gets or sets path to the file containing schema to validate the JSON string against. /// This is optional parameter. /// - [Parameter(Position = 1, ParameterSetName = SchemaFileParameterSet)] + [Parameter(Position = 1, Mandatory = true, ParameterSetName = JsonStringWithSchemaFileParameterSet)] + [Parameter(Position = 1, Mandatory = true, ParameterSetName = JsonPathWithSchemaFileParameterSet)] + [Parameter(Position = 1, Mandatory = true, ParameterSetName = JsonLiteralPathWithSchemaFileParameterSet)] [ValidateNotNullOrEmpty] public string SchemaFile { get; set; } - private JsonSchema _jschema; - /// - /// Process all exceptions in the AggregateException. - /// Unwrap TargetInvocationException if any and - /// rethrow inner exception without losing the stack trace. + /// Gets or sets JSON document options. /// - /// AggregateException to be unwrapped. - /// Return value is unreachable since we always rethrow. - private static bool UnwrapException(Exception e) - { - if (e.InnerException != null && e is TargetInvocationException) - { - ExceptionDispatchInfo.Capture(e.InnerException).Throw(); - } - else - { - ExceptionDispatchInfo.Capture(e).Throw(); - } + [Parameter] + [ValidateNotNullOrEmpty] + [ValidateSet(IgnoreCommentsOption, AllowTrailingCommasOption)] + public string[] Options { get; set; } = Array.Empty(); - return true; - } + #endregion + + #region Private Members + + private bool _isLiteralPath = false; + private JsonSchema _jschema; + private JsonDocumentOptions _documentOptions; + + #endregion /// /// Prepare a JSON schema. /// protected override void BeginProcessing() { + // By default, a JSON Schema implementation isn't supposed to automatically fetch content. + // Instead JsonSchema.Net has been set up with a registry so that users can pre-register + // any schemas they may need to resolve. + // However, pre-registering schemas doesn't make sense in the context of a Powershell command, + // and automatically fetching referenced URIs is likely the preferred behavior. To do that, + // this property must be set with a method to retrieve and deserialize the content. + // For more information, see https://json-everything.net/json-schema#automatic-resolution + SchemaRegistry.Global.Fetch = static uri => + { + try + { + string text; + switch (uri.Scheme) + { + case "http": + case "https": + { + using var client = new HttpClient(); + text = client.GetStringAsync(uri).Result; + break; + } + case "file": + var filename = Uri.UnescapeDataString(uri.AbsolutePath); + text = File.ReadAllText(filename); + break; + default: + throw new FormatException(string.Format(TestJsonCmdletStrings.InvalidUriScheme, uri.Scheme)); + } + + return JsonSerializer.Deserialize(text); + } + catch (Exception e) + { + throw new JsonSchemaReferenceResolutionException(e); + } + }; + string resolvedpath = string.Empty; try @@ -84,13 +173,12 @@ protected override void BeginProcessing() { try { - _jschema = JsonSchema.FromJsonAsync(Schema).Result; + _jschema = JsonSchema.FromText(Schema); } - catch (AggregateException ae) + catch (JsonException e) { - // Even if only one exception is thrown, it is still wrapped in an AggregateException exception - // https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/exception-handling-task-parallel-library - ae.Handle(UnwrapException); + Exception exception = new(TestJsonCmdletStrings.InvalidJsonSchema, e); + WriteError(new ErrorRecord(exception, "InvalidJsonSchema", ErrorCategory.InvalidData, Schema)); } } else if (SchemaFile != null) @@ -98,17 +186,18 @@ protected override void BeginProcessing() try { resolvedpath = Context.SessionState.Path.GetUnresolvedProviderPathFromPSPath(SchemaFile); - _jschema = JsonSchema.FromFileAsync(resolvedpath).Result; + _jschema = JsonSchema.FromFile(resolvedpath); } - catch (AggregateException ae) + catch (JsonException e) { - ae.Handle(UnwrapException); + Exception exception = new(TestJsonCmdletStrings.InvalidJsonSchema, e); + WriteError(new ErrorRecord(exception, "InvalidJsonSchema", ErrorCategory.InvalidData, SchemaFile)); } } } catch (Exception e) when ( // Handle exceptions related to file access to provide more specific error message - // https://docs.microsoft.com/en-us/dotnet/standard/io/handling-io-errors + // https://learn.microsoft.com/dotnet/standard/io/handling-io-errors e is IOException || e is UnauthorizedAccessException || e is NotSupportedException || @@ -128,6 +217,14 @@ e is SecurityException Exception exception = new(TestJsonCmdletStrings.InvalidJsonSchema, e); ThrowTerminatingError(new ErrorRecord(exception, "InvalidJsonSchema", ErrorCategory.InvalidData, resolvedpath)); } + + _documentOptions = new JsonDocumentOptions + { + CommentHandling = Options.Contains(IgnoreCommentsOption, StringComparer.OrdinalIgnoreCase) + ? JsonCommentHandling.Skip + : JsonCommentHandling.Disallow, + AllowTrailingCommas = Options.Contains(AllowTrailingCommasOption, StringComparer.OrdinalIgnoreCase) + }; } /// @@ -135,31 +232,61 @@ e is SecurityException /// protected override void ProcessRecord() { - JObject parsedJson = null; bool result = true; + string jsonToParse = string.Empty; + + if (Json != null) + { + jsonToParse = Json; + } + else if (Path != null) + { + string resolvedPath = PathUtils.ResolveFilePath(Path, this, _isLiteralPath); + + if (!File.Exists(resolvedPath)) + { + ItemNotFoundException exception = new( + Path, + "PathNotFound", + SessionStateStrings.PathNotFound); + + ThrowTerminatingError(exception.ErrorRecord); + } + + jsonToParse = File.ReadAllText(resolvedPath); + } + try { - parsedJson = JObject.Parse(Json); + + var parsedJson = JsonNode.Parse(jsonToParse, nodeOptions: null, _documentOptions); if (_jschema != null) { - var errorMessages = _jschema.Validate(parsedJson); - if (errorMessages != null && errorMessages.Count != 0) + EvaluationResults evaluationResults = _jschema.Evaluate(parsedJson, new EvaluationOptions { OutputFormat = OutputFormat.List }); + result = evaluationResults.IsValid; + if (!result) { - result = false; - - Exception exception = new(TestJsonCmdletStrings.InvalidJsonAgainstSchema); + HandleValidationErrors(evaluationResults); - foreach (var message in errorMessages) + if (evaluationResults.HasDetails) { - ErrorRecord errorRecord = new(exception, "InvalidJsonAgainstSchema", ErrorCategory.InvalidData, null); - errorRecord.ErrorDetails = new ErrorDetails(message.ToString()); - WriteError(errorRecord); + foreach (var nestedResult in evaluationResults.Details) + { + HandleValidationErrors(nestedResult); + } } } } } + catch (JsonSchemaReferenceResolutionException jsonExc) + { + result = false; + + Exception exception = new(TestJsonCmdletStrings.InvalidJsonSchema, jsonExc); + WriteError(new ErrorRecord(exception, "InvalidJsonSchema", ErrorCategory.InvalidData, _jschema)); + } catch (Exception exc) { result = false; @@ -170,5 +297,20 @@ protected override void ProcessRecord() WriteObject(result); } + + private void HandleValidationErrors(EvaluationResults evaluationResult) + { + if (!evaluationResult.HasErrors) + { + return; + } + + foreach (var error in evaluationResult.Errors!) + { + Exception exception = new(string.Format(TestJsonCmdletStrings.InvalidJsonAgainstSchemaDetailed, error.Value, evaluationResult.InstanceLocation)); + ErrorRecord errorRecord = new(exception, "InvalidJsonAgainstSchemaDetailed", ErrorCategory.InvalidData, null); + WriteError(errorRecord); + } + } } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs index 5c297ed86d5..6633a66f96f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs @@ -137,7 +137,7 @@ protected override void ProcessRecord() } } #else - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + if (Platform.IsLinux) { string errorMessage = UnblockFileStrings.LinuxNotSupported; Exception e = new PlatformNotSupportedException(errorMessage); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnregisterEventCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnregisterEventCommand.cs index f0f87eb9f66..cf65a1f73b3 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnregisterEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnregisterEventCommand.cs @@ -47,7 +47,7 @@ public string SourceIdentifier /// /// Flag that determines if we should include subscriptions used to support other subscriptions. /// - [Parameter()] + [Parameter] public SwitchParameter Force { get; set; } #endregion parameters diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-List.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-List.cs index 5850eaca7db..99507d0461b 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-List.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-List.cs @@ -26,7 +26,7 @@ public class UpdateListCommand : PSCmdlet /// Objects to add to the list. /// [Parameter(ParameterSetName = "AddRemoveSet")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public object[] Add { get; set; } @@ -35,7 +35,7 @@ public class UpdateListCommand : PSCmdlet /// Objects to be removed from the list. /// [Parameter(ParameterSetName = "AddRemoveSet")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public object[] Remove { get; set; } @@ -44,7 +44,7 @@ public class UpdateListCommand : PSCmdlet /// Objects in this list replace the objects in the target list. /// [Parameter(Mandatory = true, ParameterSetName = "ReplaceSet")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public object[] Replace { get; set; } @@ -55,7 +55,7 @@ public class UpdateListCommand : PSCmdlet // [Parameter(ValueFromPipeline = true, ParameterSetName = "AddRemoveSet")] // [Parameter(ValueFromPipeline = true, ParameterSetName = "ReplaceSet")] [Parameter(ValueFromPipeline = true)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public PSObject InputObject { get; set; } /// @@ -65,7 +65,7 @@ public class UpdateListCommand : PSCmdlet // [Parameter(Position = 0, ParameterSetName = "AddRemoveSet")] // [Parameter(Position = 0, ParameterSetName = "ReplaceSet")] [Parameter(Position = 0)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public string Property { get; set; } private PSListModifier _listModifier; @@ -83,10 +83,7 @@ protected override void ProcessRecord() } else { - if (_listModifier == null) - { - _listModifier = CreatePSListModifier(); - } + _listModifier ??= CreatePSListModifier(); PSMemberInfo memberInfo = InputObject.Members[Property]; if (memberInfo != null) 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 d137524629d..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. /// [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = DynamicTypeSet)] - [ArgumentToTypeNameTransformationAttribute()] + [ArgumentToTypeNameTransformation] [ValidateNotNullOrEmpty] public string TypeName { @@ -792,9 +792,8 @@ private void ProcessTypeFiles() if (ShouldProcess(formattedTarget, action)) { - if (!fullFileNameHash.Contains(resolvedPath)) + if (fullFileNameHash.Add(resolvedPath)) { - fullFileNameHash.Add(resolvedPath); newTypes.Add(new SessionStateTypeEntry(prependPathTotal[i])); } } @@ -806,9 +805,8 @@ private void ProcessTypeFiles() if (entry.FileName != null) { string resolvedPath = ModuleCmdletBase.ResolveRootedFilePath(entry.FileName, Context) ?? entry.FileName; - if (!fullFileNameHash.Contains(resolvedPath)) + if (fullFileNameHash.Add(resolvedPath)) { - fullFileNameHash.Add(resolvedPath); newTypes.Add(entry); } } @@ -825,9 +823,8 @@ private void ProcessTypeFiles() if (ShouldProcess(formattedTarget, action)) { - if (!fullFileNameHash.Contains(resolvedPath)) + if (fullFileNameHash.Add(resolvedPath)) { - fullFileNameHash.Add(resolvedPath); newTypes.Add(new SessionStateTypeEntry(appendPathTotalItem)); } } @@ -849,8 +846,7 @@ private void ProcessTypeFiles() } else if (sste.FileName != null) { - bool unused; - Context.TypeTable.Update(sste.FileName, sste.FileName, errors, Context.AuthorizationManager, Context.InitialSessionState.Host, out unused); + Context.TypeTable.Update(sste.FileName, sste.FileName, errors, Context.AuthorizationManager, Context.InitialSessionState.Host, out _); } else { @@ -972,9 +968,8 @@ protected override void ProcessRecord() if (ShouldProcess(formattedTarget, action)) { - if (!fullFileNameHash.Contains(appendPathTotalItem)) + if (fullFileNameHash.Add(appendPathTotalItem)) { - fullFileNameHash.Add(appendPathTotalItem); newFormats.Add(new SessionStateFormatEntry(appendPathTotalItem)); } } @@ -1056,7 +1051,7 @@ public class RemoveTypeDataCommand : PSCmdlet /// The target type to remove. /// [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = RemoveTypeSet)] - [ArgumentToTypeNameTransformationAttribute()] + [ArgumentToTypeNameTransformation] [ValidateNotNullOrEmpty] public string TypeName { @@ -1118,7 +1113,10 @@ protected override void ProcessRecord() string removeFileTarget = UpdateDataStrings.UpdateTarget; Collection typeFileTotal = UpdateData.Glob(_typeFiles, "TypePathException", this); - if (typeFileTotal.Count == 0) { return; } + if (typeFileTotal.Count == 0) + { + return; + } // Key of the map is the name of the file that is in the cache. Value of the map is a index list. Duplicate files might // exist in the cache because the user can add arbitrary files to the cache by $host.Runspace.InitialSessionState.Types.Add() @@ -1130,7 +1128,10 @@ protected override void ProcessRecord() for (int index = 0; index < Context.InitialSessionState.Types.Count; index++) { string fileName = Context.InitialSessionState.Types[index].FileName; - if (fileName == null) { continue; } + if (fileName == null) + { + continue; + } // Resolving the file path because the path to the types file in module manifest is now specified as // ..\..\types.ps1xml which expands to C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Microsoft.PowerShell.Core\..\..\types.ps1xml @@ -1161,10 +1162,7 @@ protected override void ProcessRecord() indicesToRemove.Sort(); for (int i = indicesToRemove.Count - 1; i >= 0; i--) { - if (Context.InitialSessionState != null) - { - Context.InitialSessionState.Types.RemoveItem(indicesToRemove[i]); - } + Context.InitialSessionState?.Types.RemoveItem(indicesToRemove[i]); } try @@ -1337,7 +1335,6 @@ protected override void ProcessRecord() ValidateTypeName(); Dictionary alltypes = Context.TypeTable.GetAllTypeData(); - Collection typedefs = new(); foreach (string type in alltypes.Keys) { @@ -1345,17 +1342,11 @@ protected override void ProcessRecord() { if (pattern.IsMatch(type)) { - typedefs.Add(alltypes[type]); + WriteObject(alltypes[type]); break; } } } - - // write out all the available type definitions - foreach (TypeData typedef in typedefs) - { - WriteObject(typedef); - } } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs index 69b89e928ce..1e3cc038750 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs @@ -78,15 +78,10 @@ public static class UtilityResources public static string FileReadError { get { return UtilityCommonStrings.FileReadError; } } /// - /// The resource string used to indicate 'PATH:' in the formating header. + /// The resource string used to indicate 'PATH:' in the formatting header. /// public static string FormatHexPathPrefix { get { return UtilityCommonStrings.FormatHexPathPrefix; } } - /// - /// Error message to indicate that requested algorithm is not supported on the target platform. - /// - public static string AlgorithmTypeNotSupported { get { return UtilityCommonStrings.AlgorithmTypeNotSupported; } } - /// /// The file '{0}' could not be parsed as a PowerShell Data File. /// @@ -188,11 +183,11 @@ public ByteCollection(byte[] value) /// Gets the Offset address to be used while displaying the bytes in the collection. /// [Obsolete("The property is deprecated, please use Offset64 instead.", true)] - public UInt32 Offset + public uint Offset { get { - return (UInt32)Offset64; + return (uint)Offset64; } private set @@ -204,7 +199,7 @@ private set /// /// Gets the Offset address to be used while displaying the bytes in the collection. /// - public UInt64 Offset64 { get; private set; } + public ulong Offset64 { get; private set; } /// /// Gets underlying bytes stored in the collection. @@ -220,7 +215,7 @@ private set /// /// Gets the hexadecimal representation of the value. /// - public string HexOffset { get => string.Format(CultureInfo.CurrentCulture, "{0:X16}", Offset64); } + public string HexOffset => string.Create(CultureInfo.CurrentCulture, $"{Offset64:X16}"); /// /// Gets the type of the input objects used to create the . diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs index 20f8f847475..12cfb784c72 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs @@ -22,6 +22,7 @@ public abstract class VariableCommandBase : PSCmdlet /// [Parameter] [ValidateNotNullOrEmpty] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } #endregion parameters @@ -38,10 +39,7 @@ protected string[] IncludeFilters set { - if (value == null) - { - value = Array.Empty(); - } + value ??= Array.Empty(); _include = value; } @@ -61,10 +59,7 @@ protected string[] ExcludeFilters set { - if (value == null) - { - value = Array.Empty(); - } + value ??= Array.Empty(); _exclude = value; } @@ -258,10 +253,7 @@ public string[] Name set { - if (value == null) - { - value = new string[] { "*" }; - } + value ??= new string[] { "*" }; _name = value; } @@ -336,7 +328,7 @@ protected override void ProcessRecord() GetMatchingVariables(varName, Scope, out wasFiltered, /*quiet*/ false); matchingVariables.Sort( - (PSVariable left, PSVariable right) => StringComparer.CurrentCultureIgnoreCase.Compare(left.Name, right.Name)); + static (PSVariable left, PSVariable right) => StringComparer.CurrentCultureIgnoreCase.Compare(left.Name, right.Name)); bool matchFound = false; foreach (PSVariable matchingVariable in matchingVariables) @@ -374,6 +366,7 @@ protected override void ProcessRecord() /// [Cmdlet(VerbsCommon.New, "Variable", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097121")] + [OutputType(typeof(PSVariable))] public sealed class NewVariableCommand : VariableCommandBase { #region parameters @@ -700,6 +693,13 @@ public SwitchParameter PassThru private bool _passThru; + /// + /// Gets whether we will append to the variable if it exists. + /// + [Parameter] + [Experimental(ExperimentalFeature.PSRedirectToVariable, ExperimentAction.Show)] + public SwitchParameter Append { get; set; } + private bool _nameIsFormalParameter; private bool _valueIsFormalParameter; #endregion parameters @@ -718,6 +718,33 @@ protected override void BeginProcessing() { _valueIsFormalParameter = true; } + + if (Append) + { + // create the list here and add to it if it has a value + // but if they have more than one name, produce an error + if (Name.Length != 1) + { + ErrorRecord appendVariableError = new ErrorRecord(new InvalidOperationException(), "SetVariableAppend", ErrorCategory.InvalidOperation, Name); + appendVariableError.ErrorDetails = new ErrorDetails("SetVariableAppend"); + appendVariableError.ErrorDetails.RecommendedAction = VariableCommandStrings.UseSingleVariable; + ThrowTerminatingError(appendVariableError); + } + + _valueList = new List(); + var currentValue = Context.SessionState.PSVariable.Get(Name[0]); + if (currentValue is not null) + { + if (currentValue.Value is IList ilist) + { + _valueList.AddRange(ilist); + } + else + { + _valueList.Add(currentValue.Value); + } + } + } } /// @@ -733,6 +760,16 @@ protected override void ProcessRecord() { if (_nameIsFormalParameter && _valueIsFormalParameter) { + if (Append) + { + if (Value != AutomationNull.Value) + { + _valueList ??= new List(); + + _valueList.Add(Value); + } + } + return; } @@ -740,10 +777,7 @@ protected override void ProcessRecord() { if (Value != AutomationNull.Value) { - if (_valueList == null) - { - _valueList = new List(); - } + _valueList ??= new List(); _valueList.Add(Value); } @@ -766,7 +800,14 @@ protected override void EndProcessing() { if (_valueIsFormalParameter) { - SetVariable(Name, Value); + if (Append) + { + SetVariable(Name, _valueList); + } + else + { + SetVariable(Name, Value); + } } else { @@ -870,10 +911,7 @@ private void SetVariable(string[] varNames, object varValue) newVarValue, newOptions); - if (Description == null) - { - Description = string.Empty; - } + Description ??= string.Empty; varToSet.Description = Description; @@ -1102,10 +1140,7 @@ protected override void ProcessRecord() // Removal of variables only happens in the local scope if the // scope wasn't explicitly specified by the user. - if (Scope == null) - { - Scope = "local"; - } + Scope ??= "local"; foreach (string varName in Name) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WaitEventCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WaitEventCommand.cs index 70ac9e4ae95..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 /// [Parameter] [Alias("TimeoutSec")] - [ValidateRangeAttribute(-1, Int32.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 969dd8231c8..9bd76f99413 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 @@ -1,13 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + +using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Management.Automation; using System.Net.Http; using System.Text; using System.Text.RegularExpressions; +using System.Threading; namespace Microsoft.PowerShell.Commands { @@ -16,38 +21,27 @@ namespace Microsoft.PowerShell.Commands /// public class BasicHtmlWebResponseObject : WebResponseObject { - #region Private Fields - - private static Regex s_attribNameValueRegex; - private static Regex s_attribsRegex; - private static Regex s_imageRegex; - private static Regex s_inputFieldRegex; - private static Regex s_linkRegex; - private static Regex s_tagRegex; - - #endregion Private Fields - #region Constructors /// /// Initializes a new instance of the class. /// - /// - public BasicHtmlWebResponseObject(HttpResponseMessage response) - : this(response, null) - { } + /// The response. + /// Time permitted between reads or Timeout.InfiniteTimeSpan for no timeout. + /// Cancellation token. + public BasicHtmlWebResponseObject(HttpResponseMessage response, TimeSpan perReadTimeout, CancellationToken cancellationToken) : this(response, null, perReadTimeout, cancellationToken) { } /// /// Initializes a new instance of the class /// with the specified . /// - /// - /// - public BasicHtmlWebResponseObject(HttpResponseMessage response, Stream contentStream) - : base(response, contentStream) + /// The response. + /// The content stream associated with the response. + /// Time permitted between reads or Timeout.InfiniteTimeSpan for no timeout. + /// Cancellation token. + public BasicHtmlWebResponseObject(HttpResponseMessage response, Stream? contentStream, TimeSpan perReadTimeout, CancellationToken cancellationToken) : base(response, contentStream, perReadTimeout, cancellationToken) { - EnsureHtmlParser(); - InitializeContent(); + InitializeContent(cancellationToken); InitializeRawContent(response); } @@ -72,9 +66,9 @@ public BasicHtmlWebResponseObject(HttpResponseMessage response, Stream contentSt /// Encoding of the response body from the Content-Type header, /// or if the encoding could not be determined. /// - public Encoding Encoding { get; private set; } + public Encoding? Encoding { get; private set; } - private WebCmdletElementCollection _inputFields; + private WebCmdletElementCollection? _inputFields; /// /// Gets the HTML input field elements parsed from . @@ -85,10 +79,8 @@ public WebCmdletElementCollection InputFields { if (_inputFields == null) { - EnsureHtmlParser(); - List parsedFields = new(); - MatchCollection fieldMatch = s_inputFieldRegex.Matches(Content); + MatchCollection fieldMatch = HtmlParser.InputFieldRegex.Matches(Content); foreach (Match field in fieldMatch) { parsedFields.Add(CreateHtmlObject(field.Value, "INPUT")); @@ -101,7 +93,7 @@ public WebCmdletElementCollection InputFields } } - private WebCmdletElementCollection _links; + private WebCmdletElementCollection? _links; /// /// Gets the HTML a link elements parsed from . @@ -112,10 +104,8 @@ public WebCmdletElementCollection Links { if (_links == null) { - EnsureHtmlParser(); - List parsedLinks = new(); - MatchCollection linkMatch = s_linkRegex.Matches(Content); + MatchCollection linkMatch = HtmlParser.LinkRegex.Matches(Content); foreach (Match link in linkMatch) { parsedLinks.Add(CreateHtmlObject(link.Value, "A")); @@ -128,7 +118,7 @@ public WebCmdletElementCollection Links } } - private WebCmdletElementCollection _images; + private WebCmdletElementCollection? _images; /// /// Gets the HTML img elements parsed from . @@ -139,10 +129,8 @@ public WebCmdletElementCollection Images { if (_images == null) { - EnsureHtmlParser(); - List parsedImages = new(); - MatchCollection imageMatch = s_imageRegex.Matches(Content); + MatchCollection imageMatch = HtmlParser.ImageRegex.Matches(Content); foreach (Match image in imageMatch) { parsedImages.Add(CreateHtmlObject(image.Value, "IMG")); @@ -162,26 +150,22 @@ public WebCmdletElementCollection Images /// /// Reads the response content from the web response. /// - protected void InitializeContent() + /// The cancellation token. + [MemberNotNull(nameof(Content))] + protected void InitializeContent(CancellationToken cancellationToken) { - string contentType = ContentHelper.GetContentType(BaseResponse); + string? contentType = ContentHelper.GetContentType(BaseResponse); if (ContentHelper.IsText(contentType)) { - Encoding encoding = null; - // fill the Content buffer - string characterSet = WebResponseHelper.GetCharacterSet(BaseResponse); - - if (string.IsNullOrEmpty(characterSet) && ContentHelper.IsJson(contentType)) - { - characterSet = Encoding.UTF8.HeaderName; - } + // Fill the Content buffer + string? characterSet = WebResponseHelper.GetCharacterSet(BaseResponse); - this.Content = StreamHelper.DecodeStream(RawContentStream, characterSet, out encoding); - this.Encoding = encoding; + Content = StreamHelper.DecodeStream(RawContentStream, characterSet, out Encoding encoding, perReadTimeout, cancellationToken); + Encoding = encoding; } else { - this.Content = string.Empty; + Content = string.Empty; } } @@ -197,50 +181,11 @@ private static PSObject CreateHtmlObject(string html, string tagName) return elementObject; } - private static void EnsureHtmlParser() - { - if (s_tagRegex == null) - { - s_tagRegex = new Regex(@"<\w+((\s+[^""'>/=\s\p{Cc}]+(\s*=\s*(?:"".*?""|'.*?'|[^'"">\s]+))?)+\s*|\s*)/?>", - RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled); - } - - if (s_attribsRegex == null) - { - s_attribsRegex = new Regex(@"(?<=\s+)([^""'>/=\s\p{Cc}]+(\s*=\s*(?:"".*?""|'.*?'|[^'"">\s]+))?)", - RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled); - } - - if (s_attribNameValueRegex == null) - { - s_attribNameValueRegex = new Regex(@"([^""'>/=\s\p{Cc}]+)(?:\s*=\s*(?:""(.*?)""|'(.*?)'|([^'"">\s]+)))?", - RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled); - } - - if (s_inputFieldRegex == null) - { - s_inputFieldRegex = new Regex(@"]*(/>|>.*?)", - RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled); - } - - if (s_linkRegex == null) - { - s_linkRegex = new Regex(@"]*(/>|>.*?)", - RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled); - } - - if (s_imageRegex == null) - { - s_imageRegex = new Regex(@"]*?>", - RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled); - } - } - private void InitializeRawContent(HttpResponseMessage baseResponse) { StringBuilder raw = ContentHelper.GetRawContentHeader(baseResponse); raw.Append(Content); - this.RawContent = raw.ToString(); + RawContent = raw.ToString(); } private static void ParseAttributes(string outerHtml, PSObject elementObject) @@ -250,23 +195,23 @@ private static void ParseAttributes(string outerHtml, PSObject elementObject) { // Extract just the opening tag of the HTML element (omitting the closing tag and any contents, // including contained HTML elements) - var match = s_tagRegex.Match(outerHtml); + Match match = HtmlParser.TagRegex.Match(outerHtml); // Extract all the attribute specifications within the HTML element opening tag - var attribMatches = s_attribsRegex.Matches(match.Value); + MatchCollection attribMatches = HtmlParser.AttribsRegex.Matches(match.Value); foreach (Match attribMatch in attribMatches) { // Extract the name and value for this attribute (allowing for variations like single/double/no // quotes, and no value at all) - var nvMatches = s_attribNameValueRegex.Match(attribMatch.Value); + Match nvMatches = HtmlParser.AttribNameValueRegex.Match(attribMatch.Value); Debug.Assert(nvMatches.Groups.Count == 5); // Name is always captured by group #1 string name = nvMatches.Groups[1].Value; // The value (if any) is captured by group #2, #3, or #4, depending on quoting or lack thereof - string value = null; + string? value = null; if (nvMatches.Groups[2].Success) { value = nvMatches.Groups[2].Value; @@ -286,5 +231,21 @@ private static void ParseAttributes(string outerHtml, PSObject elementObject) } #endregion Methods + + // This class is needed so the static Regexes are initialized only the first time they are used + private static class HtmlParser + { + internal static readonly Regex AttribsRegex = new Regex(@"(?<=\s+)([^""'>/=\s\p{Cc}]+(\s*=\s*(?:"".*?""|'.*?'|[^'"">\s]+))?)", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled); + + internal static readonly Regex AttribNameValueRegex = new Regex(@"([^""'>/=\s\p{Cc}]+)(?:\s*=\s*(?:""(.*?)""|'(.*?)'|([^'"">\s]+)))?", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled); + + internal static readonly Regex ImageRegex = new Regex(@"]*?>", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled); + + internal static readonly Regex InputFieldRegex = new Regex(@"]*(/?>|>.*?)", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled); + + internal static readonly Regex LinkRegex = new Regex(@"]*(/>|>.*?)", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled); + + internal static readonly Regex TagRegex = new Regex(@"<\w+((\s+[^""'>/=\s\p{Cc}]+(\s*=\s*(?:"".*?""|'.*?'|[^'"">\s]+))?)+\s*|\s*)/?>", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled); + } } } 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 f16ad99d2a1..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 @@ -1,70 +1,34 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System; +using System.Diagnostics.CodeAnalysis; using System.Management.Automation; using System.Net.Http; using System.Net.Http.Headers; using System.Text; - +using Humanizer; using Microsoft.Win32; namespace Microsoft.PowerShell.Commands { internal static class ContentHelper { - #region Constants - - // default codepage encoding for web content. See RFC 2616. - private const string _defaultCodePage = "ISO-8859-1"; - - #endregion Constants - - #region Fields - - // used to split contentType arguments - private static readonly char[] s_contentTypeParamSeparator = { ';' }; - - #endregion Fields - #region Internal Methods - internal static string GetContentType(HttpResponseMessage response) - { - // ContentType may not exist in response header. Return null if not. - return response.Content.Headers.ContentType?.MediaType; - } + // ContentType may not exist in response header. Return null if not. + internal static string? GetContentType(HttpResponseMessage response) => response.Content.Headers.ContentType?.MediaType; - internal static Encoding GetDefaultEncoding() - { - return GetEncodingOrDefault((string)null); - } + internal static string? GetContentType(HttpRequestMessage request) => request.Content?.Headers.ContentType?.MediaType; - internal static Encoding GetEncoding(HttpResponseMessage response) - { - // ContentType may not exist in response header. - string charSet = response.Content.Headers.ContentType?.CharSet; - return GetEncodingOrDefault(charSet); - } + internal static Encoding GetDefaultEncoding() => Encoding.UTF8; - internal static Encoding GetEncodingOrDefault(string characterSet) - { - // get the name of the codepage to use for response content - string codepage = (string.IsNullOrEmpty(characterSet) ? _defaultCodePage : characterSet); - Encoding encoding = null; - - try - { - encoding = Encoding.GetEncoding(codepage); - } - catch (ArgumentException) - { - // 0, default code page - encoding = Encoding.GetEncoding(0); - } - - return encoding; - } + internal static string GetFriendlyContentLength(long? length) => + length.HasValue + ? $"{length.Value.Bytes().Humanize()} ({length.Value:#,0} bytes)" + : "unknown size"; internal static StringBuilder GetRawContentHeader(HttpResponseMessage response) { @@ -75,14 +39,13 @@ internal static StringBuilder GetRawContentHeader(HttpResponseMessage response) { int statusCode = WebResponseHelper.GetStatusCode(response); string statusDescription = WebResponseHelper.GetStatusDescription(response); - raw.AppendFormat("{0} {1} {2}", protocol, statusCode, statusDescription); - raw.AppendLine(); + raw.AppendLine($"{protocol} {statusCode} {statusDescription}"); } HttpHeaders[] headerCollections = { response.Headers, - response.Content?.Headers + response.Content.Headers }; foreach (var headerCollection in headerCollections) @@ -95,12 +58,9 @@ internal static StringBuilder GetRawContentHeader(HttpResponseMessage response) foreach (var header in headerCollection) { // Headers may have multiple entries with different values - foreach (var headerValue in header.Value) + foreach (string headerValue in header.Value) { - raw.Append(header.Key); - raw.Append(": "); - raw.Append(headerValue); - raw.AppendLine(); + raw.AppendLine($"{header.Key}: {headerValue}"); } } } @@ -109,74 +69,55 @@ internal static StringBuilder GetRawContentHeader(HttpResponseMessage response) return raw; } - internal static bool IsJson(string contentType) - { - contentType = GetContentTypeSignature(contentType); - return CheckIsJson(contentType); - } - - internal static bool IsText(string contentType) - { - contentType = GetContentTypeSignature(contentType); - return CheckIsText(contentType); - } - - internal static bool IsXml(string contentType) - { - contentType = GetContentTypeSignature(contentType); - return CheckIsXml(contentType); - } - - #endregion Internal Methods - - #region Private Helper Methods - - private static bool CheckIsJson(string contentType) + internal static bool IsJson([NotNullWhen(true)] string? contentType) { if (string.IsNullOrEmpty(contentType)) + { return false; + } - // the correct type for JSON content, as specified in RFC 4627 + // The correct type for JSON content, as specified in RFC 4627 bool isJson = contentType.Equals("application/json", StringComparison.OrdinalIgnoreCase); - // add in these other "javascript" related types that + // Add in these other "javascript" related types that // sometimes get sent down as the mime type for JSON content isJson |= contentType.Equals("text/json", StringComparison.OrdinalIgnoreCase) - || contentType.Equals("application/x-javascript", StringComparison.OrdinalIgnoreCase) - || contentType.Equals("text/x-javascript", StringComparison.OrdinalIgnoreCase) - || contentType.Equals("application/javascript", StringComparison.OrdinalIgnoreCase) - || contentType.Equals("text/javascript", StringComparison.OrdinalIgnoreCase); + || contentType.Equals("application/x-javascript", StringComparison.OrdinalIgnoreCase) + || contentType.Equals("text/x-javascript", StringComparison.OrdinalIgnoreCase) + || contentType.Equals("application/javascript", StringComparison.OrdinalIgnoreCase) + || contentType.Equals("text/javascript", StringComparison.OrdinalIgnoreCase); - return (isJson); + return isJson; } - private static bool CheckIsText(string contentType) + internal static bool IsText([NotNullWhen(true)] string? contentType) { if (string.IsNullOrEmpty(contentType)) + { return false; + } - // any text, xml or json types are text + // Any text, xml or json types are text bool isText = contentType.StartsWith("text/", StringComparison.OrdinalIgnoreCase) - || CheckIsXml(contentType) - || CheckIsJson(contentType); + || IsXml(contentType) + || IsJson(contentType); // Further content type analysis is available on Windows if (Platform.IsWindows && !isText) { // Media types registered with Windows as having a perceived type of text, are text - using (RegistryKey contentTypeKey = Registry.ClassesRoot.OpenSubKey(@"MIME\Database\Content Type\" + contentType)) + using (RegistryKey? contentTypeKey = Registry.ClassesRoot.OpenSubKey(@"MIME\Database\Content Type\" + contentType)) { if (contentTypeKey != null) { - string extension = contentTypeKey.GetValue("Extension") as string; - if (extension != null) + if (contentTypeKey.GetValue("Extension") is string extension) { - using (RegistryKey extensionKey = Registry.ClassesRoot.OpenSubKey(extension)) + using (RegistryKey? extensionKey = Registry.ClassesRoot.OpenSubKey(extension)) { if (extensionKey != null) { - string perceivedType = extensionKey.GetValue("PerceivedType") as string; - isText = (perceivedType == "text"); + string? perceivedType = extensionKey.GetValue("PerceivedType") as string; + isText = perceivedType == "text"; } } } @@ -184,32 +125,28 @@ private static bool CheckIsText(string contentType) } } - return (isText); + return isText; } - private static bool CheckIsXml(string contentType) + internal static bool IsXml([NotNullWhen(true)] string? contentType) { if (string.IsNullOrEmpty(contentType)) + { return false; + } // RFC 3023: Media types with the suffix "+xml" are XML - bool isXml = (contentType.Equals("application/xml", StringComparison.OrdinalIgnoreCase) - || contentType.Equals("application/xml-external-parsed-entity", StringComparison.OrdinalIgnoreCase) - || contentType.Equals("application/xml-dtd", StringComparison.OrdinalIgnoreCase)); + bool isXml = contentType.Equals("application/xml", StringComparison.OrdinalIgnoreCase) + || contentType.Equals("application/xml-external-parsed-entity", StringComparison.OrdinalIgnoreCase) + || contentType.Equals("application/xml-dtd", StringComparison.OrdinalIgnoreCase) + || contentType.EndsWith("+xml", StringComparison.OrdinalIgnoreCase); - isXml |= contentType.EndsWith("+xml", StringComparison.OrdinalIgnoreCase); - return (isXml); + return isXml; } - private static string GetContentTypeSignature(string contentType) - { - if (string.IsNullOrEmpty(contentType)) - return null; + internal static bool IsTextBasedContentType([NotNullWhen(true)] string? contentType) + => IsText(contentType) || IsJson(contentType) || IsXml(contentType); - string sig = contentType.Split(s_contentTypeParamSeparator, 2)[0].ToUpperInvariant(); - return (sig); - } - - #endregion Private Helper Methods + #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 new file mode 100644 index 00000000000..05eb8ed96b6 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/HttpVersionCompletionsAttribute.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Management.Automation; +using System.Net; +using System.Reflection; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// A completer for HTTP version names. + /// + internal sealed class HttpVersionCompletionsAttribute : ArgumentCompletionsAttribute + { + public static readonly string[] AllowedVersions; + + static HttpVersionCompletionsAttribute() + { + FieldInfo[] fields = typeof(HttpVersion).GetFields(BindingFlags.Static | BindingFlags.Public); + + var versions = new List(fields.Length - 1); + + for (int i = 0; i < fields.Length; i++) + { + // skip field Unknown and not Version type + if (fields[i].Name == nameof(HttpVersion.Unknown) || fields[i].FieldType != typeof(Version)) + { + continue; + } + + var version = (Version?)fields[i].GetValue(null); + + if (version is not null) + { + versions.Add(version.ToString()); + } + } + + AllowedVersions = versions.ToArray(); + } + + /// + 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 235aaf82773..eafdaadbede 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 @@ -1,11 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Management.Automation; using System.Net.Http; using System.Text; +using System.Threading; using System.Xml; using Newtonsoft.Json; @@ -13,36 +17,18 @@ namespace Microsoft.PowerShell.Commands { - public partial class InvokeRestMethodCommand + /// + /// The Invoke-RestMethod command + /// This command makes an HTTP or HTTPS request to a web service, + /// and returns the response in an appropriate way. + /// Intended to work against the wide spectrum of "RESTful" web services + /// currently deployed across the web. + /// + [Cmdlet(VerbsLifecycle.Invoke, "RestMethod", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096706", DefaultParameterSetName = "StandardMethod")] + public class InvokeRestMethodCommand : WebRequestPSCmdlet { #region Parameters - /// - /// Gets or sets the parameter Method. - /// - [Parameter(ParameterSetName = "StandardMethod")] - [Parameter(ParameterSetName = "StandardMethodNoProxy")] - public override WebRequestMethod Method - { - get { return base.Method; } - - set { base.Method = value; } - } - - /// - /// Gets or sets the parameter CustomMethod. - /// - [Parameter(Mandatory = true, ParameterSetName = "CustomMethod")] - [Parameter(Mandatory = true, ParameterSetName = "CustomMethodNoProxy")] - [Alias("CM")] - [ValidateNotNullOrEmpty] - public override string CustomMethod - { - get { return base.CustomMethod; } - - set { base.CustomMethod = value; } - } - /// /// Enable automatic following of rel links. /// @@ -50,9 +36,9 @@ public override string CustomMethod [Alias("FL")] public SwitchParameter FollowRelLink { - get { return base._followRelLink; } + get => base._followRelLink; - set { base._followRelLink = value; } + set => base._followRelLink = value; } /// @@ -60,12 +46,12 @@ public SwitchParameter FollowRelLink /// [Parameter] [Alias("ML")] - [ValidateRange(1, Int32.MaxValue)] + [ValidateRange(1, int.MaxValue)] public int MaximumFollowRelLink { - get { return base._maximumFollowRelLink; } + get => base._maximumFollowRelLink; - set { base._maximumFollowRelLink = value; } + set => base._maximumFollowRelLink = value; } /// @@ -73,18 +59,137 @@ public int MaximumFollowRelLink /// [Parameter] [Alias("RHV")] - public string ResponseHeadersVariable { get; set; } + public string? ResponseHeadersVariable { get; set; } /// /// Gets or sets the variable name to use for storing the status code from the response. /// [Parameter] - public string StatusCodeVariable { get; set; } + public string? StatusCodeVariable { get; set; } #endregion Parameters + #region Virtual Method Overrides + + /// + /// Process the web response and output corresponding objects. + /// + /// + internal override void ProcessResponse(HttpResponseMessage response) + { + ArgumentNullException.ThrowIfNull(response); + ArgumentNullException.ThrowIfNull(_cancelToken); + + TimeSpan perReadTimeout = ConvertTimeoutSecondsToTimeSpan(OperationTimeoutSeconds); + Stream responseStream = StreamHelper.GetResponseStream(response, _cancelToken.Token); + + if (ShouldWriteToPipeline) + { + responseStream = new BufferingStreamReader(responseStream, perReadTimeout, _cancelToken.Token); + + // First see if it is an RSS / ATOM feed, in which case we can + // stream it - unless the user has overridden it with a return type of "XML" + if (TryProcessFeedStream(responseStream)) + { + // Do nothing, content has been processed. + } + else + { + // Try to get the response encoding from the ContentType header. + string? characterSet = WebResponseHelper.GetCharacterSet(response); + string str = StreamHelper.DecodeStream(responseStream, characterSet, out Encoding encoding, perReadTimeout, _cancelToken.Token); + + string friendlyName = "unknown"; + string encodingWebName = "unknown"; + string encodingPage = encoding.CodePage == -1 ? "unknown" : encoding.CodePage.ToString(); + try + { + // NOTE: These are getter methods that may possibly throw a NotSupportedException exception, + // hence the try/catch + encodingWebName = encoding.WebName; + friendlyName = encoding.EncodingName; + } + catch + { + } + + // 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); + + bool convertSuccess = false; + object? obj = null; + Exception? ex = null; + + if (returnType == RestReturnType.Json) + { + convertSuccess = TryConvertToJson(str, out obj, ref ex) || TryConvertToXml(str, out obj, ref ex); + } + // Default to try xml first since it's more common + else + { + convertSuccess = TryConvertToXml(str, out obj, ref ex) || TryConvertToJson(str, out obj, ref ex); + } + + if (!convertSuccess) + { + // Fallback to string + obj = str; + } + + WriteObject(obj); + } + + responseStream.Position = 0; + } + + if (ShouldSaveToOutFile) + { + string outFilePath = WebResponseHelper.GetOutFilePath(response, _qualifiedOutFile); + + WriteVerbose($"File Name: {Path.GetFileName(outFilePath)}"); + + StreamHelper.SaveStreamToFile(responseStream, outFilePath, this, response.Content.Headers.ContentLength.GetValueOrDefault(), perReadTimeout, _cancelToken.Token); + } + + if (!string.IsNullOrEmpty(StatusCodeVariable)) + { + PSVariableIntrinsics vi = SessionState.PSVariable; + vi.Set(StatusCodeVariable, (int)response.StatusCode); + } + + if (!string.IsNullOrEmpty(ResponseHeadersVariable)) + { + PSVariableIntrinsics vi = SessionState.PSVariable; + vi.Set(ResponseHeadersVariable, WebResponseHelper.GetHeadersDictionary(response)); + } + } + + #endregion Virtual Method Overrides + #region Helper Methods + private static RestReturnType CheckReturnType(HttpResponseMessage response) + { + ArgumentNullException.ThrowIfNull(response); + + RestReturnType rt = RestReturnType.Detect; + string? contentType = ContentHelper.GetContentType(response); + + if (ContentHelper.IsJson(contentType)) + { + rt = RestReturnType.Json; + } + else if (ContentHelper.IsXml(contentType)) + { + rt = RestReturnType.Xml; + } + + return rt; + } + private bool TryProcessFeedStream(Stream responseStream) { bool isRssOrFeed = false; @@ -111,7 +216,8 @@ private bool TryProcessFeedStream(Stream responseStream) if (isRssOrFeed) { XmlDocument workingDocument = new(); - // performing a Read() here to avoid rrechecking + + // Performing a Read() here to avoid rechecking // "rss" or "feed" items reader.Read(); while (!reader.EOF) @@ -122,8 +228,8 @@ private bool TryProcessFeedStream(Stream responseStream) string.Equals("Entry", reader.Name, StringComparison.OrdinalIgnoreCase)) ) { - // this one will do reader.Read() internally - XmlNode result = workingDocument.ReadNode(reader); + // This one will do reader.Read() internally + XmlNode? result = workingDocument.ReadNode(reader); WriteObject(result); } else @@ -133,7 +239,10 @@ private bool TryProcessFeedStream(Stream responseStream) } } } - catch (XmlException) { } + catch (XmlException) + { + // Catch XmlException + } finally { responseStream.Seek(0, SeekOrigin.Begin); @@ -159,14 +268,14 @@ private static XmlReaderSettings GetSecureXmlReaderSettings() return xrs; } - private static bool TryConvertToXml(string xml, out object doc, ref Exception exRef) + private static bool TryConvertToXml(string xml, [NotNullWhen(true)] out object? doc, ref Exception? exRef) { try { XmlReaderSettings settings = GetSecureXmlReaderSettings(); XmlReader xmlReader = XmlReader.Create(new StringReader(xml), settings); - var xmlDoc = new XmlDocument(); + XmlDocument xmlDoc = new(); xmlDoc.PreserveWhitespace = true; xmlDoc.Load(xmlReader); @@ -178,16 +287,15 @@ private static bool TryConvertToXml(string xml, out object doc, ref Exception ex doc = null; } - return (doc != null); + return doc != null; } - private static bool TryConvertToJson(string json, out object obj, ref Exception exRef) + private static bool TryConvertToJson(string json, [NotNullWhen(true)] out object? obj, ref Exception? exRef) { bool converted = false; try { - ErrorRecord error; - obj = JsonObject.ConvertFromJson(json, out error); + obj = JsonObject.ConvertFromJson(json, out ErrorRecord error); if (obj == null) { @@ -206,19 +314,14 @@ private static bool TryConvertToJson(string json, out object obj, ref Exception converted = true; } } - catch (ArgumentException ex) - { - exRef = ex; - obj = null; - } - catch (InvalidOperationException ex) + catch (Exception ex) when (ex is ArgumentException || ex is InvalidOperationException) { exRef = ex; obj = null; } catch (JsonException ex) { - var msg = string.Format(System.Globalization.CultureInfo.CurrentCulture, WebCmdletStrings.JsonDeserializationFailed, ex.Message); + string msg = string.Format(System.Globalization.CultureInfo.CurrentCulture, WebCmdletStrings.JsonDeserializationFailed, ex.Message); exRef = new ArgumentException(msg, ex); obj = null; } @@ -226,7 +329,7 @@ private static bool TryConvertToJson(string json, out object obj, ref Exception return converted; } - #endregion + #endregion Helper Methods /// /// Enum for rest return type. @@ -242,7 +345,7 @@ public enum RestReturnType /// /// Json return type. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] Json, /// @@ -253,50 +356,42 @@ public enum RestReturnType internal class BufferingStreamReader : Stream { - internal BufferingStreamReader(Stream baseStream) + internal BufferingStreamReader(Stream baseStream, TimeSpan perReadTimeout, CancellationToken cancellationToken) { _baseStream = baseStream; _streamBuffer = new MemoryStream(); _length = long.MaxValue; _copyBuffer = new byte[4096]; + _perReadTimeout = perReadTimeout; + _cancellationToken = cancellationToken; } private readonly Stream _baseStream; private readonly MemoryStream _streamBuffer; private readonly byte[] _copyBuffer; + private readonly TimeSpan _perReadTimeout; + private readonly CancellationToken _cancellationToken; - public override bool CanRead - { - get { return true; } - } + public override bool CanRead => true; - public override bool CanSeek - { - get { return true; } - } + public override bool CanSeek => true; - public override bool CanWrite - { - get { return false; } - } + public override bool CanWrite => false; public override void Flush() { _streamBuffer.SetLength(0); } - public override long Length - { - get { return _length; } - } + public override long Length => _length; private long _length; public override long Position { - get { return _streamBuffer.Position; } + get => _streamBuffer.Position; - set { _streamBuffer.Position = value; } + set => _streamBuffer.Position = value; } public override int Read(byte[] buffer, int offset, int count) @@ -304,13 +399,12 @@ public override int Read(byte[] buffer, int offset, int count) long previousPosition = Position; bool consumedStream = false; int totalCount = count; - while ((!consumedStream) && - ((Position + totalCount) > _streamBuffer.Length)) + while (!consumedStream && (Position + totalCount) > _streamBuffer.Length) { // If we don't have enough data to fill this from memory, cache more. // We try to read 4096 bytes from base stream every time, so at most we // may cache 4095 bytes more than what is required by the Read operation. - int bytesRead = _baseStream.Read(_copyBuffer, 0, _copyBuffer.Length); + int bytesRead = _baseStream.ReadAsync(_copyBuffer.AsMemory(), _perReadTimeout, _cancellationToken).GetAwaiter().GetResult(); if (_streamBuffer.Position < _streamBuffer.Length) { @@ -359,147 +453,4 @@ public override void Write(byte[] buffer, int offset, int count) } } } - - // TODO: Merge Partials - - /// - /// The Invoke-RestMethod command - /// This command makes an HTTP or HTTPS request to a web service, - /// and returns the response in an appropriate way. - /// Intended to work against the wide spectrum of "RESTful" web services - /// currently deployed across the web. - /// - [Cmdlet(VerbsLifecycle.Invoke, "RestMethod", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096706", DefaultParameterSetName = "StandardMethod")] - public partial class InvokeRestMethodCommand : WebRequestPSCmdlet - { - #region Virtual Method Overrides - - /// - /// Process the web response and output corresponding objects. - /// - /// - internal override void ProcessResponse(HttpResponseMessage response) - { - if (response == null) { throw new ArgumentNullException(nameof(response)); } - - var baseResponseStream = StreamHelper.GetResponseStream(response); - - if (ShouldWriteToPipeline) - { - using var responseStream = new BufferingStreamReader(baseResponseStream); - - // First see if it is an RSS / ATOM feed, in which case we can - // stream it - unless the user has overridden it with a return type of "XML" - if (TryProcessFeedStream(responseStream)) - { - // Do nothing, content has been processed. - } - else - { - // determine the response type - RestReturnType returnType = CheckReturnType(response); - - // Try to get the response encoding from the ContentType header. - Encoding encoding = null; - string charSet = response.Content.Headers.ContentType?.CharSet; - if (!string.IsNullOrEmpty(charSet)) - { - // NOTE: Don't use ContentHelper.GetEncoding; it returns a - // default which bypasses checking for a meta charset value. - StreamHelper.TryGetEncoding(charSet, out encoding); - } - - if (string.IsNullOrEmpty(charSet) && returnType == RestReturnType.Json) - { - encoding = Encoding.UTF8; - } - - object obj = null; - Exception ex = null; - - string str = StreamHelper.DecodeStream(responseStream, ref encoding); - - string encodingVerboseName; - try - { - encodingVerboseName = string.IsNullOrEmpty(encoding.HeaderName) ? encoding.EncodingName : encoding.HeaderName; - } - catch (NotSupportedException) - { - encodingVerboseName = encoding.EncodingName; - } - // NOTE: Tests use this verbose output to verify the encoding. - WriteVerbose(string.Format - ( - System.Globalization.CultureInfo.InvariantCulture, - "Content encoding: {0}", - encodingVerboseName) - ); - bool convertSuccess = false; - - if (returnType == RestReturnType.Json) - { - convertSuccess = TryConvertToJson(str, out obj, ref ex) || TryConvertToXml(str, out obj, ref ex); - } - // default to try xml first since it's more common - else - { - convertSuccess = TryConvertToXml(str, out obj, ref ex) || TryConvertToJson(str, out obj, ref ex); - } - - if (!convertSuccess) - { - // fallback to string - obj = str; - } - - WriteObject(obj); - } - } - else if (ShouldSaveToOutFile) - { - StreamHelper.SaveStreamToFile(baseResponseStream, QualifiedOutFile, this, _cancelToken.Token); - } - - if (!string.IsNullOrEmpty(StatusCodeVariable)) - { - PSVariableIntrinsics vi = SessionState.PSVariable; - vi.Set(StatusCodeVariable, (int)response.StatusCode); - } - - if (!string.IsNullOrEmpty(ResponseHeadersVariable)) - { - PSVariableIntrinsics vi = SessionState.PSVariable; - vi.Set(ResponseHeadersVariable, WebResponseHelper.GetHeadersDictionary(response)); - } - } - - #endregion Virtual Method Overrides - - #region Helper Methods - - private static RestReturnType CheckReturnType(HttpResponseMessage response) - { - if (response == null) { throw new ArgumentNullException(nameof(response)); } - - RestReturnType rt = RestReturnType.Detect; - string contentType = ContentHelper.GetContentType(response); - if (string.IsNullOrEmpty(contentType)) - { - rt = RestReturnType.Detect; - } - else if (ContentHelper.IsJson(contentType)) - { - rt = RestReturnType.Json; - } - else if (ContentHelper.IsXml(contentType)) - { - rt = RestReturnType.Xml; - } - - return (rt); - } - - #endregion Helper Methods - } } 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 3423b912fe7..3b8ff2e3372 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 @@ -11,11 +11,14 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Net.Sockets; using System.Security; using System.Security.Authentication; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -60,17 +63,17 @@ public enum WebSslProtocol /// /// No SSL protocol will be set and the system defaults will be used. /// - Default = 0, + Default = SslProtocols.None, /// - /// Specifies the TLS 1.0 security protocol. The TLS protocol is defined in IETF RFC 2246. + /// Specifies the TLS 1.0 is obsolete. Using this value now defaults to TLS 1.2. /// - Tls = SslProtocols.Tls, + Tls = SslProtocols.Tls12, /// - /// Specifies the TLS 1.1 security protocol. The TLS protocol is defined in IETF RFC 4346. + /// Specifies the TLS 1.1 is obsolete. Using this value now defaults to TLS 1.2. /// - Tls11 = SslProtocols.Tls11, + Tls11 = SslProtocols.Tls12, /// /// Specifies the TLS 1.2 security protocol. The TLS protocol is defined in IETF RFC 5246. @@ -86,8 +89,62 @@ public enum WebSslProtocol /// /// Base class for Invoke-RestMethod and Invoke-WebRequest commands. /// - public abstract partial class WebRequestPSCmdlet : PSCmdlet + public abstract class WebRequestPSCmdlet : PSCmdlet, IDisposable { + #region Fields + + /// + /// Used to prefix the headers in debug and verbose messaging. + /// + internal const string DebugHeaderPrefix = "--- "; + + /// + /// Cancellation token source. + /// + internal CancellationTokenSource _cancelToken = null; + + /// + /// Automatically follow Rel Links. + /// + internal bool _followRelLink = false; + + /// + /// Maximum number of Rel Links to follow. + /// + internal int _maximumFollowRelLink = int.MaxValue; + + /// + /// Maximum number of Redirects to follow. + /// + internal int _maximumRedirection; + + /// + /// Parse Rel Links. + /// + internal bool _parseRelLink = false; + + /// + /// Automatically follow Rel Links. + /// + internal Dictionary _relationLink = null; + + /// + /// The current size of the local file being resumed. + /// + private long _resumeFileSize = 0; + + /// + /// The remote endpoint returned a 206 status code indicating successful resume. + /// + private bool _resumeSuccess = false; + + /// + /// True if the Dispose() method has already been called to cleanup Disposable fields. + /// + private bool _disposed = false; + + #endregion Fields + #region Virtual Properties #region URI @@ -105,7 +162,19 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet [ValidateNotNullOrEmpty] public virtual Uri Uri { get; set; } - #endregion + #endregion URI + + #region HTTP Version + + /// + /// Gets or sets the HTTP Version property. + /// + [Parameter] + [ArgumentToVersionTransformation] + [HttpVersionCompletions] + public virtual Version HttpVersion { get; set; } + + #endregion HTTP Version #region Session /// @@ -121,7 +190,7 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet [Alias("SV")] public virtual string SessionVariable { get; set; } - #endregion + #endregion Session #region Authorization and Credentials @@ -132,7 +201,7 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet public virtual SwitchParameter AllowUnencryptedAuthentication { get; set; } /// - /// Gets or sets the Authentication property used to determin the Authentication method for the web session. + /// Gets or sets the Authentication property used to determine the Authentication method for the web session. /// Authentication does not work with UseDefaultCredentials. /// Authentication over unencrypted sessions requires AllowUnencryptedAuthentication. /// Basic: Requires Credential. @@ -186,7 +255,7 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet [Parameter] public virtual SecureString Token { get; set; } - #endregion + #endregion Authorization and Credentials #region Headers @@ -203,11 +272,25 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet public virtual SwitchParameter DisableKeepAlive { get; set; } /// - /// Gets or sets the TimeOut property. + /// Gets or sets the ConnectionTimeoutSeconds property. + /// + /// + /// This property applies to sending the request and receiving the response headers only. + /// + [Alias("TimeoutSec")] + [Parameter] + [ValidateRange(0, int.MaxValue)] + public virtual int ConnectionTimeoutSeconds { get; set; } + + /// + /// Gets or sets the OperationTimeoutSeconds property. /// + /// + /// This property applies to each read operation when receiving the response body. + /// [Parameter] - [ValidateRange(0, Int32.MaxValue)] - public virtual int TimeoutSec { get; set; } + [ValidateRange(0, int.MaxValue)] + public virtual int OperationTimeoutSeconds { get; set; } /// /// Gets or sets the Headers property. @@ -216,39 +299,62 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet [Parameter] public virtual IDictionary Headers { get; set; } - #endregion + /// + /// Gets or sets the SkipHeaderValidation property. + /// + /// + /// This property adds headers to the request's header collection without validation. + /// + [Parameter] + public virtual SwitchParameter SkipHeaderValidation { get; set; } + + #endregion Headers #region Redirect /// - /// Gets or sets the RedirectMax property. + /// Gets or sets the AllowInsecureRedirect property used to follow HTTP redirects from HTTPS. /// [Parameter] - [ValidateRange(0, Int32.MaxValue)] - public virtual int MaximumRedirection - { - get { return _maximumRedirection; } - - set { _maximumRedirection = value; } - } + public virtual SwitchParameter AllowInsecureRedirect { get; set; } - private int _maximumRedirection = -1; + /// + /// Gets or sets the RedirectMax property. + /// + [Parameter] + [ValidateRange(0, int.MaxValue)] + public virtual int MaximumRedirection { get; set; } = -1; /// /// Gets or sets the MaximumRetryCount property, which determines the number of retries of a failed web request. /// [Parameter] - [ValidateRange(0, Int32.MaxValue)] + [ValidateRange(0, int.MaxValue)] public virtual int MaximumRetryCount { get; set; } + /// + /// Gets or sets the PreserveAuthorizationOnRedirect property. + /// + /// + /// This property overrides compatibility with web requests on Windows. + /// On FullCLR (WebRequest), authorization headers are stripped during redirect. + /// CoreCLR (HTTPClient) does not have this behavior so web requests that work on + /// PowerShell/FullCLR can fail with PowerShell/CoreCLR. To provide compatibility, + /// we'll detect requests with an Authorization header and automatically strip + /// the header when the first redirect occurs. This switch turns off this logic for + /// edge cases where the authorization header needs to be preserved across redirects. + /// + [Parameter] + public virtual SwitchParameter PreserveAuthorizationOnRedirect { get; set; } + /// /// Gets or sets the RetryIntervalSec property, which determines the number seconds between retries. /// [Parameter] - [ValidateRange(1, Int32.MaxValue)] + [ValidateRange(1, int.MaxValue)] public virtual int RetryIntervalSec { get; set; } = 5; - #endregion + #endregion Redirect #region Method @@ -257,14 +363,7 @@ public virtual int MaximumRedirection /// [Parameter(ParameterSetName = "StandardMethod")] [Parameter(ParameterSetName = "StandardMethodNoProxy")] - public virtual WebRequestMethod Method - { - get { return _method; } - - set { _method = value; } - } - - private WebRequestMethod _method = WebRequestMethod.Default; + public virtual WebRequestMethod Method { get; set; } = WebRequestMethod.Default; /// /// Gets or sets the CustomMethod property. @@ -273,16 +372,24 @@ public virtual WebRequestMethod Method [Parameter(Mandatory = true, ParameterSetName = "CustomMethodNoProxy")] [Alias("CM")] [ValidateNotNullOrEmpty] - public virtual string CustomMethod - { - get { return _customMethod; } - - set { _customMethod = value; } - } + public virtual string CustomMethod { get => _customMethod; set => _customMethod = value.ToUpperInvariant(); } private string _customMethod; - #endregion + /// + /// Gets or sets the PreserveHttpMethodOnRedirect property. + /// + [Parameter] + public virtual SwitchParameter PreserveHttpMethodOnRedirect { get; set; } + + /// + /// Gets or sets the UnixSocket property. + /// + [Parameter] + [ValidateNotNullOrEmpty] + public virtual UnixDomainSocketEndPoint UnixSocket { get; set; } + + #endregion Method #region NoProxy @@ -293,7 +400,7 @@ public virtual string CustomMethod [Parameter(Mandatory = true, ParameterSetName = "StandardMethodNoProxy")] public virtual SwitchParameter NoProxy { get; set; } - #endregion + #endregion NoProxy #region Proxy @@ -319,7 +426,7 @@ public virtual string CustomMethod [Parameter(ParameterSetName = "CustomMethod")] public virtual SwitchParameter ProxyUseDefaultCredentials { get; set; } - #endregion + #endregion Proxy #region Input @@ -354,6 +461,7 @@ public virtual string CustomMethod /// Gets or sets the InFile property. /// [Parameter] + [ValidateNotNullOrEmpty] public virtual string InFile { get; set; } /// @@ -361,7 +469,7 @@ public virtual string CustomMethod /// private string _originalFilePath; - #endregion + #endregion Input #region Output @@ -369,6 +477,7 @@ public virtual string CustomMethod /// Gets or sets the OutFile property. /// [Parameter] + [ValidateNotNullOrEmpty] public virtual string OutFile { get; set; } /// @@ -389,142 +498,366 @@ public virtual string CustomMethod [Parameter] public virtual SwitchParameter SkipHttpErrorCheck { get; set; } - #endregion + #endregion Output #endregion Virtual Properties - #region Virtual Methods + #region Helper Properties - internal virtual void ValidateParameters() + internal string QualifiedOutFile => QualifyFilePath(OutFile); + + internal string _qualifiedOutFile; + + internal bool ShouldCheckHttpStatus => !SkipHttpErrorCheck; + + /// + /// Determines whether writing to a file should Resume and append rather than overwrite. + /// + internal bool ShouldResume => Resume.IsPresent && _resumeSuccess; + + internal bool ShouldSaveToOutFile => !string.IsNullOrEmpty(OutFile); + + internal bool ShouldWriteToPipeline => !ShouldSaveToOutFile || PassThru; + + #endregion Helper Properties + + #region Abstract Methods + + /// + /// Read the supplied WebResponse object and push the resulting output into the pipeline. + /// + /// Instance of a WebResponse object to be processed. + internal abstract void ProcessResponse(HttpResponseMessage response); + + #endregion Abstract Methods + + #region Overrides + + /// + /// The main execution method for cmdlets derived from WebRequestPSCmdlet. + /// + protected override void ProcessRecord() { - // sessions - if ((WebSession != null) && (SessionVariable != null)) + try { - ErrorRecord error = GetValidationError(WebCmdletStrings.SessionConflict, - "WebCmdletSessionConflictException"); - ThrowTerminatingError(error); + // Set cmdlet context for write progress + ValidateParameters(); + PrepareSession(); + + // If the request contains an authorization header and PreserveAuthorizationOnRedirect is not set, + // it needs to be stripped on the first redirect. + bool keepAuthorizationOnRedirect = PreserveAuthorizationOnRedirect.IsPresent + && WebSession.Headers.ContainsKey(HttpKnownHeaderNames.Authorization); + + bool handleRedirect = keepAuthorizationOnRedirect || AllowInsecureRedirect || PreserveHttpMethodOnRedirect; + + HttpClient client = GetHttpClient(handleRedirect); + + int followedRelLink = 0; + Uri uri = Uri; + do + { + if (followedRelLink > 0) + { + string linkVerboseMsg = string.Format( + CultureInfo.CurrentCulture, + WebCmdletStrings.FollowingRelLinkVerboseMsg, + uri.AbsoluteUri); + + WriteVerbose(linkVerboseMsg); + } + + using (HttpRequestMessage request = GetRequest(uri)) + { + FillRequestStream(request); + try + { + _maximumRedirection = WebSession.MaximumRedirection; + + using HttpResponseMessage response = GetResponse(client, request, handleRedirect); + + bool _isSuccess = response.IsSuccessStatusCode; + + // Check if the Resume range was not satisfiable because the file already completed downloading. + // This happens when the local file is the same size as the remote file. + if (Resume.IsPresent + && response.StatusCode == HttpStatusCode.RequestedRangeNotSatisfiable + && response.Content.Headers.ContentRange.HasLength + && response.Content.Headers.ContentRange.Length == _resumeFileSize) + { + _isSuccess = true; + WriteVerbose(string.Format( + CultureInfo.CurrentCulture, + WebCmdletStrings.OutFileWritingSkipped, + OutFile)); + + // Disable writing to the OutFile. + OutFile = null; + } + + // Detect insecure redirection. + if (!AllowInsecureRedirect) + { + // We will skip detection if either of the URIs is relative, because the 'Scheme' property is not supported on a relative URI. + // If we have to skip the check, an error may be thrown later if it's actually an insecure https-to-http redirect. + bool originIsHttps = response.RequestMessage.RequestUri.IsAbsoluteUri && response.RequestMessage.RequestUri.Scheme == "https"; + bool destinationIsHttp = response.Headers.Location is not null && response.Headers.Location.IsAbsoluteUri && response.Headers.Location.Scheme == "http"; + + if (originIsHttps && destinationIsHttp) + { + ErrorRecord er = new(new InvalidOperationException(), "InsecureRedirection", ErrorCategory.InvalidOperation, request); + er.ErrorDetails = new ErrorDetails(WebCmdletStrings.InsecureRedirection); + ThrowTerminatingError(er); + } + } + + if (ShouldCheckHttpStatus && !_isSuccess) + { + string message = string.Format( + CultureInfo.CurrentCulture, + WebCmdletStrings.ResponseStatusCodeFailure, + (int)response.StatusCode, + response.ReasonPhrase); + + HttpResponseException httpEx = new(message, response); + ErrorRecord er = new(httpEx, "WebCmdletWebResponseException", ErrorCategory.InvalidOperation, request); + 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); + var responseStream = StreamHelper.GetResponseStream(response, _cancelToken.Token); + int initialCapacity = (int)Math.Min(contentLength ?? StreamHelper.DefaultReadBuffer, StreamHelper.DefaultReadBuffer); + var bufferedStream = new WebResponseContentMemoryStream(responseStream, initialCapacity, this, contentLength, perReadTimeout, _cancelToken.Token); + string error = StreamHelper.DecodeStream(bufferedStream, characterSet, out Encoding encoding, perReadTimeout, _cancelToken.Token); + detailMsg = FormatErrorMessage(error, contentType); + } + catch (Exception ex) + { + // Catch all + er.ErrorDetails = new ErrorDetails(ex.ToString()); + } + + if (!string.IsNullOrEmpty(detailMsg)) + { + er.ErrorDetails = new ErrorDetails(detailMsg); + } + + ThrowTerminatingError(er); + } + + if (_parseRelLink || _followRelLink) + { + ParseLinkHeader(response); + } + + ProcessResponse(response); + UpdateSession(response); + + // If we hit our maximum redirection count, generate an error. + // Errors with redirection counts of greater than 0 are handled automatically by .NET, but are + // impossible to detect programmatically when we hit this limit. By handling this ourselves + // (and still writing out the result), users can debug actual HTTP redirect problems. + if (_maximumRedirection == 0 && IsRedirectCode(response.StatusCode)) + { + ErrorRecord er = new(new InvalidOperationException(), "MaximumRedirectExceeded", ErrorCategory.InvalidOperation, request); + er.ErrorDetails = new ErrorDetails(WebCmdletStrings.MaximumRedirectionCountExceeded); + WriteError(er); + } + } + catch (TimeoutException ex) + { + ErrorRecord er = new(ex, "OperationTimeoutReached", ErrorCategory.OperationTimeout, null); + ThrowTerminatingError(er); + } + catch (HttpRequestException ex) + { + ErrorRecord er = new(ex, "WebCmdletWebResponseException", ErrorCategory.InvalidOperation, request); + if (ex.InnerException is not null) + { + er.ErrorDetails = new ErrorDetails(ex.InnerException.Message); + } + + ThrowTerminatingError(er); + } + finally + { + _cancelToken?.Dispose(); + _cancelToken = null; + } + + if (_followRelLink) + { + if (!_relationLink.ContainsKey("next")) + { + return; + } + + uri = new Uri(_relationLink["next"]); + followedRelLink++; + } + } + } + while (_followRelLink && (followedRelLink < _maximumFollowRelLink)); } + catch (CryptographicException ex) + { + ErrorRecord er = new(ex, "WebCmdletCertificateException", ErrorCategory.SecurityError, null); + ThrowTerminatingError(er); + } + catch (NotSupportedException ex) + { + ErrorRecord er = new(ex, "WebCmdletIEDomNotSupportedException", ErrorCategory.NotImplemented, null); + ThrowTerminatingError(er); + } + } - // Authentication - if (UseDefaultCredentials && (Authentication != WebAuthenticationType.None)) + /// + /// To implement ^C. + /// + protected override void StopProcessing() => _cancelToken?.Cancel(); + + /// + /// Disposes the associated WebSession if it is not being used as part of a persistent session. + /// + /// True when called from Dispose() and false when called from finalizer. + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing && !IsPersistentSession()) + { + WebSession?.Dispose(); + WebSession = null; + } + + _disposed = true; + } + } + + /// + /// Disposes the associated WebSession if it is not being used as part of a persistent session. + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + #endregion Overrides + + #region Virtual Methods + + internal virtual void ValidateParameters() + { + // Sessions + if (WebSession is not null && SessionVariable is not null) { - ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationConflict, - "WebCmdletAuthenticationConflictException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.SessionConflict, "WebCmdletSessionConflictException"); ThrowTerminatingError(error); } - if ((Authentication != WebAuthenticationType.None) && (Token != null) && (Credential != null)) + // Authentication + if (UseDefaultCredentials && Authentication != WebAuthenticationType.None) { - ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationTokenConflict, - "WebCmdletAuthenticationTokenConflictException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationConflict, "WebCmdletAuthenticationConflictException"); ThrowTerminatingError(error); } - if ((Authentication == WebAuthenticationType.Basic) && (Credential == null)) + if (Authentication != WebAuthenticationType.None && Token is not null && Credential is not null) { - ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationCredentialNotSupplied, - "WebCmdletAuthenticationCredentialNotSuppliedException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationTokenConflict, "WebCmdletAuthenticationTokenConflictException"); ThrowTerminatingError(error); } - if ((Authentication == WebAuthenticationType.OAuth || Authentication == WebAuthenticationType.Bearer) && (Token == null)) + if (Authentication == WebAuthenticationType.Basic && Credential is null) { - ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationTokenNotSupplied, - "WebCmdletAuthenticationTokenNotSuppliedException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationCredentialNotSupplied, "WebCmdletAuthenticationCredentialNotSuppliedException"); ThrowTerminatingError(error); } - if (!AllowUnencryptedAuthentication && (Authentication != WebAuthenticationType.None) && (Uri.Scheme != "https")) + if ((Authentication == WebAuthenticationType.OAuth || Authentication == WebAuthenticationType.Bearer) && Token is null) { - ErrorRecord error = GetValidationError(WebCmdletStrings.AllowUnencryptedAuthenticationRequired, - "WebCmdletAllowUnencryptedAuthenticationRequiredException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationTokenNotSupplied, "WebCmdletAuthenticationTokenNotSuppliedException"); ThrowTerminatingError(error); } - if (!AllowUnencryptedAuthentication && (Credential != null || UseDefaultCredentials) && (Uri.Scheme != "https")) + if (!AllowUnencryptedAuthentication && (Authentication != WebAuthenticationType.None || Credential is not null || UseDefaultCredentials) && Uri.Scheme != "https") { - ErrorRecord error = GetValidationError(WebCmdletStrings.AllowUnencryptedAuthenticationRequired, - "WebCmdletAllowUnencryptedAuthenticationRequiredException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.AllowUnencryptedAuthenticationRequired, "WebCmdletAllowUnencryptedAuthenticationRequiredException"); ThrowTerminatingError(error); } - // credentials - if (UseDefaultCredentials && (Credential != null)) + // Credentials + if (UseDefaultCredentials && Credential is not null) { - ErrorRecord error = GetValidationError(WebCmdletStrings.CredentialConflict, - "WebCmdletCredentialConflictException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.CredentialConflict, "WebCmdletCredentialConflictException"); ThrowTerminatingError(error); } // Proxy server - if (ProxyUseDefaultCredentials && (ProxyCredential != null)) + if (ProxyUseDefaultCredentials && ProxyCredential is not null) { - ErrorRecord error = GetValidationError(WebCmdletStrings.ProxyCredentialConflict, - "WebCmdletProxyCredentialConflictException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.ProxyCredentialConflict, "WebCmdletProxyCredentialConflictException"); ThrowTerminatingError(error); } - else if ((Proxy == null) && ((ProxyCredential != null) || ProxyUseDefaultCredentials)) + else if (Proxy is null && (ProxyCredential is not null || ProxyUseDefaultCredentials)) { - ErrorRecord error = GetValidationError(WebCmdletStrings.ProxyUriNotSupplied, - "WebCmdletProxyUriNotSuppliedException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.ProxyUriNotSupplied, "WebCmdletProxyUriNotSuppliedException"); ThrowTerminatingError(error); } - // request body content - if ((Body != null) && (InFile != null)) + // Request body content + if (Body is not null && InFile is not null) { - ErrorRecord error = GetValidationError(WebCmdletStrings.BodyConflict, - "WebCmdletBodyConflictException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.BodyConflict, "WebCmdletBodyConflictException"); ThrowTerminatingError(error); } - if ((Body != null) && (Form != null)) + if (Body is not null && Form is not null) { - ErrorRecord error = GetValidationError(WebCmdletStrings.BodyFormConflict, - "WebCmdletBodyFormConflictException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.BodyFormConflict, "WebCmdletBodyFormConflictException"); ThrowTerminatingError(error); } - if ((InFile != null) && (Form != null)) + if (InFile is not null && Form is not null) { - ErrorRecord error = GetValidationError(WebCmdletStrings.FormInFileConflict, - "WebCmdletFormInFileConflictException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.FormInFileConflict, "WebCmdletFormInFileConflictException"); ThrowTerminatingError(error); } - // validate InFile path - if (InFile != null) + // Validate InFile path + if (InFile is not null) { - ProviderInfo provider = null; ErrorRecord errorRecord = null; try { - Collection providerPaths = GetResolvedProviderPathFromPSPath(InFile, out provider); + Collection providerPaths = GetResolvedProviderPathFromPSPath(InFile, out ProviderInfo provider); if (!provider.Name.Equals(FileSystemProvider.ProviderName, StringComparison.OrdinalIgnoreCase)) { - errorRecord = GetValidationError(WebCmdletStrings.NotFilesystemPath, - "WebCmdletInFileNotFilesystemPathException", InFile); + errorRecord = GetValidationError(WebCmdletStrings.NotFilesystemPath, "WebCmdletInFileNotFilesystemPathException", InFile); } else { if (providerPaths.Count > 1) { - errorRecord = GetValidationError(WebCmdletStrings.MultiplePathsResolved, - "WebCmdletInFileMultiplePathsResolvedException", InFile); + errorRecord = GetValidationError(WebCmdletStrings.MultiplePathsResolved, "WebCmdletInFileMultiplePathsResolvedException", InFile); } else if (providerPaths.Count == 0) { - errorRecord = GetValidationError(WebCmdletStrings.NoPathResolved, - "WebCmdletInFileNoPathResolvedException", InFile); + errorRecord = GetValidationError(WebCmdletStrings.NoPathResolved, "WebCmdletInFileNoPathResolvedException", InFile); } else { if (Directory.Exists(providerPaths[0])) { - errorRecord = GetValidationError(WebCmdletStrings.DirectoryPathSpecified, - "WebCmdletInFileNotFilePathException", InFile); + errorRecord = GetValidationError(WebCmdletStrings.DirectoryPathSpecified, "WebCmdletInFileNotFilePathException", InFile); } _originalFilePath = InFile; @@ -545,57 +878,59 @@ internal virtual void ValidateParameters() errorRecord = new ErrorRecord(driveNotFound.ErrorRecord, driveNotFound); } - if (errorRecord != null) + if (errorRecord is not null) { ThrowTerminatingError(errorRecord); } } - // output ?? - if (PassThru && (OutFile == null)) + // Output ?? + if (PassThru.IsPresent && OutFile is null) { - ErrorRecord error = GetValidationError(WebCmdletStrings.OutFileMissing, - "WebCmdletOutFileMissingException", nameof(PassThru)); + ErrorRecord error = GetValidationError(WebCmdletStrings.OutFileMissing, "WebCmdletOutFileMissingException", nameof(PassThru)); ThrowTerminatingError(error); } // Resume requires OutFile. - if (Resume.IsPresent && OutFile == null) + if (Resume.IsPresent && OutFile is null) + { + ErrorRecord error = GetValidationError(WebCmdletStrings.OutFileMissing, "WebCmdletOutFileMissingException", nameof(Resume)); + ThrowTerminatingError(error); + } + + _qualifiedOutFile = ShouldSaveToOutFile ? QualifiedOutFile : null; + + // OutFile must not be a directory to use Resume. + if (Resume.IsPresent && Directory.Exists(_qualifiedOutFile)) { - ErrorRecord error = GetValidationError(WebCmdletStrings.OutFileMissing, - "WebCmdletOutFileMissingException", nameof(Resume)); + ErrorRecord error = GetValidationError(WebCmdletStrings.ResumeNotFilePath, "WebCmdletResumeNotFilePathException", _qualifiedOutFile); ThrowTerminatingError(error); } } internal virtual void PrepareSession() { - // make sure we have a valid WebRequestSession object to work with - if (WebSession == null) - { - WebSession = new WebRequestSession(); - } + // Make sure we have a valid WebRequestSession object to work with + WebSession ??= new WebRequestSession(); - if (SessionVariable != null) + if (SessionVariable is not null) { - // save the session back to the PS environment if requested + // Save the session back to the PS environment if requested PSVariableIntrinsics vi = SessionState.PSVariable; vi.Set(SessionVariable, WebSession); } - // - // handle credentials - // - if (Credential != null && Authentication == WebAuthenticationType.None) + // Handle credentials + if (Credential is not null && Authentication == WebAuthenticationType.None) { - // get the relevant NetworkCredential + // Get the relevant NetworkCredential NetworkCredential netCred = Credential.GetNetworkCredential(); WebSession.Credentials = netCred; - // supplying a credential overrides the UseDefaultCredentials setting + // Supplying a credential overrides the UseDefaultCredentials setting WebSession.UseDefaultCredentials = false; } - else if ((Credential != null || Token != null) && Authentication != WebAuthenticationType.None) + else if ((Credential is not null || Token is not null) && Authentication != WebAuthenticationType.None) { ProcessAuthentication(); } @@ -604,16 +939,15 @@ internal virtual void PrepareSession() WebSession.UseDefaultCredentials = true; } - if (CertificateThumbprint != null) + if (CertificateThumbprint is not null) { - X509Store store = new(StoreName.My, StoreLocation.CurrentUser); + using X509Store store = new(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates; X509Certificate2Collection tbCollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByThumbprint, CertificateThumbprint, false); if (tbCollection.Count == 0) { - CryptographicException ex = new(WebCmdletStrings.ThumbprintNotFound); - throw ex; + throw new CryptographicException(WebCmdletStrings.ThumbprintNotFound); } foreach (X509Certificate2 tbCert in tbCollection) @@ -623,36 +957,53 @@ internal virtual void PrepareSession() } } - if (Certificate != null) + if (Certificate is not null) { WebSession.AddCertificate(Certificate); } - // - // handle the user agent - // - if (UserAgent != null) + // Handle the user agent + if (UserAgent is not null) { - // store the UserAgent string + // Store the UserAgent string WebSession.UserAgent = UserAgent; } - if (Proxy != null) + // Proxy and NoProxy parameters are mutually exclusive. + // If NoProxy is provided, WebSession will turn off the proxy + // and if Proxy is provided NoProxy will be turned off. + if (NoProxy.IsPresent) { - WebProxy webProxy = new(Proxy); - webProxy.BypassProxyOnLocal = false; - if (ProxyCredential != null) + WebSession.NoProxy = true; + } + else + { + if (Proxy is not null) { - webProxy.Credentials = ProxyCredential.GetNetworkCredential(); - } - else if (ProxyUseDefaultCredentials) - { - // If both ProxyCredential and ProxyUseDefaultCredentials are passed, - // UseDefaultCredentials will overwrite the supplied credentials. - webProxy.UseDefaultCredentials = true; + WebProxy webProxy = new(Proxy); + webProxy.BypassProxyOnLocal = false; + if (ProxyCredential is not null) + { + webProxy.Credentials = ProxyCredential.GetNetworkCredential(); + } + else + { + webProxy.UseDefaultCredentials = ProxyUseDefaultCredentials; + } + + // We don't want to update the WebSession unless the proxies are different + // as that will require us to create a new HttpClientHandler and lose connection + // persistence. + if (!webProxy.Equals(WebSession.Proxy)) + { + WebSession.Proxy = webProxy; + } } + } - WebSession.Proxy = webProxy; + if (MyInvocation.BoundParameters.ContainsKey(nameof(SslProtocol))) + { + WebSession.SslProtocol = SslProtocol; } if (MaximumRedirection > -1) @@ -660,18 +1011,22 @@ internal virtual void PrepareSession() WebSession.MaximumRedirection = MaximumRedirection; } - // store the other supplied headers - if (Headers != null) + WebSession.UnixSocket = UnixSocket; + + WebSession.SkipCertificateCheck = SkipCertificateCheck.IsPresent; + + // Store the other supplied headers + if (Headers is not null) { foreach (string key in Headers.Keys) { - var value = Headers[key]; + object value = Headers[key]; // null is not valid value for header. // We silently ignore header if value is null. if (value is not null) { - // add the header value (or overwrite it if already present) + // Add the header value (or overwrite it if already present). WebSession.Headers[key] = value.ToString(); } } @@ -681,407 +1036,39 @@ internal virtual void PrepareSession() { WebSession.MaximumRetryCount = MaximumRetryCount; - // only set retry interval if retry count is set. + // Only set retry interval if retry count is set. WebSession.RetryIntervalInSeconds = RetryIntervalSec; } - } - - #endregion Virtual Methods - - #region Helper Properties - - internal string QualifiedOutFile - { - get { return (QualifyFilePath(OutFile)); } - } - - internal bool ShouldSaveToOutFile - { - get { return (!string.IsNullOrEmpty(OutFile)); } - } - - internal bool ShouldWriteToPipeline - { - get { return (!ShouldSaveToOutFile || PassThru); } - } - - internal bool ShouldCheckHttpStatus - { - get { return !SkipHttpErrorCheck; } - } - - /// - /// Determines whether writing to a file should Resume and append rather than overwrite. - /// - internal bool ShouldResume - { - get { return (Resume.IsPresent && _resumeSuccess); } - } - - #endregion Helper Properties - - #region Helper Methods - private Uri PrepareUri(Uri uri) - { - uri = CheckProtocol(uri); - - // before creating the web request, - // preprocess Body if content is a dictionary and method is GET (set as query) - IDictionary bodyAsDictionary; - LanguagePrimitives.TryConvertTo(Body, out bodyAsDictionary); - if ((bodyAsDictionary != null) - && ((IsStandardMethodSet() && (Method == WebRequestMethod.Default || Method == WebRequestMethod.Get)) - || (IsCustomMethodSet() && CustomMethod.ToUpperInvariant() == "GET"))) - { - UriBuilder uriBuilder = new(uri); - if (uriBuilder.Query != null && uriBuilder.Query.Length > 1) - { - uriBuilder.Query = string.Concat(uriBuilder.Query.AsSpan(1), "&", FormatDictionary(bodyAsDictionary)); - } - else - { - uriBuilder.Query = FormatDictionary(bodyAsDictionary); - } - - uri = uriBuilder.Uri; - // set body to null to prevent later FillRequestStream - Body = null; - } - - return uri; - } - - private static Uri CheckProtocol(Uri uri) - { - if (uri == null) { throw new ArgumentNullException(nameof(uri)); } - - if (!uri.IsAbsoluteUri) - { - uri = new Uri("http://" + uri.OriginalString); - } - - return (uri); - } - - private string QualifyFilePath(string path) - { - string resolvedFilePath = PathUtils.ResolveFilePath(filePath: path, command: this, isLiteralPath: true); - return resolvedFilePath; - } - - private static string FormatDictionary(IDictionary content) - { - if (content == null) - throw new ArgumentNullException(nameof(content)); - - StringBuilder bodyBuilder = new(); - foreach (string key in content.Keys) - { - if (bodyBuilder.Length > 0) - { - bodyBuilder.Append('&'); - } - - object value = content[key]; - - // URLEncode the key and value - string encodedKey = WebUtility.UrlEncode(key); - string encodedValue = string.Empty; - if (value != null) - { - encodedValue = WebUtility.UrlEncode(value.ToString()); - } - - bodyBuilder.AppendFormat("{0}={1}", encodedKey, encodedValue); - } - - return bodyBuilder.ToString(); - } - - private ErrorRecord GetValidationError(string msg, string errorId) - { - var ex = new ValidationMetadataException(msg); - var error = new ErrorRecord(ex, errorId, ErrorCategory.InvalidArgument, this); - return (error); - } - - private ErrorRecord GetValidationError(string msg, string errorId, params object[] args) - { - msg = string.Format(CultureInfo.InvariantCulture, msg, args); - var ex = new ValidationMetadataException(msg); - var error = new ErrorRecord(ex, errorId, ErrorCategory.InvalidArgument, this); - return (error); - } - - private bool IsStandardMethodSet() - { - return (ParameterSetName == "StandardMethod" || ParameterSetName == "StandardMethodNoProxy"); - } - - private bool IsCustomMethodSet() - { - return (ParameterSetName == "CustomMethod" || ParameterSetName == "CustomMethodNoProxy"); - } - - private string GetBasicAuthorizationHeader() - { - var password = new NetworkCredential(null, Credential.Password).Password; - string unencoded = string.Format("{0}:{1}", Credential.UserName, password); - byte[] bytes = Encoding.UTF8.GetBytes(unencoded); - return string.Format("Basic {0}", Convert.ToBase64String(bytes)); - } - - private string GetBearerAuthorizationHeader() - { - return string.Format("Bearer {0}", new NetworkCredential(string.Empty, Token).Password); - } - - private void ProcessAuthentication() - { - if (Authentication == WebAuthenticationType.Basic) - { - WebSession.Headers["Authorization"] = GetBasicAuthorizationHeader(); - } - else if (Authentication == WebAuthenticationType.Bearer || Authentication == WebAuthenticationType.OAuth) - { - WebSession.Headers["Authorization"] = GetBearerAuthorizationHeader(); - } - else - { - Diagnostics.Assert(false, string.Format("Unrecognized Authentication value: {0}", Authentication)); - } - } - - #endregion Helper Methods - } - - // TODO: Merge Partials - - /// - /// Exception class for webcmdlets to enable returning HTTP error response. - /// - public sealed class HttpResponseException : HttpRequestException - { - /// - /// Initializes a new instance of the class. - /// - /// Message for the exception. - /// Response from the HTTP server. - public HttpResponseException(string message, HttpResponseMessage response) : base(message) - { - Response = response; - } - - /// - /// HTTP error response. - /// - public HttpResponseMessage Response { get; } - } - - /// - /// Base class for Invoke-RestMethod and Invoke-WebRequest commands. - /// - public abstract partial class WebRequestPSCmdlet : PSCmdlet - { - /// - /// Gets or sets the PreserveAuthorizationOnRedirect property. - /// - /// - /// This property overrides compatibility with web requests on Windows. - /// On FullCLR (WebRequest), authorization headers are stripped during redirect. - /// CoreCLR (HTTPClient) does not have this behavior so web requests that work on - /// PowerShell/FullCLR can fail with PowerShell/CoreCLR. To provide compatibility, - /// we'll detect requests with an Authorization header and automatically strip - /// the header when the first redirect occurs. This switch turns off this logic for - /// edge cases where the authorization header needs to be preserved across redirects. - /// - [Parameter] - public virtual SwitchParameter PreserveAuthorizationOnRedirect { get; set; } - - /// - /// Gets or sets the SkipHeaderValidation property. - /// - /// - /// This property adds headers to the request's header collection without validation. - /// - [Parameter] - public virtual SwitchParameter SkipHeaderValidation { get; set; } - - #region Abstract Methods - - /// - /// Read the supplied WebResponse object and push the resulting output into the pipeline. - /// - /// Instance of a WebResponse object to be processed. - internal abstract void ProcessResponse(HttpResponseMessage response); - - #endregion Abstract Methods - - /// - /// Cancellation token source. - /// - internal CancellationTokenSource _cancelToken = null; - - /// - /// Parse Rel Links. - /// - internal bool _parseRelLink = false; - - /// - /// Automatically follow Rel Links. - /// - internal bool _followRelLink = false; - - /// - /// Automatically follow Rel Links. - /// - internal Dictionary _relationLink = null; - - /// - /// Maximum number of Rel Links to follow. - /// - internal int _maximumFollowRelLink = Int32.MaxValue; - /// - /// The remote endpoint returned a 206 status code indicating successful resume. - /// - private bool _resumeSuccess = false; - - /// - /// The current size of the local file being resumed. - /// - private long _resumeFileSize = 0; - - private HttpMethod GetHttpMethod(WebRequestMethod method) - { - switch (Method) - { - case WebRequestMethod.Default: - case WebRequestMethod.Get: - return HttpMethod.Get; - case WebRequestMethod.Head: - return HttpMethod.Head; - case WebRequestMethod.Post: - return HttpMethod.Post; - case WebRequestMethod.Put: - return HttpMethod.Put; - case WebRequestMethod.Delete: - return HttpMethod.Delete; - case WebRequestMethod.Trace: - return HttpMethod.Trace; - case WebRequestMethod.Options: - return HttpMethod.Options; - default: - // Merge and Patch - return new HttpMethod(Method.ToString().ToUpperInvariant()); - } + WebSession.ConnectionTimeout = ConvertTimeoutSecondsToTimeSpan(ConnectionTimeoutSeconds); } - #region Virtual Methods - - // NOTE: Only pass true for handleRedirect if the original request has an authorization header - // and PreserveAuthorizationOnRedirect is NOT set. internal virtual HttpClient GetHttpClient(bool handleRedirect) { - // By default the HttpClientHandler will automatically decompress GZip and Deflate content - HttpClientHandler handler = new(); - handler.CookieContainer = WebSession.Cookies; - - // set the credentials used by this request - if (WebSession.UseDefaultCredentials) - { - // the UseDefaultCredentials flag overrides other supplied credentials - handler.UseDefaultCredentials = true; - } - else if (WebSession.Credentials != null) - { - handler.Credentials = WebSession.Credentials; - } - - if (NoProxy) - { - handler.UseProxy = false; - } - else if (WebSession.Proxy != null) - { - handler.Proxy = WebSession.Proxy; - } - - if (WebSession.Certificates != null) - { - handler.ClientCertificates.AddRange(WebSession.Certificates); - } - - if (SkipCertificateCheck) - { - handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; - handler.ClientCertificateOptions = ClientCertificateOption.Manual; - } - - // This indicates GetResponse will handle redirects. - if (handleRedirect) - { - handler.AllowAutoRedirect = false; - } - else if (WebSession.MaximumRedirection > -1) - { - if (WebSession.MaximumRedirection == 0) - { - handler.AllowAutoRedirect = false; - } - else - { - handler.MaxAutomaticRedirections = WebSession.MaximumRedirection; - } - } - - handler.SslProtocols = (SslProtocols)SslProtocol; - - HttpClient httpClient = new(handler); + HttpClient client = WebSession.GetHttpClient(handleRedirect, out bool clientWasReset); - // check timeout setting (in seconds instead of milliseconds as in HttpWebRequest) - if (TimeoutSec == 0) - { - // A zero timeout means infinite - httpClient.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite); - } - else if (TimeoutSec > 0) + if (clientWasReset) { - httpClient.Timeout = new TimeSpan(0, 0, TimeoutSec); + WriteVerbose(WebCmdletStrings.WebSessionConnectionRecreated); } - return httpClient; + return client; } internal virtual HttpRequestMessage GetRequest(Uri uri) { Uri requestUri = PrepareUri(uri); - HttpMethod httpMethod = null; - - switch (ParameterSetName) - { - case "StandardMethodNoProxy": - goto case "StandardMethod"; - case "StandardMethod": - // set the method if the parameter was provided - httpMethod = GetHttpMethod(Method); - break; - case "CustomMethodNoProxy": - goto case "CustomMethod"; - case "CustomMethod": - if (!string.IsNullOrEmpty(CustomMethod)) - { - // set the method if the parameter was provided - httpMethod = new HttpMethod(CustomMethod.ToUpperInvariant()); - } + HttpMethod httpMethod = string.IsNullOrEmpty(CustomMethod) ? GetHttpMethod(Method) : new HttpMethod(CustomMethod); - break; - } + // Create the base WebRequest object + HttpRequestMessage request = new(httpMethod, requestUri); - // create the base WebRequest object - var request = new HttpRequestMessage(httpMethod, requestUri); + if (HttpVersion is not null) + { + request.Version = HttpVersion; + } - // pull in session data + // Pull in session data if (WebSession.Headers.Count > 0) { WebSession.ContentHeaders.Clear(); @@ -1112,8 +1099,7 @@ internal virtual HttpRequestMessage GetRequest(Uri uri) } // Set 'User-Agent' if WebSession.Headers doesn't already contain it - string userAgent = null; - if (WebSession.Headers.TryGetValue(HttpKnownHeaderNames.UserAgent, out userAgent)) + if (WebSession.Headers.TryGetValue(HttpKnownHeaderNames.UserAgent, out string userAgent)) { WebSession.UserAgent = userAgent; } @@ -1136,10 +1122,10 @@ internal virtual HttpRequestMessage GetRequest(Uri uri) } // Set 'Transfer-Encoding' - if (TransferEncoding != null) + if (TransferEncoding is not null) { request.Headers.TransferEncodingChunked = true; - var headerValue = new TransferCodingHeaderValue(TransferEncoding); + TransferCodingHeaderValue headerValue = new(TransferEncoding); if (!request.Headers.TransferEncoding.Contains(headerValue)) { request.Headers.TransferEncoding.Add(headerValue); @@ -1150,7 +1136,8 @@ internal virtual HttpRequestMessage GetRequest(Uri uri) // If not, create a Range to request the entire file. if (Resume.IsPresent) { - var fileInfo = new FileInfo(QualifiedOutFile); + FileInfo fileInfo = new(QualifiedOutFile); + if (fileInfo.Exists) { request.Headers.Range = new RangeHeaderValue(fileInfo.Length, null); @@ -1162,37 +1149,31 @@ internal virtual HttpRequestMessage GetRequest(Uri uri) } } - return (request); + return request; } internal virtual void FillRequestStream(HttpRequestMessage request) { - if (request == null) { throw new ArgumentNullException(nameof(request)); } + ArgumentNullException.ThrowIfNull(request); - // set the content type - if (ContentType != null) + // Set the request content type + if (ContentType is not null) { WebSession.ContentHeaders[HttpKnownHeaderNames.ContentType] = ContentType; - // request } - // ContentType == null - else if (Method == WebRequestMethod.Post || (IsCustomMethodSet() && CustomMethod.ToUpperInvariant() == "POST")) + else if (request.Method == HttpMethod.Post) { // Win8:545310 Invoke-WebRequest does not properly set MIME type for POST - string contentType = null; - WebSession.ContentHeaders.TryGetValue(HttpKnownHeaderNames.ContentType, out contentType); + WebSession.ContentHeaders.TryGetValue(HttpKnownHeaderNames.ContentType, out string contentType); if (string.IsNullOrEmpty(contentType)) { WebSession.ContentHeaders[HttpKnownHeaderNames.ContentType] = "application/x-www-form-urlencoded"; } } - if (Form != null) + if (Form is not null) { - // Content headers will be set by MultipartFormDataContent which will throw unless we clear them first - WebSession.ContentHeaders.Clear(); - - var formData = new MultipartFormDataContent(); + MultipartFormDataContent formData = new(); foreach (DictionaryEntry formEntry in Form) { // AddMultipartContent will handle PSObject unwrapping, Object type determination and enumerateing top level IEnumerables. @@ -1201,73 +1182,67 @@ internal virtual void FillRequestStream(HttpRequestMessage request) SetRequestContent(request, formData); } - // coerce body into a usable form - else if (Body != null) + else if (Body is not null) { - object content = Body; - - // make sure we're using the base object of the body, not the PSObject wrapper - PSObject psBody = Body as PSObject; - if (psBody != null) - { - content = psBody.BaseObject; - } + // Coerce body into a usable form + // Make sure we're using the base object of the body, not the PSObject wrapper + object content = Body is PSObject psBody ? psBody.BaseObject : Body; - if (content is FormObject form) - { - SetRequestContent(request, form.Fields); - } - else if (content is IDictionary dictionary && request.Method != HttpMethod.Get) - { - SetRequestContent(request, dictionary); - } - else if (content is XmlNode xmlNode) - { - SetRequestContent(request, xmlNode); - } - else if (content is Stream stream) - { - SetRequestContent(request, stream); - } - else if (content is byte[] bytes) - { - SetRequestContent(request, bytes); - } - else if (content is MultipartFormDataContent multipartFormDataContent) - { - WebSession.ContentHeaders.Clear(); - SetRequestContent(request, multipartFormDataContent); - } - else + switch (content) { - SetRequestContent( - request, - (string)LanguagePrimitives.ConvertTo(content, typeof(string), CultureInfo.InvariantCulture)); + case FormObject form: + SetRequestContent(request, form.Fields); + break; + case IDictionary dictionary when request.Method != HttpMethod.Get: + SetRequestContent(request, dictionary); + break; + case XmlNode xmlNode: + SetRequestContent(request, xmlNode); + break; + case Stream stream: + SetRequestContent(request, stream); + break; + case byte[] bytes: + SetRequestContent(request, bytes); + break; + case MultipartFormDataContent multipartFormDataContent: + SetRequestContent(request, multipartFormDataContent); + break; + default: + SetRequestContent(request, (string)LanguagePrimitives.ConvertTo(content, typeof(string), CultureInfo.InvariantCulture)); + break; } } - else if (InFile != null) // copy InFile data + else if (InFile is not null) { + // Copy InFile data try { - // open the input file + // Open the input file SetRequestContent(request, new FileStream(InFile, FileMode.Open, FileAccess.Read, FileShare.Read)); } catch (UnauthorizedAccessException) { - string msg = string.Format(CultureInfo.InvariantCulture, WebCmdletStrings.AccessDenied, - _originalFilePath); + string msg = string.Format(CultureInfo.InvariantCulture, WebCmdletStrings.AccessDenied, _originalFilePath); + throw new UnauthorizedAccessException(msg); } } - // Add the content headers - if (request.Content == null) + // For other methods like Put where empty content has meaning, we need to fill in the content + if (request.Content is null) { + // If this is a Get request and there is no content, then don't fill in the content as empty content gets rejected by some web services per RFC7230 + if (request.Method == HttpMethod.Get && ContentType is null) + { + return; + } + request.Content = new StringContent(string.Empty); request.Content.Headers.Clear(); } - foreach (var entry in WebSession.ContentHeaders) + foreach (KeyValuePair entry in WebSession.ContentHeaders) { if (!string.IsNullOrWhiteSpace(entry.Value)) { @@ -1283,7 +1258,7 @@ internal virtual void FillRequestStream(HttpRequestMessage request) } catch (FormatException ex) { - var outerEx = new ValidationMetadataException(WebCmdletStrings.ContentTypeException, ex); + ValidationMetadataException outerEx = new(WebCmdletStrings.ContentTypeException, ex); ErrorRecord er = new(outerEx, "WebCmdletContentTypeException", ErrorCategory.InvalidArgument, ContentType); ThrowTerminatingError(er); } @@ -1292,106 +1267,96 @@ internal virtual void FillRequestStream(HttpRequestMessage request) } } - // Returns true if the status code is one of the supported redirection codes. - private static bool IsRedirectCode(HttpStatusCode code) - { - int intCode = (int)code; - return - ( - (intCode >= 300 && intCode < 304) - || - intCode == 307 - ); - } - - // Returns true if the status code is a redirection code and the action requires switching from POST to GET on redirection. - // NOTE: Some of these status codes map to the same underlying value but spelling them out for completeness. - private static bool IsRedirectToGet(HttpStatusCode code) - { - return - ( - code == HttpStatusCode.Found - || - code == HttpStatusCode.Moved - || - code == HttpStatusCode.Redirect - || - code == HttpStatusCode.RedirectMethod - || - code == HttpStatusCode.SeeOther - || - code == HttpStatusCode.Ambiguous - || - code == HttpStatusCode.MultipleChoices - ); - } - - private bool ShouldRetry(HttpStatusCode code) - { - int intCode = (int)code; - - if (((intCode == 304) || (intCode >= 400 && intCode <= 599)) && WebSession.MaximumRetryCount > 0) - { - return true; - } - - return false; - } - - internal virtual HttpResponseMessage GetResponse(HttpClient client, HttpRequestMessage request, bool keepAuthorization) + internal virtual HttpResponseMessage GetResponse(HttpClient client, HttpRequestMessage request, bool handleRedirect) { - if (client == null) { throw new ArgumentNullException(nameof(client)); } - - if (request == null) { throw new ArgumentNullException(nameof(request)); } + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(request); // Add 1 to account for the first request. int totalRequests = WebSession.MaximumRetryCount + 1; - HttpRequestMessage req = request; + HttpRequestMessage currentRequest = request; HttpResponseMessage response = null; do { // Track the current URI being used by various requests and re-requests. - var currentUri = req.RequestUri; + Uri currentUri = currentRequest.RequestUri; _cancelToken = new CancellationTokenSource(); - response = client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, _cancelToken.Token).GetAwaiter().GetResult(); + try + { + if (IsWriteVerboseEnabled()) + { + WriteWebRequestVerboseInfo(currentRequest); + } + + if (IsWriteDebugEnabled()) + { + WriteWebRequestDebugInfo(currentRequest); + } + + response = client.SendAsync(currentRequest, HttpCompletionOption.ResponseHeadersRead, _cancelToken.Token).GetAwaiter().GetResult(); + + if (IsWriteVerboseEnabled()) + { + WriteWebResponseVerboseInfo(response); + } + + if (IsWriteDebugEnabled()) + { + WriteWebResponseDebugInfo(response); + } + } + catch (TaskCanceledException ex) + { + if (ex.InnerException is TimeoutException) + { + // HTTP Request timed out + ErrorRecord er = new(ex, "ConnectionTimeoutReached", ErrorCategory.OperationTimeout, null); + ThrowTerminatingError(er); + } + else + { + throw; + } - if (keepAuthorization && IsRedirectCode(response.StatusCode) && response.Headers.Location != null) + } + if (handleRedirect + && _maximumRedirection is not 0 + && IsRedirectCode(response.StatusCode) + && response.Headers.Location is not null) { _cancelToken.Cancel(); _cancelToken = null; - // if explicit count was provided, reduce it for this redirection. - if (WebSession.MaximumRedirection > 0) + // If explicit count was provided, reduce it for this redirection. + if (_maximumRedirection > 0) { - WebSession.MaximumRedirection--; + _maximumRedirection--; } - // For selected redirects that used POST, GET must be used with the - // redirected Location. - // Since GET is the default; POST only occurs when -Method POST is used. - if (Method == WebRequestMethod.Post && IsRedirectToGet(response.StatusCode)) + + // For selected redirects, GET must be used with the redirected Location. + if (RequestRequiresForceGet(response.StatusCode, currentRequest.Method) && !PreserveHttpMethodOnRedirect) { - // See https://msdn.microsoft.com/library/system.net.httpstatuscode(v=vs.110).aspx Method = WebRequestMethod.Get; + CustomMethod = string.Empty; } currentUri = new Uri(request.RequestUri, response.Headers.Location); + // Continue to handle redirection - using (client = GetHttpClient(handleRedirect: true)) - using (HttpRequestMessage redirectRequest = GetRequest(currentUri)) - { - response = GetResponse(client, redirectRequest, keepAuthorization); - } + using HttpRequestMessage redirectRequest = GetRequest(currentUri); + response.Dispose(); + response = GetResponse(client, redirectRequest, handleRedirect); } // Request again without the Range header because the server indicated the range was not satisfiable. // This happens when the local file is larger than the remote file. // If the size of the remote file is the same as the local file, there is nothing to resume. - if (Resume.IsPresent && - response.StatusCode == HttpStatusCode.RequestedRangeNotSatisfiable && - (response.Content.Headers.ContentRange.HasLength && - response.Content.Headers.ContentRange.Length != _resumeFileSize)) + if (Resume.IsPresent + && response.StatusCode == HttpStatusCode.RequestedRangeNotSatisfiable + && (response.Content.Headers.ContentRange.HasLength + && response.Content.Headers.ContentRange.Length != _resumeFileSize)) { _cancelToken.Cancel(); @@ -1404,45 +1369,53 @@ internal virtual HttpResponseMessage GetResponse(HttpClient client, HttpRequestM using (HttpRequestMessage requestWithoutRange = GetRequest(currentUri)) { FillRequestStream(requestWithoutRange); - long requestContentLength = 0; - if (requestWithoutRange.Content != null) - { - requestContentLength = requestWithoutRange.Content.Headers.ContentLength.Value; - } - string reqVerboseMsg = string.Format( - CultureInfo.CurrentCulture, - WebCmdletStrings.WebMethodInvocationVerboseMsg, - requestWithoutRange.Method, - requestWithoutRange.RequestUri, - requestContentLength); - WriteVerbose(reqVerboseMsg); - - return GetResponse(client, requestWithoutRange, keepAuthorization); + response.Dispose(); + response = GetResponse(client, requestWithoutRange, handleRedirect); } } _resumeSuccess = response.StatusCode == HttpStatusCode.PartialContent; - // When MaximumRetryCount is not specified, the totalRequests == 1. - if (totalRequests > 1 && ShouldRetry(response.StatusCode)) - { + // When MaximumRetryCount is not specified, the totalRequests is 1. + if (totalRequests > 1 && ShouldRetry(response.StatusCode)) + { + int retryIntervalInSeconds = WebSession.RetryIntervalInSeconds; + + // If the status code is 429 get the retry interval from the Headers. + // Ignore broken header and its value. + if (response.StatusCode is HttpStatusCode.TooManyRequests && response.Headers.TryGetValues(HttpKnownHeaderNames.RetryAfter, out IEnumerable retryAfter)) + { + try + { + IEnumerator enumerator = retryAfter.GetEnumerator(); + if (enumerator.MoveNext()) + { + retryIntervalInSeconds = Convert.ToInt32(enumerator.Current); + } + } + catch + { + // Ignore broken header. + } + } + string retryMessage = string.Format( CultureInfo.CurrentCulture, WebCmdletStrings.RetryVerboseMsg, - RetryIntervalSec, + retryIntervalInSeconds, response.StatusCode); WriteVerbose(retryMessage); _cancelToken = new CancellationTokenSource(); - Task.Delay(WebSession.RetryIntervalInSeconds * 1000, _cancelToken.Token).GetAwaiter().GetResult(); + Task.Delay(retryIntervalInSeconds * 1000, _cancelToken.Token).GetAwaiter().GetResult(); _cancelToken.Cancel(); _cancelToken = null; - req.Dispose(); - req = GetRequest(currentUri); - FillRequestStream(req); + currentRequest.Dispose(); + currentRequest = GetRequest(currentUri); + FillRequestStream(currentRequest); } totalRequests--; @@ -1454,219 +1427,329 @@ internal virtual HttpResponseMessage GetResponse(HttpClient client, HttpRequestM internal virtual void UpdateSession(HttpResponseMessage response) { - if (response == null) { throw new ArgumentNullException(nameof(response)); } + ArgumentNullException.ThrowIfNull(response); } - #endregion Virtual Methods - #region Overrides + #region Helper Methods +#nullable enable + internal static TimeSpan ConvertTimeoutSecondsToTimeSpan(int timeout) => timeout > 0 ? TimeSpan.FromSeconds(timeout) : Timeout.InfiniteTimeSpan; - /// - /// The main execution method for cmdlets derived from WebRequestPSCmdlet. - /// - protected override void ProcessRecord() + private void WriteWebRequestVerboseInfo(HttpRequestMessage request) { try { - // Set cmdlet context for write progress - ValidateParameters(); - PrepareSession(); + // Typical Basic Example: 'WebRequest: v1.1 POST https://httpstat.us/200 with query length 6' + StringBuilder verboseBuilder = new(128); - // if the request contains an authorization header and PreserveAuthorizationOnRedirect is not set, - // it needs to be stripped on the first redirect. - bool keepAuthorization = WebSession != null - && - WebSession.Headers != null - && - PreserveAuthorizationOnRedirect.IsPresent - && - WebSession.Headers.ContainsKey(HttpKnownHeaderNames.Authorization); - - using (HttpClient client = GetHttpClient(keepAuthorization)) + // "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 }) { - int followedRelLink = 0; - Uri uri = Uri; - do + if (headerSet is null) { - if (followedRelLink > 0) - { - string linkVerboseMsg = string.Format(CultureInfo.CurrentCulture, - WebCmdletStrings.FollowingRelLinkVerboseMsg, - uri.AbsoluteUri); - WriteVerbose(linkVerboseMsg); - } + continue; + } - using (HttpRequestMessage request = GetRequest(uri)) - { - FillRequestStream(request); - try - { - long requestContentLength = 0; - if (request.Content != null) - requestContentLength = request.Content.Headers.ContentLength.Value; - - string reqVerboseMsg = string.Format(CultureInfo.CurrentCulture, - WebCmdletStrings.WebMethodInvocationVerboseMsg, - request.Method, - requestContentLength); - WriteVerbose(reqVerboseMsg); - - HttpResponseMessage response = GetResponse(client, request, keepAuthorization); - - string contentType = ContentHelper.GetContentType(response); - string respVerboseMsg = string.Format(CultureInfo.CurrentCulture, - WebCmdletStrings.WebResponseVerboseMsg, - response.Content.Headers.ContentLength, - contentType); - WriteVerbose(respVerboseMsg); - - bool _isSuccess = response.IsSuccessStatusCode; - - // Check if the Resume range was not satisfiable because the file already completed downloading. - // This happens when the local file is the same size as the remote file. - if (Resume.IsPresent && - response.StatusCode == HttpStatusCode.RequestedRangeNotSatisfiable && - response.Content.Headers.ContentRange.HasLength && - response.Content.Headers.ContentRange.Length == _resumeFileSize) - { - _isSuccess = true; - WriteVerbose(string.Format(CultureInfo.CurrentCulture, WebCmdletStrings.OutFileWritingSkipped, OutFile)); - // Disable writing to the OutFile. - OutFile = null; - } + debugBuilder.AppendLine(headerSet.ToString()); + } - if (ShouldCheckHttpStatus && !_isSuccess) - { - string message = string.Format(CultureInfo.CurrentCulture, WebCmdletStrings.ResponseStatusCodeFailure, - (int)response.StatusCode, response.ReasonPhrase); - HttpResponseException httpEx = new(message, response); - ErrorRecord er = new(httpEx, "WebCmdletWebResponseException", ErrorCategory.InvalidOperation, request); - string detailMsg = string.Empty; - StreamReader reader = null; - try - { - reader = new StreamReader(StreamHelper.GetResponseStream(response)); - // remove HTML tags making it easier to read - detailMsg = System.Text.RegularExpressions.Regex.Replace(reader.ReadToEnd(), "<[^>]*>", string.Empty); - } - catch (Exception) - { - // catch all - } - finally - { - if (reader != null) - { - reader.Dispose(); - } - } - - if (!string.IsNullOrEmpty(detailMsg)) - { - er.ErrorDetails = new ErrorDetails(detailMsg); - } + 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(); + } - ThrowTerminatingError(er); - } + 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}"); + } + } - if (_parseRelLink || _followRelLink) - { - ParseLinkHeader(response, uri); - } + 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()}"); - ProcessResponse(response); - UpdateSession(response); + string? responseContentType = ContentHelper.GetContentType(response); + if (responseContentType is not null) + { + verboseBuilder.Append($" with {responseContentType} payload"); + } - // If we hit our maximum redirection count, generate an error. - // Errors with redirection counts of greater than 0 are handled automatically by .NET, but are - // impossible to detect programmatically when we hit this limit. By handling this ourselves - // (and still writing out the result), users can debug actual HTTP redirect problems. - if (WebSession.MaximumRedirection == 0) // Indicate "HttpClientHandler.AllowAutoRedirect == false" - { - if (response.StatusCode == HttpStatusCode.Found || - response.StatusCode == HttpStatusCode.Moved || - response.StatusCode == HttpStatusCode.MovedPermanently) - { - ErrorRecord er = new(new InvalidOperationException(), "MaximumRedirectExceeded", ErrorCategory.InvalidOperation, request); - er.ErrorDetails = new ErrorDetails(WebCmdletStrings.MaximumRedirectionCountExceeded); - WriteError(er); - } - } - } - catch (HttpRequestException ex) - { - ErrorRecord er = new(ex, "WebCmdletWebResponseException", ErrorCategory.InvalidOperation, request); - if (ex.InnerException != null) - { - er.ErrorDetails = new ErrorDetails(ex.InnerException.Message); - } + long? responseContentLength = response.Content?.Headers?.ContentLength; + if (responseContentLength is not null) + { + verboseBuilder.Append($" with body size {ContentHelper.GetFriendlyContentLength(responseContentLength)}"); + } - ThrowTerminatingError(er); - } + 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}"); + } + } - if (_followRelLink) - { - if (!_relationLink.ContainsKey("next")) - { - return; - } + 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; + } - uri = new Uri(_relationLink["next"]); - followedRelLink++; - } - } + 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}]"); } - while (_followRelLink && (followedRelLink < _maximumFollowRelLink)); } + + WriteDebug(debugBuilder.ToString().Trim()); } - catch (CryptographicException ex) + catch (Exception ex) { - ErrorRecord er = new(ex, "WebCmdletCertificateException", ErrorCategory.SecurityError, null); - ThrowTerminatingError(er); + // 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}"); } - catch (NotSupportedException ex) + } + + private Uri PrepareUri(Uri uri) + { + uri = CheckProtocol(uri); + + // Before creating the web request, + // preprocess Body if content is a dictionary and method is GET (set as query) + LanguagePrimitives.TryConvertTo(Body, out IDictionary bodyAsDictionary); + if (bodyAsDictionary is not null && (Method == WebRequestMethod.Default || Method == WebRequestMethod.Get || CustomMethod == "GET")) { - ErrorRecord er = new(ex, "WebCmdletIEDomNotSupportedException", ErrorCategory.NotImplemented, null); - ThrowTerminatingError(er); + UriBuilder uriBuilder = new(uri); + if (uriBuilder.Query is not null && uriBuilder.Query.Length > 1) + { + uriBuilder.Query = string.Concat(uriBuilder.Query.AsSpan(1), "&", FormatDictionary(bodyAsDictionary)); + } + else + { + uriBuilder.Query = FormatDictionary(bodyAsDictionary); + } + + uri = uriBuilder.Uri; + + // Set body to null to prevent later FillRequestStream + Body = null; } + + return uri; } - /// - /// Implementing ^C, after start the BeginGetResponse. - /// - protected override void StopProcessing() + private static Uri CheckProtocol(Uri uri) + { + ArgumentNullException.ThrowIfNull(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); + + private static string FormatDictionary(IDictionary content) { - if (_cancelToken != null) + ArgumentNullException.ThrowIfNull(content); + + StringBuilder bodyBuilder = new(); + foreach (string key in content.Keys) { - _cancelToken.Cancel(); + if (bodyBuilder.Length > 0) + { + bodyBuilder.Append('&'); + } + + object value = content[key]; + + // URLEncode the key and value + string encodedKey = WebUtility.UrlEncode(key); + string encodedValue = value is null ? string.Empty : WebUtility.UrlEncode(value.ToString()); + + bodyBuilder.Append($"{encodedKey}={encodedValue}"); } + + return bodyBuilder.ToString(); } - #endregion Overrides + private ErrorRecord GetValidationError(string msg, string errorId) + { + ValidationMetadataException ex = new(msg); + return new ErrorRecord(ex, errorId, ErrorCategory.InvalidArgument, this); + } - #region Helper Methods + private ErrorRecord GetValidationError(string msg, string errorId, params object[] args) + { + msg = string.Format(CultureInfo.InvariantCulture, msg, args); + ValidationMetadataException ex = new(msg); + return new ErrorRecord(ex, errorId, ErrorCategory.InvalidArgument, this); + } + + private string GetBasicAuthorizationHeader() + { + string password = new NetworkCredential(string.Empty, Credential.Password).Password; + string unencoded = string.Create(CultureInfo.InvariantCulture, $"{Credential.UserName}:{password}"); + byte[] bytes = Encoding.UTF8.GetBytes(unencoded); + return string.Create(CultureInfo.InvariantCulture, $"Basic {Convert.ToBase64String(bytes)}"); + } + + private string GetBearerAuthorizationHeader() + { + return string.Create(CultureInfo.InvariantCulture, $"Bearer {new NetworkCredential(string.Empty, Token).Password}"); + } + + private void ProcessAuthentication() + { + if (Authentication == WebAuthenticationType.Basic) + { + WebSession.Headers["Authorization"] = GetBasicAuthorizationHeader(); + } + else if (Authentication == WebAuthenticationType.Bearer || Authentication == WebAuthenticationType.OAuth) + { + WebSession.Headers["Authorization"] = GetBearerAuthorizationHeader(); + } + else + { + Diagnostics.Assert(false, string.Create(CultureInfo.InvariantCulture, $"Unrecognized Authentication value: {Authentication}")); + } + } + + private bool IsPersistentSession() => MyInvocation.BoundParameters.ContainsKey(nameof(WebSession)) || MyInvocation.BoundParameters.ContainsKey(nameof(SessionVariable)); /// /// Sets the ContentLength property of the request and writes the specified content to the request's RequestStream. /// /// The WebRequest who's content is to be set. /// A byte array containing the content data. - /// The number of bytes written to the requests RequestStream (and the new value of the request's ContentLength property. /// - /// Because this function sets the request's ContentLength property and writes content data into the requests's stream, + /// Because this function sets the request's ContentLength property and writes content data into the request's stream, /// it should be called one time maximum on a given request. /// - internal long SetRequestContent(HttpRequestMessage request, byte[] content) + internal void SetRequestContent(HttpRequestMessage request, byte[] content) { - if (request == null) - throw new ArgumentNullException(nameof(request)); - if (content == null) - return 0; - - var byteArrayContent = new ByteArrayContent(content); - request.Content = byteArrayContent; + ArgumentNullException.ThrowIfNull(request); + ArgumentNullException.ThrowIfNull(content); - return byteArrayContent.Headers.ContentLength.Value; + request.Content = new ByteArrayContent(content); } /// @@ -1674,85 +1757,63 @@ internal long SetRequestContent(HttpRequestMessage request, byte[] content) /// /// The WebRequest who's content is to be set. /// A String object containing the content data. - /// The number of bytes written to the requests RequestStream (and the new value of the request's ContentLength property. /// - /// Because this function sets the request's ContentLength property and writes content data into the requests's stream, + /// Because this function sets the request's ContentLength property and writes content data into the request's stream, /// it should be called one time maximum on a given request. /// - internal long SetRequestContent(HttpRequestMessage request, string content) + internal void SetRequestContent(HttpRequestMessage request, string content) { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (content == null) - return 0; + ArgumentNullException.ThrowIfNull(request); + ArgumentNullException.ThrowIfNull(content); Encoding encoding = null; - if (ContentType != null) + + if (WebSession.ContentHeaders.TryGetValue(HttpKnownHeaderNames.ContentType, out string contentType) && contentType is not null) { // If Content-Type contains the encoding format (as CharSet), use this encoding format // to encode the Body of the WebRequest sent to the server. Default Encoding format // would be used if Charset is not supplied in the Content-Type property. try { - var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(ContentType); + MediaTypeHeaderValue mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(contentType); if (!string.IsNullOrEmpty(mediaTypeHeaderValue.CharSet)) { encoding = Encoding.GetEncoding(mediaTypeHeaderValue.CharSet); } } - catch (FormatException ex) + catch (Exception ex) when (ex is FormatException || ex is ArgumentException) { if (!SkipHeaderValidation) { - var outerEx = new ValidationMetadataException(WebCmdletStrings.ContentTypeException, ex); - ErrorRecord er = new(outerEx, "WebCmdletContentTypeException", ErrorCategory.InvalidArgument, ContentType); - ThrowTerminatingError(er); - } - } - catch (ArgumentException ex) - { - if (!SkipHeaderValidation) - { - var outerEx = new ValidationMetadataException(WebCmdletStrings.ContentTypeException, ex); - ErrorRecord er = new(outerEx, "WebCmdletContentTypeException", ErrorCategory.InvalidArgument, ContentType); + ValidationMetadataException outerEx = new(WebCmdletStrings.ContentTypeException, ex); + ErrorRecord er = new(outerEx, "WebCmdletContentTypeException", ErrorCategory.InvalidArgument, contentType); ThrowTerminatingError(er); } } } byte[] bytes = StreamHelper.EncodeToBytes(content, encoding); - var byteArrayContent = new ByteArrayContent(bytes); - request.Content = byteArrayContent; - - return byteArrayContent.Headers.ContentLength.Value; + request.Content = new ByteArrayContent(bytes); } - internal long SetRequestContent(HttpRequestMessage request, XmlNode xmlNode) + internal void SetRequestContent(HttpRequestMessage request, XmlNode xmlNode) { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (xmlNode == null) - return 0; + ArgumentNullException.ThrowIfNull(request); + ArgumentNullException.ThrowIfNull(xmlNode); byte[] bytes = null; XmlDocument doc = xmlNode as XmlDocument; - if (doc?.FirstChild is XmlDeclaration) + if (doc?.FirstChild is XmlDeclaration decl && !string.IsNullOrEmpty(decl.Encoding)) { - XmlDeclaration decl = doc.FirstChild as XmlDeclaration; Encoding encoding = Encoding.GetEncoding(decl.Encoding); bytes = StreamHelper.EncodeToBytes(doc.OuterXml, encoding); } else { - bytes = StreamHelper.EncodeToBytes(xmlNode.OuterXml); + bytes = StreamHelper.EncodeToBytes(xmlNode.OuterXml, encoding: null); } - var byteArrayContent = new ByteArrayContent(bytes); - request.Content = byteArrayContent; - - return byteArrayContent.Headers.ContentLength.Value; + request.Content = new ByteArrayContent(bytes); } /// @@ -1760,65 +1821,51 @@ internal long SetRequestContent(HttpRequestMessage request, XmlNode xmlNode) /// /// The WebRequest who's content is to be set. /// A Stream object containing the content data. - /// The number of bytes written to the requests RequestStream (and the new value of the request's ContentLength property. /// - /// Because this function sets the request's ContentLength property and writes content data into the requests's stream, + /// Because this function sets the request's ContentLength property and writes content data into the request's stream, /// it should be called one time maximum on a given request. /// - internal long SetRequestContent(HttpRequestMessage request, Stream contentStream) + internal void SetRequestContent(HttpRequestMessage request, Stream contentStream) { - if (request == null) - throw new ArgumentNullException(nameof(request)); - if (contentStream == null) - throw new ArgumentNullException(nameof(contentStream)); - - var streamContent = new StreamContent(contentStream); - request.Content = streamContent; + ArgumentNullException.ThrowIfNull(request); + ArgumentNullException.ThrowIfNull(contentStream); - return streamContent.Headers.ContentLength.Value; + request.Content = new StreamContent(contentStream); } /// - /// Sets the ContentLength property of the request and writes the specified content to the request's RequestStream. + /// Sets the ContentLength property of the request and writes the ContentLength property of the request and writes the specified content to the request's RequestStream. /// /// The WebRequest who's content is to be set. /// A MultipartFormDataContent object containing multipart/form-data content. - /// The number of bytes written to the requests RequestStream (and the new value of the request's ContentLength property. /// - /// Because this function sets the request's ContentLength property and writes content data into the requests's stream, + /// Because this function sets the request's ContentLength property and writes content data into the request's stream, /// it should be called one time maximum on a given request. /// - internal long SetRequestContent(HttpRequestMessage request, MultipartFormDataContent multipartContent) + internal void SetRequestContent(HttpRequestMessage request, MultipartFormDataContent multipartContent) { - if (request == null) - { - throw new ArgumentNullException(nameof(request)); - } + ArgumentNullException.ThrowIfNull(request); + ArgumentNullException.ThrowIfNull(multipartContent); - if (multipartContent == null) - { - throw new ArgumentNullException(nameof(multipartContent)); - } + // Content headers will be set by MultipartFormDataContent which will throw unless we clear them first + WebSession.ContentHeaders.Clear(); request.Content = multipartContent; - - return multipartContent.Headers.ContentLength.Value; } - internal long SetRequestContent(HttpRequestMessage request, IDictionary content) + internal void SetRequestContent(HttpRequestMessage request, IDictionary content) { - if (request == null) - throw new ArgumentNullException(nameof(request)); - if (content == null) - throw new ArgumentNullException(nameof(content)); + ArgumentNullException.ThrowIfNull(request); + ArgumentNullException.ThrowIfNull(content); string body = FormatDictionary(content); - return (SetRequestContent(request, body)); + SetRequestContent(request, body); } - internal void ParseLinkHeader(HttpResponseMessage response, System.Uri requestUri) + internal void ParseLinkHeader(HttpResponseMessage response) { - if (_relationLink == null) + Uri requestUri = response.RequestMessage.RequestUri; + if (_relationLink is null) { // Must ignore the case of relation links. See RFC 8288 (https://tools.ietf.org/html/rfc8288) _relationLink = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -1828,17 +1875,16 @@ internal void ParseLinkHeader(HttpResponseMessage response, System.Uri requestUr _relationLink.Clear(); } - // we only support the URL in angle brackets and `rel`, other attributes are ignored + // We only support the URL in angle brackets and `rel`, other attributes are ignored // user can still parse it themselves via the Headers property - const string pattern = "<(?.*?)>;\\s*rel=(\"?)(?.*?)\\1[^\\w -.]?"; - IEnumerable links; - if (response.Headers.TryGetValues("Link", out links)) + const string Pattern = "<(?.*?)>;\\s*rel=(?\")?(?(?(quoted).*?|[^,;]*))(?(quoted)\")"; + if (response.Headers.TryGetValues("Link", out IEnumerable links)) { foreach (string linkHeader in links) { - foreach (string link in linkHeader.Split(',')) + MatchCollection matchCollection = Regex.Matches(linkHeader, Pattern); + foreach (Match match in matchCollection) { - Match match = Regex.Match(link, pattern); if (match.Success) { string url = match.Groups["url"].Value; @@ -1859,14 +1905,11 @@ internal void ParseLinkHeader(HttpResponseMessage response, System.Uri requestUr /// /// The Field Name to use. /// The Field Value to use. - /// The > to update. + /// The to update. /// If true, collection types in will be enumerated. If false, collections will be treated as single value. - private void AddMultipartContent(object fieldName, object fieldValue, MultipartFormDataContent formData, bool enumerate) + private static void AddMultipartContent(object fieldName, object fieldValue, MultipartFormDataContent formData, bool enumerate) { - if (formData == null) - { - throw new ArgumentNullException("formDate"); - } + ArgumentNullException.ThrowIfNull(formData); // It is possible that the dictionary keys or values are PSObject wrapped depending on how the dictionary is defined and assigned. // Before processing the field name and value we need to ensure we are working with the base objects and not the PSObject wrappers. @@ -1902,9 +1945,9 @@ private void AddMultipartContent(object fieldName, object fieldValue, MultipartF // Treat the value as a collection and enumerate it if enumeration is true if (enumerate && fieldValue is IEnumerable items) { - foreach (var item in items) + foreach (object item in items) { - // Recruse, but do not enumerate the next level. IEnumerables will be treated as single values. + // Recurse, but do not enumerate the next level. IEnumerables will be treated as single values. AddMultipartContent(fieldName: fieldName, fieldValue: item, formData: formData, enumerate: false); } } @@ -1917,11 +1960,11 @@ private void AddMultipartContent(object fieldName, object fieldValue, MultipartF /// The Field Value to use for the private static StringContent GetMultipartStringContent(object fieldName, object fieldValue) { - var contentDisposition = new ContentDispositionHeaderValue("form-data"); - // .NET does not enclose field names in quotes, however, modern browsers and curl do. - contentDisposition.Name = "\"" + LanguagePrimitives.ConvertTo(fieldName) + "\""; + ContentDispositionHeaderValue contentDisposition = new("form-data"); + contentDisposition.Name = LanguagePrimitives.ConvertTo(fieldName); - var result = new StringContent(LanguagePrimitives.ConvertTo(fieldValue)); + // 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(fieldValue)); result.Headers.ContentDisposition = contentDisposition; return result; @@ -1934,11 +1977,10 @@ private static StringContent GetMultipartStringContent(object fieldName, object /// The to use for the private static StreamContent GetMultipartStreamContent(object fieldName, Stream stream) { - var contentDisposition = new ContentDispositionHeaderValue("form-data"); - // .NET does not enclose field names in quotes, however, modern browsers and curl do. - contentDisposition.Name = "\"" + LanguagePrimitives.ConvertTo(fieldName) + "\""; + ContentDispositionHeaderValue contentDisposition = new("form-data"); + contentDisposition.Name = LanguagePrimitives.ConvertTo(fieldName); - var result = new StreamContent(stream); + StreamContent result = new(stream); result.Headers.ContentDisposition = contentDisposition; result.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); @@ -1952,12 +1994,131 @@ private static StreamContent GetMultipartStreamContent(object fieldName, Stream /// The file to use for the private static StreamContent GetMultipartFileContent(object fieldName, FileInfo file) { - var result = GetMultipartStreamContent(fieldName: fieldName, stream: new FileStream(file.FullName, FileMode.Open)); - // .NET does not enclose field names in quotes, however, modern browsers and curl do. - result.Headers.ContentDisposition.FileName = "\"" + file.Name + "\""; + StreamContent result = GetMultipartStreamContent(fieldName: fieldName, stream: new FileStream(file.FullName, FileMode.Open)); + + result.Headers.ContentDisposition.FileName = file.Name; + result.Headers.ContentDisposition.FileNameStar = file.Name; return result; } + + private static string FormatErrorMessage(string error, string contentType) + { + string formattedError = null; + + try + { + if (ContentHelper.IsXml(contentType)) + { + XmlDocument doc = new(); + doc.LoadXml(error); + + XmlWriterSettings settings = new XmlWriterSettings + { + Indent = true, + NewLineOnAttributes = true, + OmitXmlDeclaration = true + }; + + if (doc.FirstChild is XmlDeclaration decl) + { + settings.Encoding = Encoding.GetEncoding(decl.Encoding); + } + + StringBuilder stringBuilder = new(); + using XmlWriter xmlWriter = XmlWriter.Create(stringBuilder, settings); + doc.Save(xmlWriter); + string xmlString = stringBuilder.ToString(); + + formattedError = Environment.NewLine + xmlString; + } + else if (ContentHelper.IsJson(contentType)) + { + JsonNode jsonNode = JsonNode.Parse(error); + JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = true }; + string jsonString = jsonNode.ToJsonString(options); + + formattedError = Environment.NewLine + jsonString; + } + } + catch + { + // Ignore errors + } + + if (string.IsNullOrEmpty(formattedError)) + { + // Remove HTML tags making it easier to read + formattedError = Regex.Replace(error, "<[^>]*>", string.Empty); + } + + return formattedError; + } + + // Returns true if the status code is one of the supported redirection codes. + private static bool IsRedirectCode(HttpStatusCode statusCode) => statusCode switch + { + HttpStatusCode.Found + or HttpStatusCode.Moved + or HttpStatusCode.MultipleChoices + or HttpStatusCode.PermanentRedirect + or HttpStatusCode.SeeOther + or HttpStatusCode.TemporaryRedirect => true, + _ => false + }; + + // Returns true if the status code is a redirection code and the action requires switching to GET on redirection. + // See https://learn.microsoft.com/en-us/dotnet/api/system.net.httpstatuscode + private static bool RequestRequiresForceGet(HttpStatusCode statusCode, HttpMethod requestMethod) => statusCode switch + { + HttpStatusCode.Found + or HttpStatusCode.Moved + or HttpStatusCode.MultipleChoices => requestMethod == HttpMethod.Post, + HttpStatusCode.SeeOther => requestMethod != HttpMethod.Get && requestMethod != HttpMethod.Head, + _ => false + }; + + // Returns true if the status code shows a server or client error and MaximumRetryCount > 0 + private static bool ShouldRetry(HttpStatusCode statusCode) => (int)statusCode switch + { + 304 or (>= 400 and <= 599) => true, + _ => false + }; + + private static HttpMethod GetHttpMethod(WebRequestMethod method) => method switch + { + WebRequestMethod.Default or WebRequestMethod.Get => HttpMethod.Get, + WebRequestMethod.Delete => HttpMethod.Delete, + WebRequestMethod.Head => HttpMethod.Head, + WebRequestMethod.Patch => HttpMethod.Patch, + WebRequestMethod.Post => HttpMethod.Post, + WebRequestMethod.Put => HttpMethod.Put, + WebRequestMethod.Options => HttpMethod.Options, + WebRequestMethod.Trace => HttpMethod.Trace, + _ => new HttpMethod(method.ToString().ToUpperInvariant()) + }; + #endregion Helper Methods } + + /// + /// Exception class for webcmdlets to enable returning HTTP error response. + /// + public sealed class HttpResponseException : HttpRequestException + { + /// + /// Initializes a new instance of the class. + /// + /// Message for the exception. + /// Response from the HTTP server. + public HttpResponseException(string message, HttpResponseMessage response) : base(message, inner: null, response.StatusCode) + { + Response = response; + } + + /// + /// HTTP error response. + /// + public HttpResponseMessage Response { get; } + } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebResponseObject.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebResponseObject.Common.cs index 98e7c397e45..ace84f480f9 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebResponseObject.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebResponseObject.Common.cs @@ -1,60 +1,41 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net.Http; using System.Text; +using System.Threading; namespace Microsoft.PowerShell.Commands { /// /// WebResponseObject. /// - public partial class WebResponseObject + public class WebResponseObject { #region Properties /// - /// Gets or protected sets the response body content. - /// - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public byte[] Content { get; protected set; } - - /// - /// Gets the response status code. + /// Gets or sets the BaseResponse property. /// - public int StatusCode - { - get { return (WebResponseHelper.GetStatusCode(BaseResponse)); } - } + public HttpResponseMessage BaseResponse { get; set; } /// - /// Gets the response status description. + /// Gets or protected sets the response body content. /// - public string StatusDescription - { - get { return (WebResponseHelper.GetStatusDescription(BaseResponse)); } - } + public byte[]? Content { get; protected set; } - private MemoryStream _rawContentStream; /// - /// Gets the response body content as a . + /// Gets the Headers property. /// - public MemoryStream RawContentStream - { - get { return (_rawContentStream); } - } + public Dictionary> Headers => _headers ??= WebResponseHelper.GetHeadersDictionary(BaseResponse); - /// - /// Gets the length (in bytes) of . - /// - public long RawContentLength - { - get { return (RawContentStream == null ? -1 : RawContentStream.Length); } - } + private Dictionary>? _headers; /// /// Gets or protected sets the full response content. @@ -62,104 +43,71 @@ public long RawContentLength /// /// Full response content, including the HTTP status line, headers, and body. /// - public string RawContent { get; protected set; } - - #endregion Properties - - #region Methods + public string? RawContent { get; protected set; } /// - /// Reads the response content from the web response. + /// Gets the length (in bytes) of . /// - private void InitializeContent() - { - this.Content = this.RawContentStream.ToArray(); - } - - private static bool IsPrintable(char c) - { - return (char.IsLetterOrDigit(c) || char.IsPunctuation(c) || char.IsSeparator(c) || char.IsSymbol(c) || char.IsWhiteSpace(c)); - } + public long RawContentLength => RawContentStream is null ? -1 : RawContentStream.Length; /// - /// Returns the string representation of this web response. + /// Gets or protected sets the response body content as a . /// - /// The string representation of this web response. - public sealed override string ToString() - { - char[] stringContent = System.Text.Encoding.ASCII.GetChars(Content); - for (int counter = 0; counter < stringContent.Length; counter++) - { - if (!IsPrintable(stringContent[counter])) - { - stringContent[counter] = '.'; - } - } - - return new string(stringContent); - } + public MemoryStream RawContentStream { get; protected set; } - #endregion Methods - } - - // TODO: Merge Partials + /// + /// Gets the RelationLink property. + /// + public Dictionary? RelationLink { get; internal set; } - /// - /// WebResponseObject. - /// - public partial class WebResponseObject - { - #region Properties + /// + /// Gets the response status code. + /// + public int StatusCode => WebResponseHelper.GetStatusCode(BaseResponse); /// - /// Gets or sets the BaseResponse property. + /// Gets the response status description. /// - public HttpResponseMessage BaseResponse { get; set; } + public string StatusDescription => WebResponseHelper.GetStatusDescription(BaseResponse); /// - /// Gets the Headers property. + /// Gets or sets the output file path. /// - public Dictionary> Headers - { - get - { - if (_headers == null) - { - _headers = WebResponseHelper.GetHeadersDictionary(BaseResponse); - } + public string? OutFile { get; internal set; } - return _headers; - } - } + #endregion Properties - private Dictionary> _headers = null; + #region Protected Fields /// - /// Gets the RelationLink property. + /// Time permitted between reads or Timeout.InfiniteTimeSpan for no timeout. /// - public Dictionary RelationLink { get; internal set; } + protected TimeSpan perReadTimeout; - #endregion + #endregion Protected Fields #region Constructors /// /// Initializes a new instance of the class. /// - /// - public WebResponseObject(HttpResponseMessage response) - : this(response, null) - { } + /// The Http response. + /// Time permitted between reads or Timeout.InfiniteTimeSpan for no timeout. + /// The cancellation token. + public WebResponseObject(HttpResponseMessage response, TimeSpan perReadTimeout, CancellationToken cancellationToken) : this(response, null, perReadTimeout, cancellationToken) { } /// /// Initializes a new instance of the class /// with the specified . /// - /// - /// - public WebResponseObject(HttpResponseMessage response, Stream contentStream) + /// Http response. + /// The http content stream. + /// Time permitted between reads or Timeout.InfiniteTimeSpan for no timeout. + /// The cancellation token. + public WebResponseObject(HttpResponseMessage response, Stream? contentStream, TimeSpan perReadTimeout, CancellationToken cancellationToken) { - SetResponse(response, contentStream); + this.perReadTimeout = perReadTimeout; + SetResponse(response, contentStream, cancellationToken); InitializeContent(); InitializeRawContent(response); } @@ -168,50 +116,86 @@ public WebResponseObject(HttpResponseMessage response, Stream contentStream) #region Methods + /// + /// Reads the response content from the web response. + /// + private void InitializeContent() + { + Content = RawContentStream.ToArray(); + } + private void InitializeRawContent(HttpResponseMessage baseResponse) { StringBuilder raw = ContentHelper.GetRawContentHeader(baseResponse); // Use ASCII encoding for the RawContent visual view of the content. - if (Content.Length > 0) + if (Content?.Length > 0) { - raw.Append(this.ToString()); + raw.Append(ToString()); } - this.RawContent = raw.ToString(); + RawContent = raw.ToString(); } - private void SetResponse(HttpResponseMessage response, Stream contentStream) + private static bool IsPrintable(char c) => char.IsLetterOrDigit(c) + || char.IsPunctuation(c) + || char.IsSeparator(c) + || char.IsSymbol(c) + || char.IsWhiteSpace(c); + + [MemberNotNull(nameof(RawContentStream))] + [MemberNotNull(nameof(BaseResponse))] + private void SetResponse(HttpResponseMessage response, Stream? contentStream, CancellationToken cancellationToken) { - if (response == null) { throw new ArgumentNullException(nameof(response)); } + ArgumentNullException.ThrowIfNull(response); BaseResponse = response; - MemoryStream ms = contentStream as MemoryStream; - if (ms != null) + if (contentStream is MemoryStream ms) { - _rawContentStream = ms; + RawContentStream = ms; } else { - Stream st = contentStream; - if (contentStream == null) - { - st = StreamHelper.GetResponseStream(response); - } + Stream st = contentStream ?? StreamHelper.GetResponseStream(response, cancellationToken); - long contentLength = response.Content.Headers.ContentLength.Value; + long contentLength = response.Content.Headers.ContentLength.GetValueOrDefault(); if (contentLength <= 0) { contentLength = StreamHelper.DefaultReadBuffer; } int initialCapacity = (int)Math.Min(contentLength, StreamHelper.DefaultReadBuffer); - _rawContentStream = new WebResponseContentMemoryStream(st, initialCapacity, null); + RawContentStream = new WebResponseContentMemoryStream(st, initialCapacity, cmdlet: null, response.Content.Headers.ContentLength.GetValueOrDefault(), perReadTimeout, cancellationToken); + } + + // Set the position of the content stream to the beginning + RawContentStream.Position = 0; + } + + /// + /// Returns the string representation of this web response. + /// + /// The string representation of this web response. + public sealed override string ToString() + { + if (Content is null) + { + return string.Empty; } - // set the position of the content stream to the beginning - _rawContentStream.Position = 0; + + char[] stringContent = Encoding.ASCII.GetChars(Content); + for (int counter = 0; counter < stringContent.Length; counter++) + { + if (!IsPrintable(stringContent[counter])) + { + stringContent[counter] = '.'; + } + } + + return new string(stringContent); } - #endregion + + #endregion Methods } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs index 0af97b061a9..82e1277e00c 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs @@ -32,13 +32,13 @@ public class ConvertFromJsonCommand : Cmdlet /// /// Returned data structure is a Hashtable instead a CustomPSObject. /// - [Parameter()] + [Parameter] public SwitchParameter AsHashtable { get; set; } /// /// Gets or sets the maximum depth the JSON input is allowed to have. By default, it is 1024. /// - [Parameter()] + [Parameter] [ValidateRange(ValidateRangeKind.Positive)] public int Depth { get; set; } = 1024; @@ -49,6 +49,12 @@ public class ConvertFromJsonCommand : Cmdlet [Parameter] public SwitchParameter NoEnumerate { get; set; } + /// + /// Gets or sets the switch to control how DateTime values are to be parsed as a dotnet object. + /// + [Parameter] + public JsonDateKind DateKind { get; set; } = JsonDateKind.Default; + #endregion parameters #region overrides @@ -86,7 +92,7 @@ protected override void EndProcessing() catch (ArgumentException) { // The first input string does not represent a complete Json Syntax. - // Hence consider the the entire input as a single Json content. + // Hence consider the entire input as a single Json content. } if (successfullyConverted) @@ -113,7 +119,7 @@ protected override void EndProcessing() private bool ConvertFromJsonHelper(string input) { ErrorRecord error = null; - object result = JsonObject.ConvertFromJson(input, AsHashtable.IsPresent, Depth, out error); + object result = JsonObject.ConvertFromJson(input, AsHashtable.IsPresent, Depth, DateKind, out error); if (error != null) { 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 d15303557eb..eb8c3e3cc08 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommand.cs @@ -16,7 +16,8 @@ namespace Microsoft.PowerShell.Commands /// This command converts an object to a Json string representation. /// [Cmdlet(VerbsData.ConvertTo, "Json", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096925", RemotingCapability = RemotingCapability.None)] - public class ConvertToJsonCommand : PSCmdlet + [OutputType(typeof(string))] + public class ConvertToJsonCommand : PSCmdlet, IDisposable { /// /// Gets or sets the InputObject property. @@ -27,15 +28,13 @@ public class ConvertToJsonCommand : PSCmdlet private int _depth = 2; - private const int maxDepthAllowed = 100; - private readonly CancellationTokenSource _cancellationSource = new(); /// /// Gets or sets the Depth property. /// [Parameter] - [ValidateRange(1, int.MaxValue)] + [ValidateRange(0, 100)] public int Depth { get { return _depth; } @@ -78,21 +77,28 @@ public int Depth public StringEscapeHandling EscapeHandling { get; set; } = StringEscapeHandling.Default; /// - /// Prerequisite checks. + /// IDisposable implementation, dispose of any disposable resources created by the cmdlet. /// - protected override void BeginProcessing() + public void Dispose() { - if (_depth > maxDepthAllowed) + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// + /// Implementation of IDisposable for both manual Dispose() and finalizer-called disposal of resources. + /// + /// + /// Specified as true when Dispose() was called, false if this is called from the finalizer. + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) { - string errorMessage = StringUtil.Format(WebCmdletStrings.ReachedMaximumDepthAllowed, maxDepthAllowed); - ThrowTerminatingError(new ErrorRecord( - new InvalidOperationException(errorMessage), - "ReachedMaximumDepthAllowed", - ErrorCategory.InvalidOperation, - null)); + _cancellationSource.Dispose(); } } - + private readonly List _inputObjects = new(); /// diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/HttpKnownHeaderNames.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/HttpKnownHeaderNames.cs index 80d07a54ddf..6571696e5bf 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/HttpKnownHeaderNames.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/HttpKnownHeaderNames.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System; using System.Collections.Generic; @@ -11,14 +13,22 @@ internal static class HttpKnownHeaderNames #region Known_HTTP_Header_Names // Known HTTP Header Names. - // List comes from corefx/System/Net/HttpKnownHeaderNames.cs + // List comes from https://github.com/dotnet/runtime/blob/51a8dd5323721b363e61069575511f783e7ea6d3/src/libraries/Common/src/System/Net/HttpKnownHeaderNames.cs public const string Accept = "Accept"; public const string AcceptCharset = "Accept-Charset"; public const string AcceptEncoding = "Accept-Encoding"; public const string AcceptLanguage = "Accept-Language"; + public const string AcceptPatch = "Accept-Patch"; public const string AcceptRanges = "Accept-Ranges"; + public const string AccessControlAllowCredentials = "Access-Control-Allow-Credentials"; + public const string AccessControlAllowHeaders = "Access-Control-Allow-Headers"; + public const string AccessControlAllowMethods = "Access-Control-Allow-Methods"; + public const string AccessControlAllowOrigin = "Access-Control-Allow-Origin"; + public const string AccessControlExposeHeaders = "Access-Control-Expose-Headers"; + public const string AccessControlMaxAge = "Access-Control-Max-Age"; public const string Age = "Age"; public const string Allow = "Allow"; + public const string AltSvc = "Alt-Svc"; public const string Authorization = "Authorization"; public const string CacheControl = "Cache-Control"; public const string Connection = "Connection"; @@ -29,6 +39,7 @@ internal static class HttpKnownHeaderNames public const string ContentLocation = "Content-Location"; public const string ContentMD5 = "Content-MD5"; public const string ContentRange = "Content-Range"; + public const string ContentSecurityPolicy = "Content-Security-Policy"; public const string ContentType = "Content-Type"; public const string Cookie = "Cookie"; public const string Cookie2 = "Cookie2"; @@ -45,6 +56,7 @@ internal static class HttpKnownHeaderNames public const string IfUnmodifiedSince = "If-Unmodified-Since"; public const string KeepAlive = "Keep-Alive"; public const string LastModified = "Last-Modified"; + public const string Link = "Link"; public const string Location = "Location"; public const string MaxForwards = "Max-Forwards"; public const string Origin = "Origin"; @@ -53,6 +65,7 @@ internal static class HttpKnownHeaderNames public const string ProxyAuthenticate = "Proxy-Authenticate"; public const string ProxyAuthorization = "Proxy-Authorization"; public const string ProxyConnection = "Proxy-Connection"; + public const string PublicKeyPins = "Public-Key-Pins"; public const string Range = "Range"; public const string Referer = "Referer"; // NB: The spelling-mistake "Referer" for "Referrer" must be matched. public const string RetryAfter = "Retry-After"; @@ -64,45 +77,49 @@ internal static class HttpKnownHeaderNames public const string Server = "Server"; public const string SetCookie = "Set-Cookie"; public const string SetCookie2 = "Set-Cookie2"; + public const string StrictTransportSecurity = "Strict-Transport-Security"; public const string TE = "TE"; + public const string TSV = "TSV"; public const string Trailer = "Trailer"; public const string TransferEncoding = "Transfer-Encoding"; public const string Upgrade = "Upgrade"; + public const string UpgradeInsecureRequests = "Upgrade-Insecure-Requests"; public const string UserAgent = "User-Agent"; public const string Vary = "Vary"; public const string Via = "Via"; public const string WWWAuthenticate = "WWW-Authenticate"; public const string Warning = "Warning"; public const string XAspNetVersion = "X-AspNet-Version"; + public const string XContentDuration = "X-Content-Duration"; + public const string XContentTypeOptions = "X-Content-Type-Options"; + public const string XFrameOptions = "X-Frame-Options"; + public const string XMSEdgeRef = "X-MSEdge-Ref"; public const string XPoweredBy = "X-Powered-By"; + public const string XRequestID = "X-Request-ID"; + public const string XUACompatible = "X-UA-Compatible"; #endregion Known_HTTP_Header_Names - private static HashSet s_contentHeaderSet = null; + private static readonly HashSet s_contentHeaderSet; - internal static HashSet ContentHeaders + static HttpKnownHeaderNames() { - get - { - if (s_contentHeaderSet == null) - { - s_contentHeaderSet = new HashSet(StringComparer.OrdinalIgnoreCase); - - s_contentHeaderSet.Add(HttpKnownHeaderNames.Allow); - s_contentHeaderSet.Add(HttpKnownHeaderNames.ContentDisposition); - s_contentHeaderSet.Add(HttpKnownHeaderNames.ContentEncoding); - s_contentHeaderSet.Add(HttpKnownHeaderNames.ContentLanguage); - s_contentHeaderSet.Add(HttpKnownHeaderNames.ContentLength); - s_contentHeaderSet.Add(HttpKnownHeaderNames.ContentLocation); - s_contentHeaderSet.Add(HttpKnownHeaderNames.ContentMD5); - s_contentHeaderSet.Add(HttpKnownHeaderNames.ContentRange); - s_contentHeaderSet.Add(HttpKnownHeaderNames.ContentType); - s_contentHeaderSet.Add(HttpKnownHeaderNames.Expires); - s_contentHeaderSet.Add(HttpKnownHeaderNames.LastModified); - } + // Thread-safe initialization. + s_contentHeaderSet = new HashSet(StringComparer.OrdinalIgnoreCase); - return s_contentHeaderSet; - } + s_contentHeaderSet.Add(HttpKnownHeaderNames.Allow); + s_contentHeaderSet.Add(HttpKnownHeaderNames.ContentDisposition); + s_contentHeaderSet.Add(HttpKnownHeaderNames.ContentEncoding); + s_contentHeaderSet.Add(HttpKnownHeaderNames.ContentLanguage); + s_contentHeaderSet.Add(HttpKnownHeaderNames.ContentLength); + s_contentHeaderSet.Add(HttpKnownHeaderNames.ContentLocation); + s_contentHeaderSet.Add(HttpKnownHeaderNames.ContentMD5); + s_contentHeaderSet.Add(HttpKnownHeaderNames.ContentRange); + s_contentHeaderSet.Add(HttpKnownHeaderNames.ContentType); + s_contentHeaderSet.Add(HttpKnownHeaderNames.Expires); + s_contentHeaderSet.Add(HttpKnownHeaderNames.LastModified); } + + internal static HashSet ContentHeaders => s_contentHeaderSet; } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/InvokeWebRequestCommand.CoreClr.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/InvokeWebRequestCommand.CoreClr.cs index dafcf3b4295..0bcafcf2964 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/InvokeWebRequestCommand.CoreClr.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/InvokeWebRequestCommand.CoreClr.cs @@ -1,10 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System; using System.IO; using System.Management.Automation; using System.Net.Http; +using System.Threading; namespace Microsoft.PowerShell.Commands { @@ -13,6 +16,7 @@ namespace Microsoft.PowerShell.Commands /// This command makes an HTTP or HTTPS request to a web server and returns the results. /// [Cmdlet(VerbsLifecycle.Invoke, "WebRequest", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097126", DefaultParameterSetName = "StandardMethod")] + [OutputType(typeof(BasicHtmlWebResponseObject))] public class InvokeWebRequestCommand : WebRequestPSCmdlet { #region Virtual Method Overrides @@ -22,7 +26,7 @@ public class InvokeWebRequestCommand : WebRequestPSCmdlet /// public InvokeWebRequestCommand() : base() { - this._parseRelLink = true; + _parseRelLink = true; } /// @@ -31,18 +35,27 @@ public InvokeWebRequestCommand() : base() /// internal override void ProcessResponse(HttpResponseMessage response) { - if (response == null) { throw new ArgumentNullException(nameof(response)); } + ArgumentNullException.ThrowIfNull(response); + TimeSpan perReadTimeout = ConvertTimeoutSecondsToTimeSpan(OperationTimeoutSeconds); + Stream responseStream = StreamHelper.GetResponseStream(response, _cancelToken.Token); + string outFilePath = WebResponseHelper.GetOutFilePath(response, _qualifiedOutFile); - Stream responseStream = StreamHelper.GetResponseStream(response); if (ShouldWriteToPipeline) { - // creating a MemoryStream wrapper to response stream here to support IsStopping. - responseStream = new WebResponseContentMemoryStream(responseStream, StreamHelper.ChunkSize, this); - WebResponseObject ro = WebResponseObjectFactory.GetResponseObject(response, responseStream, this.Context); + // Creating a MemoryStream wrapper to response stream here to support IsStopping. + responseStream = new WebResponseContentMemoryStream( + responseStream, + StreamHelper.ChunkSize, + this, + response.Content.Headers.ContentLength.GetValueOrDefault(), + perReadTimeout, + _cancelToken.Token); + WebResponseObject ro = WebResponseHelper.IsText(response) ? new BasicHtmlWebResponseObject(response, responseStream, perReadTimeout, _cancelToken.Token) : new WebResponseObject(response, responseStream, perReadTimeout, _cancelToken.Token); ro.RelationLink = _relationLink; + ro.OutFile = outFilePath; WriteObject(ro); - // use the rawcontent stream from WebResponseObject for further + // Use the rawcontent stream from WebResponseObject for further // processing of the stream. This is need because WebResponse's // stream can be used only once. responseStream = ro.RawContentStream; @@ -51,7 +64,11 @@ internal override void ProcessResponse(HttpResponseMessage response) if (ShouldSaveToOutFile) { - StreamHelper.SaveStreamToFile(responseStream, QualifiedOutFile, this, _cancelToken.Token); + WriteVerbose($"File Name: {Path.GetFileName(outFilePath)}"); + + // ContentLength is always the partial length, while ContentRange is the full length + // Without Request.Range set, ContentRange is null and partial length (ContentLength) equals to full length + StreamHelper.SaveStreamToFile(responseStream, outFilePath, this, response.Content.Headers.ContentRange?.Length.GetValueOrDefault() ?? response.Content.Headers.ContentLength.GetValueOrDefault(), perReadTimeout, _cancelToken.Token); } } 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 226a981f8a2..6e9f6bdf98e 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 @@ -1,69 +1,65 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System; using System.Net; namespace Microsoft.PowerShell.Commands { - internal class WebProxy : IWebProxy + internal class WebProxy : IWebProxy, IEquatable { - private ICredentials _credentials; + private ICredentials? _credentials; private readonly Uri _proxyAddress; internal WebProxy(Uri address) { - if (address == null) - { - throw new ArgumentNullException(nameof(address)); - } + ArgumentNullException.ThrowIfNull(address); _proxyAddress = address; } - public ICredentials Credentials + public override bool Equals(object? obj) => Equals(obj as WebProxy); + + public override int GetHashCode() => HashCode.Combine(_proxyAddress, _credentials, BypassProxyOnLocal); + + public bool Equals(WebProxy? other) { - get { return _credentials; } + if (other is null) + { + return false; + } - set { _credentials = value; } + // _proxyAddress cannot be null as it is set in the constructor + return other._credentials == _credentials + && _proxyAddress.Equals(other._proxyAddress) + && BypassProxyOnLocal == other.BypassProxyOnLocal; } - internal bool BypassProxyOnLocal + public ICredentials? Credentials { - get; set; + get => _credentials; + + set => _credentials = value; } + internal bool BypassProxyOnLocal { get; set; } + internal bool UseDefaultCredentials { - get - { - return _credentials == CredentialCache.DefaultCredentials; - } + get => _credentials == CredentialCache.DefaultCredentials; - set - { - _credentials = value ? CredentialCache.DefaultCredentials : null; - } + set => _credentials = value ? CredentialCache.DefaultCredentials : null; } public Uri GetProxy(Uri destination) { - if (destination == null) - { - throw new ArgumentNullException(nameof(destination)); - } + ArgumentNullException.ThrowIfNull(destination); - if (destination.IsLoopback) - { - return destination; - } - - return _proxyAddress; + return destination.IsLoopback ? destination : _proxyAddress; } - public bool IsBypassed(Uri host) - { - return host.IsLoopback; - } + public bool IsBypassed(Uri host) => host.IsLoopback; } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseHelper.CoreClr.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseHelper.CoreClr.cs index dc2f734d6bd..377a7e56265 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseHelper.CoreClr.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseHelper.CoreClr.cs @@ -1,20 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Net.Http; namespace Microsoft.PowerShell.Commands { internal static class WebResponseHelper { - internal static string GetCharacterSet(HttpResponseMessage response) - { - string characterSet = response.Content.Headers.ContentType.CharSet; - return characterSet; - } + internal static string? GetCharacterSet(HttpResponseMessage response) => response.Content.Headers.ContentType?.CharSet; internal static Dictionary> GetHeadersDictionary(HttpResponseMessage response) { @@ -27,7 +26,7 @@ internal static Dictionary> GetHeadersDictionary(Htt // HttpResponseMessage.Content.Headers. The remaining headers are in HttpResponseMessage.Headers. // The keys in both should be unique with no duplicates between them. // Added for backwards compatibility with PowerShell 5.1 and earlier. - if (response.Content != null) + if (response.Content is not null) { foreach (var entry in response.Content.Headers) { @@ -38,29 +37,24 @@ internal static Dictionary> GetHeadersDictionary(Htt return headers; } - internal static string GetProtocol(HttpResponseMessage response) + internal static string GetOutFilePath(HttpResponseMessage response, string qualifiedOutFile) { - string protocol = string.Format(CultureInfo.InvariantCulture, - "HTTP/{0}", response.Version); - return protocol; - } + // Get file name from last segment of Uri + string? lastUriSegment = System.Net.WebUtility.UrlDecode(response.RequestMessage?.RequestUri?.Segments[^1]); - internal static int GetStatusCode(HttpResponseMessage response) - { - int statusCode = (int)response.StatusCode; - return statusCode; + return Directory.Exists(qualifiedOutFile) ? Path.Join(qualifiedOutFile, lastUriSegment) : qualifiedOutFile; } - internal static string GetStatusDescription(HttpResponseMessage response) - { - string statusDescription = response.StatusCode.ToString(); - return statusDescription; - } + internal static string GetProtocol(HttpResponseMessage response) => string.Create(CultureInfo.InvariantCulture, $"HTTP/{response.Version}"); + + internal static int GetStatusCode(HttpResponseMessage response) => (int)response.StatusCode; + + internal static string GetStatusDescription(HttpResponseMessage response) => response.StatusCode.ToString(); internal static bool IsText(HttpResponseMessage response) { // ContentType may not exist in response header. - string contentType = response.Content.Headers.ContentType?.MediaType; + string? contentType = ContentHelper.GetContentType(response); return ContentHelper.IsText(contentType); } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseObjectFactory.CoreClr.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseObjectFactory.CoreClr.cs deleted file mode 100644 index c4371515fe7..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseObjectFactory.CoreClr.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.IO; -using System.Management.Automation; -using System.Net.Http; - -namespace Microsoft.PowerShell.Commands -{ - internal static class WebResponseObjectFactory - { - internal static WebResponseObject GetResponseObject(HttpResponseMessage response, Stream responseStream, ExecutionContext executionContext) - { - WebResponseObject output; - if (WebResponseHelper.IsText(response)) - { - output = new BasicHtmlWebResponseObject(response, responseStream); - } - else - { - output = new WebResponseObject(response, responseStream); - } - - return (output); - } - } -} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/FormObject.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/FormObject.cs index b8f7d252711..5ac4adfbb64 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/FormObject.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/FormObject.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System.Collections.Generic; namespace Microsoft.PowerShell.Commands @@ -11,22 +13,22 @@ namespace Microsoft.PowerShell.Commands public class FormObject { /// - /// Gets or private sets the Id property. + /// Gets the Id property. /// public string Id { get; } /// - /// Gets or private sets the Method property. + /// Gets the Method property. /// public string Method { get; } /// - /// Gets or private sets the Action property. + /// Gets the Action property. /// public string Action { get; } /// - /// Gets or private sets the Fields property. + /// Gets the Fields property. /// public Dictionary Fields { get; } @@ -46,8 +48,7 @@ public FormObject(string id, string method, string action) internal void AddField(string key, string value) { - string test; - if (key != null && !Fields.TryGetValue(key, out test)) + if (key is not null && !Fields.TryGetValue(key, out string? _)) { Fields[key] = value; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/FormObjectCollection.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/FormObjectCollection.cs index 9072e431cae..5f923558185 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/FormObjectCollection.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/FormObjectCollection.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System; using System.Collections.ObjectModel; @@ -16,11 +18,11 @@ public class FormObjectCollection : Collection /// /// /// - public FormObject this[string key] + public FormObject? this[string key] { get { - FormObject form = null; + FormObject? form = null; foreach (FormObject f in this) { if (string.Equals(key, f.Id, StringComparison.OrdinalIgnoreCase)) @@ -30,7 +32,7 @@ public FormObject this[string key] } } - return (form); + return form; } } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonDateKind.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonDateKind.cs new file mode 100644 index 00000000000..2fed27128a6 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonDateKind.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +namespace Microsoft.PowerShell.Commands +{ + /// + /// Enums for ConvertFrom-Json -DateKind parameter. + /// + public enum JsonDateKind + { + /// + /// DateTime values are returned as a DateTime with the Kind representing the time zone in the raw string. + /// + Default, + + /// + /// DateTime values are returned as the Local kind representation of the value. + /// + Local, + + /// + /// DateTime values are returned as the UTC kind representation of the value. + /// + Utc, + + /// + /// DateTime values are returned as a DateTimeOffset value preserving the timezone information. + /// + Offset, + + /// + /// DateTime values are returned as raw strings. + /// + String, + } +} 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 9f1d2b1c8a2..33465683153 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.Management.Automation; using System.Management.Automation.Language; +using System.Numerics; using System.Reflection; using System.Text.RegularExpressions; using System.Threading; @@ -98,7 +99,7 @@ public ConvertToJsonContext( } } - private class DuplicateMemberHashSet : HashSet + private sealed class DuplicateMemberHashSet : HashSet { public DuplicateMemberHashSet(int capacity) : base(capacity, StringComparer.OrdinalIgnoreCase) @@ -151,35 +152,68 @@ public static object ConvertFromJson(string input, bool returnHashtable, out Err /// if the parameter is true. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Preferring Json over JSON")] public static object ConvertFromJson(string input, bool returnHashtable, int? maxDepth, out ErrorRecord error) + => ConvertFromJson(input, returnHashtable, maxDepth, jsonDateKind: JsonDateKind.Default, out error); + + /// + /// Convert a JSON string back to an object of type or + /// depending on parameter . + /// + /// The JSON text to convert. + /// True if the result should be returned as a + /// instead of a . + /// The max depth allowed when deserializing the json input. Set to null for no maximum. + /// Controls how DateTime values are to be converted. + /// An error record if the conversion failed. + /// A or a + /// if the parameter is true. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Preferring Json over JSON")] + internal static object ConvertFromJson(string input, bool returnHashtable, int? maxDepth, JsonDateKind jsonDateKind, out ErrorRecord error) { - if (input == null) + ArgumentNullException.ThrowIfNull(input); + + DateParseHandling dateParseHandling; + DateTimeZoneHandling dateTimeZoneHandling; + switch (jsonDateKind) { - throw new ArgumentNullException(nameof(input)); + case JsonDateKind.Default: + dateParseHandling = DateParseHandling.DateTime; + dateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind; + break; + + case JsonDateKind.Local: + dateParseHandling = DateParseHandling.DateTime; + dateTimeZoneHandling = DateTimeZoneHandling.Local; + break; + + case JsonDateKind.Utc: + dateParseHandling = DateParseHandling.DateTime; + dateTimeZoneHandling = DateTimeZoneHandling.Utc; + break; + + case JsonDateKind.Offset: + dateParseHandling = DateParseHandling.DateTimeOffset; + dateTimeZoneHandling = DateTimeZoneHandling.Unspecified; + break; + + case JsonDateKind.String: + dateParseHandling = DateParseHandling.None; + dateTimeZoneHandling = DateTimeZoneHandling.Unspecified; + break; + + default: + throw new ArgumentException($"Unknown JsonDateKind value requested '{jsonDateKind}'"); } error = null; try { - // JsonConvert.DeserializeObject does not throw an exception when an invalid Json array is passed. - // This issue is being tracked by https://github.com/JamesNK/Newtonsoft.Json/issues/1930. - // To work around this, we need to identify when input is a Json array, and then try to parse it via JArray.Parse(). - - // If input starts with '[' (ignoring white spaces). - if (Regex.Match(input, @"^\s*\[").Success) - { - // JArray.Parse() will throw a JsonException if the array is invalid. - // This will be caught by the catch block below, and then throw an - // ArgumentException - this is done to have same behavior as the JavaScriptSerializer. - JArray.Parse(input); - - // Please note that if the Json array is valid, we don't do anything, - // we just continue the deserialization. - } - var obj = JsonConvert.DeserializeObject( input, new JsonSerializerSettings { + DateParseHandling = dateParseHandling, + DateTimeZoneHandling = dateTimeZoneHandling, + // This TypeNameHandling setting is required to be secure. TypeNameHandling = TypeNameHandling.None, MetadataPropertyHandling = MetadataPropertyHandling.Ignore, @@ -344,7 +378,7 @@ private static ICollection PopulateFromJArray(JArray list, out ErrorReco private static Hashtable PopulateHashTableFromJDictionary(JObject entries, out ErrorRecord error) { error = null; - Hashtable result = new(entries.Count); + OrderedHashtable result = new(entries.Count); foreach (var entry in entries) { // Case sensitive duplicates should normally not occur since JsonConvert.DeserializeObject @@ -530,7 +564,8 @@ private static object ProcessValue(object obj, int currentDepth, in ConvertToJso || obj is Uri || obj is double || obj is float - || obj is decimal) + || obj is decimal + || obj is BigInteger) { rv = obj; } @@ -542,7 +577,7 @@ private static object ProcessValue(object obj, int currentDepth, in ConvertToJso { Type t = obj.GetType(); - if (t.IsPrimitive) + if (t.IsPrimitive || (t.IsEnum && ExperimentalFeature.IsEnabled(ExperimentalFeature.PSSerializeJSONLongEnumAsNumber))) { rv = obj; } @@ -551,7 +586,7 @@ private static object ProcessValue(object obj, int currentDepth, in ConvertToJso // Win8:378368 Enums based on System.Int64 or System.UInt64 are not JSON-serializable // because JavaScript does not support the necessary precision. Type enumUnderlyingType = Enum.GetUnderlyingType(obj.GetType()); - if (enumUnderlyingType.Equals(typeof(Int64)) || enumUnderlyingType.Equals(typeof(UInt64))) + if (enumUnderlyingType.Equals(typeof(long)) || enumUnderlyingType.Equals(typeof(ulong))) { rv = obj.ToString(); } @@ -590,15 +625,13 @@ private static object ProcessValue(object obj, int currentDepth, in ConvertToJso } else { - IDictionary dict = obj as IDictionary; - if (dict != null) + if (obj is IDictionary dict) { rv = ProcessDictionary(dict, currentDepth, in context); } else { - IEnumerable enumerable = obj as IEnumerable; - if (enumerable != null) + if (obj is IEnumerable enumerable) { rv = ProcessEnumerable(enumerable, currentDepth, in context); } @@ -645,9 +678,8 @@ private static object AddPsProperties(object psObj, object obj, int depth, bool } bool wasDictionary = true; - IDictionary dict = obj as IDictionary; - if (dict == null) + if (obj is not IDictionary dict) { wasDictionary = false; dict = new Dictionary(); @@ -678,6 +710,12 @@ private static object AddPsProperties(object psObj, object obj, int depth, bool /// The context for the operation. private static void AppendPsProperties(PSObject psObj, IDictionary receiver, int depth, bool isCustomObject, in ConvertToJsonContext context) { + // if the psObj is a DateTime or String type, we don't serialize any extended or adapted properties + if (psObj.BaseObject is string || psObj.BaseObject is DateTime) + { + return; + } + // serialize only Extended and Adapted properties.. PSMemberInfoCollection srcPropertiesToSearch = new PSMemberInfoIntegratingCollection(psObj, diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/PSUserAgent.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/PSUserAgent.cs index 2ea29e73fa3..1a19d6b0457 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/PSUserAgent.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/PSUserAgent.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System; using System.Globalization; using System.Management.Automation; @@ -14,112 +16,39 @@ namespace Microsoft.PowerShell.Commands /// public static class PSUserAgent { - private static string s_windowsUserAgent; + private static string? s_windowsUserAgent; - internal static string UserAgent - { - get - { - // format the user-agent string from the various component parts - string userAgent = string.Format(CultureInfo.InvariantCulture, - "{0} ({1}; {2}; {3}) {4}", - Compatibility, PlatformName, OS, Culture, App); - return (userAgent); - } - } + // Format the user-agent string from the various component parts + internal static string UserAgent => string.Create(CultureInfo.InvariantCulture, $"{Compatibility} ({PlatformName}; {OS}; {Culture}) {App}"); /// /// Useragent string for InternetExplorer (9.0). /// - public static string InternetExplorer - { - get - { - // format the user-agent string from the various component parts - string userAgent = string.Format(CultureInfo.InvariantCulture, - "{0} (compatible; MSIE 9.0; {1}; {2}; {3})", - Compatibility, PlatformName, OS, Culture); - return (userAgent); - } - } + public static string InternetExplorer => string.Create(CultureInfo.InvariantCulture, $"{Compatibility} (compatible; MSIE 9.0; {PlatformName}; {OS}; {Culture})"); /// /// Useragent string for Firefox (4.0). /// - public static string FireFox - { - get - { - // format the user-agent string from the various component parts - string userAgent = string.Format(CultureInfo.InvariantCulture, - "{0} ({1}; {2}; {3}) Gecko/20100401 Firefox/4.0", - Compatibility, PlatformName, OS, Culture); - return (userAgent); - } - } + public static string FireFox => string.Create(CultureInfo.InvariantCulture, $"{Compatibility} ({PlatformName}; {OS}; {Culture}) Gecko/20100401 Firefox/4.0"); /// /// Useragent string for Chrome (7.0). /// - public static string Chrome - { - get - { - // format the user-agent string from the various component parts - string userAgent = string.Format(CultureInfo.InvariantCulture, - "{0} ({1}; {2}; {3}) AppleWebKit/534.6 (KHTML, like Gecko) Chrome/7.0.500.0 Safari/534.6", - Compatibility, PlatformName, OS, Culture); - return (userAgent); - } - } + public static string Chrome => string.Create(CultureInfo.InvariantCulture, $"{Compatibility} ({PlatformName}; {OS}; {Culture}) AppleWebKit/534.6 (KHTML, like Gecko) Chrome/7.0.500.0 Safari/534.6"); /// /// Useragent string for Opera (9.0). /// - public static string Opera - { - get - { - // format the user-agent string from the various component parts - string userAgent = string.Format(CultureInfo.InvariantCulture, - "Opera/9.70 ({0}; {1}; {2}) Presto/2.2.1", - PlatformName, OS, Culture); - return (userAgent); - } - } + public static string Opera => string.Create(CultureInfo.InvariantCulture, $"Opera/9.70 ({PlatformName}; {OS}; {Culture}) Presto/2.2.1"); /// /// Useragent string for Safari (5.0). /// - public static string Safari - { - get - { - // format the user-agent string from the various component parts - string userAgent = string.Format(CultureInfo.InvariantCulture, - "{0} ({1}; {2}; {3}) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16", - Compatibility, PlatformName, OS, Culture); - return (userAgent); - } - } + public static string Safari => string.Create(CultureInfo.InvariantCulture, $"{Compatibility} ({PlatformName}; {OS}; {Culture}) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16"); - internal static string Compatibility - { - get - { - return ("Mozilla/5.0"); - } - } + internal static string Compatibility => "Mozilla/5.0"; - internal static string App - { - get - { - string app = string.Format(CultureInfo.InvariantCulture, - "PowerShell/{0}", PSVersionInfo.PSVersion); - return (app); - } - } + internal static string App => string.Create(CultureInfo.InvariantCulture, $"PowerShell/{PSVersionInfo.PSVersion}"); internal static string PlatformName { @@ -127,10 +56,10 @@ internal static string PlatformName { if (Platform.IsWindows) { - // only generate the windows user agent once - if (s_windowsUserAgent == null) + // Only generate the windows user agent once + if (s_windowsUserAgent is null) { - // find the version in the windows operating system description + // Find the version in the windows operating system description Regex pattern = new(@"\d+(\.\d+)+"); string versionText = pattern.Match(OS).Value; Version windowsPlatformversion = new(versionText); @@ -149,27 +78,15 @@ internal static string PlatformName } else { - // unknown/unsupported platform + // Unknown/unsupported platform Diagnostics.Assert(false, "Unable to determine Operating System Platform"); return string.Empty; } } } - internal static string OS - { - get - { - return RuntimeInformation.OSDescription.Trim(); - } - } + internal static string OS => RuntimeInformation.OSDescription.Trim(); - internal static string Culture - { - get - { - return (CultureInfo.CurrentCulture.Name); - } - } + internal static string Culture => CultureInfo.CurrentCulture.Name; } } 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 43b625311b9..52ff515b0b2 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs @@ -1,9 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System; +using System.Buffers; using System.IO; -using System.IO.Compression; using System.Management.Automation; using System.Management.Automation.Internal; using System.Net.Http; @@ -24,66 +26,46 @@ internal class WebResponseContentMemoryStream : MemoryStream { #region Data + private readonly long? _contentLength; private readonly Stream _originalStreamToProxy; + private readonly Cmdlet? _ownerCmdlet; + private readonly CancellationToken _cancellationToken; + private readonly TimeSpan _perReadTimeout; private bool _isInitialized = false; - private readonly Cmdlet _ownerCmdlet; - #endregion + #endregion Data #region Constructors /// /// Initializes a new instance of the class. /// - /// - /// + /// Response stream. + /// Presize the memory stream. /// Owner cmdlet if any. - internal WebResponseContentMemoryStream(Stream stream, int initialCapacity, Cmdlet cmdlet) - : base(initialCapacity) + /// Expected download size in Bytes. + /// Time permitted between reads or Timeout.InfiniteTimeSpan for no timeout. + /// Cancellation token. + internal WebResponseContentMemoryStream(Stream stream, int initialCapacity, Cmdlet? cmdlet, long? contentLength, TimeSpan perReadTimeout, CancellationToken cancellationToken) : base(initialCapacity) { + this._contentLength = contentLength; _originalStreamToProxy = stream; _ownerCmdlet = cmdlet; + _cancellationToken = cancellationToken; + _perReadTimeout = perReadTimeout; } - #endregion + #endregion Constructors /// /// - public override bool CanRead - { - get - { - return true; - } - } + public override bool CanRead => true; /// /// - public override bool CanSeek - { - get - { - return true; - } - } + public override bool CanSeek => true; /// /// - public override bool CanTimeout - { - get - { - return base.CanTimeout; - } - } - - /// - /// - public override bool CanWrite - { - get - { - return true; - } - } + public override bool CanWrite => true; /// /// @@ -104,7 +86,7 @@ public override long Length /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { - Initialize(); + Initialize(cancellationToken); return base.CopyToAsync(destination, bufferSize, cancellationToken); } @@ -129,7 +111,7 @@ public override int Read(byte[] buffer, int offset, int count) /// public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - Initialize(); + Initialize(cancellationToken); return base.ReadAsync(buffer, offset, count, cancellationToken); } @@ -180,7 +162,7 @@ public override void Write(byte[] buffer, int offset, int count) /// public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - Initialize(); + Initialize(cancellationToken); return base.WriteAsync(buffer, offset, count, cancellationToken); } @@ -209,23 +191,39 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - /// - /// - private void Initialize() + private void Initialize(CancellationToken cancellationToken = default) { - if (_isInitialized) { return; } + if (_isInitialized) + { + return; + } + + if (cancellationToken == default) + { + cancellationToken = _cancellationToken; + } _isInitialized = true; try { - long totalLength = 0; + long totalRead = 0; byte[] buffer = new byte[StreamHelper.ChunkSize]; ProgressRecord record = new(StreamHelper.ActivityId, WebCmdletStrings.ReadResponseProgressActivity, "statusDescriptionPlaceholder"); - for (int read = 1; read > 0; totalLength += read) + string totalDownloadSize = _contentLength is null ? "???" : Utils.DisplayHumanReadableFileSize((long)_contentLength); + for (int read = 1; read > 0; totalRead += read) { - if (_ownerCmdlet != null) + if (_ownerCmdlet is not null) { - record.StatusDescription = StringUtil.Format(WebCmdletStrings.ReadResponseProgressStatus, totalLength); + record.StatusDescription = StringUtil.Format( + WebCmdletStrings.ReadResponseProgressStatus, + Utils.DisplayHumanReadableFileSize(totalRead), + totalDownloadSize); + + if (_contentLength > 0) + { + record.PercentComplete = Math.Min((int)(totalRead * 100 / (long)_contentLength), 100); + } + _ownerCmdlet.WriteProgress(record); if (_ownerCmdlet.IsStopping) @@ -234,7 +232,7 @@ private void Initialize() } } - read = _originalStreamToProxy.Read(buffer, 0, buffer.Length); + read = _originalStreamToProxy.ReadAsync(buffer.AsMemory(), _perReadTimeout, cancellationToken).GetAwaiter().GetResult(); if (read > 0) { @@ -242,25 +240,103 @@ private void Initialize() } } - if (_ownerCmdlet != null) + if (_ownerCmdlet is not null) { - record.StatusDescription = StringUtil.Format(WebCmdletStrings.ReadResponseComplete, totalLength); + record.StatusDescription = StringUtil.Format(WebCmdletStrings.ReadResponseComplete, totalRead); record.RecordType = ProgressRecordType.Completed; _ownerCmdlet.WriteProgress(record); } - // make sure the length is set appropriately - base.SetLength(totalLength); - base.Seek(0, SeekOrigin.Begin); + // Make sure the length is set appropriately + base.SetLength(totalRead); + Seek(0, SeekOrigin.Begin); } catch (Exception) { - base.Dispose(); + Dispose(); throw; } } } + internal static class StreamTimeoutExtensions + { + internal static async Task ReadAsync(this Stream stream, Memory buffer, TimeSpan readTimeout, CancellationToken cancellationToken) + { + if (readTimeout == Timeout.InfiniteTimeSpan) + { + return await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); + } + + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + try + { + cts.CancelAfter(readTimeout); + return await stream.ReadAsync(buffer, cts.Token).ConfigureAwait(false); + } + catch (TaskCanceledException ex) + { + if (cts.IsCancellationRequested) + { + throw new TimeoutException($"The request was canceled due to the configured OperationTimeout of {readTimeout.TotalSeconds} seconds elapsing", ex); + } + else + { + throw; + } + } + } + + internal static async Task CopyToAsync(this Stream source, Stream destination, TimeSpan perReadTimeout, CancellationToken cancellationToken) + { + if (perReadTimeout == Timeout.InfiniteTimeSpan) + { + // No timeout - use fast path + await source.CopyToAsync(destination, cancellationToken).ConfigureAwait(false); + return; + } + + byte[] buffer = ArrayPool.Shared.Rent(StreamHelper.ChunkSize); + CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + try + { + while (true) + { + if (!cts.TryReset()) + { + cts.Dispose(); + cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + } + + cts.CancelAfter(perReadTimeout); + int bytesRead = await source.ReadAsync(buffer, cts.Token).ConfigureAwait(false); + if (bytesRead == 0) + { + break; + } + + await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false); + } + } + catch (TaskCanceledException ex) + { + if (cts.IsCancellationRequested) + { + throw new TimeoutException($"The request was canceled due to the configured OperationTimeout of {perReadTimeout.TotalSeconds} seconds elapsing", ex); + } + else + { + throw; + } + } + finally + { + cts.Dispose(); + ArrayPool.Shared.Return(buffer); + } + } + } + internal static class StreamHelper { #region Constants @@ -269,46 +345,61 @@ internal static class StreamHelper internal const int ChunkSize = 10000; - // just picked a random number + // Just picked a random number internal const int ActivityId = 174593042; #endregion Constants #region Static Methods - internal static void WriteToStream(Stream input, Stream output, PSCmdlet cmdlet, CancellationToken cancellationToken) + internal static void WriteToStream(Stream input, Stream output, PSCmdlet cmdlet, long? contentLength, TimeSpan perReadTimeout, CancellationToken cancellationToken) { - if (cmdlet == null) - { - throw new ArgumentNullException(nameof(cmdlet)); - } + ArgumentNullException.ThrowIfNull(cmdlet); - Task copyTask = input.CopyToAsync(output, cancellationToken); + Task copyTask = input.CopyToAsync(output, perReadTimeout, cancellationToken); + bool wroteProgress = false; ProgressRecord record = new( ActivityId, WebCmdletStrings.WriteRequestProgressActivity, WebCmdletStrings.WriteRequestProgressStatus); + string totalDownloadSize = contentLength is null ? "???" : Utils.DisplayHumanReadableFileSize((long)contentLength); + try { - do + while (!copyTask.Wait(1000, cancellationToken)) { - record.StatusDescription = StringUtil.Format(WebCmdletStrings.WriteRequestProgressStatus, output.Position); - cmdlet.WriteProgress(record); + record.StatusDescription = StringUtil.Format( + WebCmdletStrings.WriteRequestProgressStatus, + Utils.DisplayHumanReadableFileSize(output.Position), + totalDownloadSize); - Task.Delay(1000).Wait(cancellationToken); - } - while (!copyTask.IsCompleted && !cancellationToken.IsCancellationRequested); + if (contentLength > 0) + { + record.PercentComplete = Math.Min((int)(output.Position * 100 / (long)contentLength), 100); + } - if (copyTask.IsCompleted) - { - record.StatusDescription = StringUtil.Format(WebCmdletStrings.WriteRequestComplete, output.Position); cmdlet.WriteProgress(record); + wroteProgress = true; } } catch (OperationCanceledException) { } + finally + { + if (wroteProgress) + { + // Write out the completion progress record only if we did render the progress. + record.StatusDescription = StringUtil.Format( + copyTask.IsCompleted + ? WebCmdletStrings.WriteRequestComplete + : WebCmdletStrings.WriteRequestCancelled, + output.Position); + record.RecordType = ProgressRecordType.Completed; + cmdlet.WriteProgress(record); + } + } } /// @@ -318,16 +409,18 @@ internal static void WriteToStream(Stream input, Stream output, PSCmdlet cmdlet, /// Input stream. /// Output file name. /// Current cmdlet (Invoke-WebRequest or Invoke-RestMethod). + /// Expected download size in Bytes. + /// Time permitted between reads or Timeout.InfiniteTimeSpan for no timeout. /// CancellationToken to track the cmdlet cancellation. - internal static void SaveStreamToFile(Stream stream, string filePath, PSCmdlet cmdlet, CancellationToken cancellationToken) + internal static void SaveStreamToFile(Stream stream, string filePath, PSCmdlet cmdlet, long? contentLength, TimeSpan perReadTimeout, CancellationToken cancellationToken) { // If the web cmdlet should resume, append the file instead of overwriting. FileMode fileMode = cmdlet is WebRequestPSCmdlet webCmdlet && webCmdlet.ShouldResume ? FileMode.Append : FileMode.Create; using FileStream output = new(filePath, fileMode, FileAccess.Write, FileShare.Read); - WriteToStream(stream, output, cmdlet, cancellationToken); + WriteToStream(stream, output, cmdlet, contentLength, perReadTimeout, cancellationToken); } - private static string StreamToString(Stream stream, Encoding encoding) + private static string StreamToString(Stream stream, Encoding encoding, TimeSpan perReadTimeout, CancellationToken cancellationToken) { StringBuilder result = new(capacity: ChunkSize); Decoder decoder = encoding.GetDecoder(); @@ -338,157 +431,126 @@ private static string StreamToString(Stream stream, Encoding encoding) useBufferSize = encoding.GetMaxCharCount(10); } - char[] chars = new char[useBufferSize]; - byte[] bytes = new byte[useBufferSize * 4]; - int bytesRead = 0; - do + char[] chars = ArrayPool.Shared.Rent(useBufferSize); + byte[] bytes = ArrayPool.Shared.Rent(useBufferSize * 4); + try { - // Read at most the number of bytes that will fit in the input buffer. The - // return value is the actual number of bytes read, or zero if no bytes remain. - bytesRead = stream.Read(bytes, 0, useBufferSize * 4); + int bytesRead = 0; + do + { + // Read at most the number of bytes that will fit in the input buffer. The + // return value is the actual number of bytes read, or zero if no bytes remain. + bytesRead = stream.ReadAsync(bytes.AsMemory(), perReadTimeout, cancellationToken).GetAwaiter().GetResult(); - bool completed = false; - int byteIndex = 0; - int bytesUsed; - int charsUsed; + bool completed = false; + int byteIndex = 0; - while (!completed) - { - // If this is the last input data, flush the decoder's internal buffer and state. - bool flush = (bytesRead == 0); - decoder.Convert(bytes, byteIndex, bytesRead - byteIndex, - chars, 0, useBufferSize, flush, - out bytesUsed, out charsUsed, out completed); - - // The conversion produced the number of characters indicated by charsUsed. Write that number - // of characters to our result buffer - result.Append(chars, 0, charsUsed); - - // Increment byteIndex to the next block of bytes in the input buffer, if any, to convert. - byteIndex += bytesUsed; - - // The behavior of decoder.Convert changed start .NET 3.1-preview2. - // The change was made in https://github.com/dotnet/coreclr/pull/27229 - // The recommendation from .NET team is to not check for 'completed' if 'flush' is false. - // Break out of the loop if all bytes have been read. - if (!flush && bytesRead == byteIndex) + while (!completed) { - break; + // If this is the last input data, flush the decoder's internal buffer and state. + bool flush = bytesRead is 0; + decoder.Convert(bytes, byteIndex, bytesRead - byteIndex, chars, 0, useBufferSize, flush, out int bytesUsed, out int charsUsed, out completed); + + // The conversion produced the number of characters indicated by charsUsed. Write that number + // of characters to our result buffer + result.Append(chars, 0, charsUsed); + + // Increment byteIndex to the next block of bytes in the input buffer, if any, to convert. + byteIndex += bytesUsed; + + // The behavior of decoder.Convert changed start .NET 3.1-preview2. + // The change was made in https://github.com/dotnet/coreclr/pull/27229 + // The recommendation from .NET team is to not check for 'completed' if 'flush' is false. + // Break out of the loop if all bytes have been read. + if (!flush && bytesRead == byteIndex) + { + break; + } } } - } while (bytesRead != 0); + while (bytesRead != 0); - return result.ToString(); + return result.ToString(); + } + finally + { + ArrayPool.Shared.Return(chars); + ArrayPool.Shared.Return(bytes); + } } - internal static string DecodeStream(Stream stream, string characterSet, out Encoding encoding) + internal static string DecodeStream(Stream stream, string? characterSet, out Encoding encoding, TimeSpan perReadTimeout, CancellationToken cancellationToken) { - try - { - encoding = Encoding.GetEncoding(characterSet); - } - catch (ArgumentException) + bool isDefaultEncoding = !TryGetEncoding(characterSet, out encoding); + + string content = StreamToString(stream, encoding, perReadTimeout, cancellationToken); + if (isDefaultEncoding) { - encoding = null; + // We only look within the first 1k characters as the meta element and + // the xml declaration are at the start of the document + string substring = content.Substring(0, Math.Min(content.Length, 1024)); + + // Check for a charset attribute on the meta element to override the default + Match match = s_metaRegex.Match(substring); + + // Check for a encoding attribute on the xml declaration to override the default + if (!match.Success) + { + match = s_xmlRegex.Match(substring); + } + + if (match.Success) + { + characterSet = match.Groups["charset"].Value; + + if (TryGetEncoding(characterSet, out Encoding localEncoding)) + { + stream.Seek(0, SeekOrigin.Begin); + content = StreamToString(stream, localEncoding, perReadTimeout, cancellationToken); + encoding = localEncoding; + } + } } - return DecodeStream(stream, ref encoding); + return content; } - internal static bool TryGetEncoding(string characterSet, out Encoding encoding) + internal static bool TryGetEncoding(string? characterSet, out Encoding encoding) { bool result = false; try { - encoding = Encoding.GetEncoding(characterSet); + encoding = Encoding.GetEncoding(characterSet!); result = true; } catch (ArgumentException) { - encoding = null; + // Use the default encoding if one wasn't provided + encoding = ContentHelper.GetDefaultEncoding(); } return result; } - private static readonly Regex s_metaexp = new( + private static readonly Regex s_metaRegex = new( @"<]*charset\s*=\s*[""'\n]?(?[A-Za-z].[^\s""'\n<>]*)[\s""'\n>]", - RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase + RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.NonBacktracking ); - internal static string DecodeStream(Stream stream, ref Encoding encoding) - { - bool isDefaultEncoding = false; - if (encoding == null) - { - // Use the default encoding if one wasn't provided - encoding = ContentHelper.GetDefaultEncoding(); - isDefaultEncoding = true; - } - - string content = StreamToString(stream, encoding); - if (isDefaultEncoding) - { - do - { - // check for a charset attribute on the meta element to override the default. - Match match = s_metaexp.Match(content); - if (match.Success) - { - Encoding localEncoding = null; - string characterSet = match.Groups["charset"].Value; - - if (TryGetEncoding(characterSet, out localEncoding)) - { - stream.Seek(0, SeekOrigin.Begin); - content = StreamToString(stream, localEncoding); - // report the encoding used. - encoding = localEncoding; - } - } - } while (false); - } - - return content; - } + private static readonly Regex s_xmlRegex = new( + @"<\?xml\s.*[^.><]*encoding\s*=\s*[""'\n]?(?[A-Za-z].[^\s""'\n<>]*)[\s""'\n>]", + RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.NonBacktracking + ); internal static byte[] EncodeToBytes(string str, Encoding encoding) { - if (encoding == null) - { - // just use the default encoding if one wasn't provided - encoding = ContentHelper.GetDefaultEncoding(); - } + // Just use the default encoding if one wasn't provided + encoding ??= ContentHelper.GetDefaultEncoding(); return encoding.GetBytes(str); } - internal static byte[] EncodeToBytes(string str) - { - return EncodeToBytes(str, null); - } - - internal static Stream GetResponseStream(HttpResponseMessage response) - { - Stream responseStream = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); - var contentEncoding = response.Content.Headers.ContentEncoding; - - // HttpClient by default will automatically decompress GZip and Deflate content. - // We keep this decompression logic here just in case. - if (contentEncoding != null && contentEncoding.Count > 0) - { - if (contentEncoding.Contains("gzip")) - { - responseStream = new GZipStream(responseStream, CompressionMode.Decompress); - } - else if (contentEncoding.Contains("deflate")) - { - responseStream = new DeflateStream(responseStream, CompressionMode.Decompress); - } - } - - return responseStream; - } + internal static Stream GetResponseStream(HttpResponseMessage response, CancellationToken cancellationToken) => response.Content.ReadAsStreamAsync(cancellationToken).GetAwaiter().GetResult(); #endregion Static Methods } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebCmdletElementCollection.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebCmdletElementCollection.cs index fada385d4ac..99326898d9f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebCmdletElementCollection.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebCmdletElementCollection.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation; @@ -12,8 +14,7 @@ namespace Microsoft.PowerShell.Commands /// public class WebCmdletElementCollection : ReadOnlyCollection { - internal WebCmdletElementCollection(IList list) - : base(list) + internal WebCmdletElementCollection(IList list) : base(list) { } @@ -22,35 +23,23 @@ internal WebCmdletElementCollection(IList list) /// /// /// Found element as PSObject. - public PSObject Find(string nameOrId) - { - // try Id first - PSObject result = FindById(nameOrId) ?? FindByName(nameOrId); - - return (result); - } + public PSObject? Find(string nameOrId) => FindById(nameOrId) ?? FindByName(nameOrId); /// /// Finds the element by id. /// /// /// Found element as PSObject. - public PSObject FindById(string id) - { - return Find(id, true); - } + public PSObject? FindById(string id) => Find(id, findById: true); /// /// Finds the element by name. /// /// /// Found element as PSObject. - public PSObject FindByName(string name) - { - return Find(name, false); - } + public PSObject? FindByName(string name) => Find(name, findById: false); - private PSObject Find(string nameOrId, bool findById) + private PSObject? Find(string nameOrId, bool findById) { foreach (PSObject candidate in this) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestMethod.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestMethod.cs index aa7067f1c23..9b90115a1d5 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestMethod.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestMethod.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + namespace Microsoft.PowerShell.Commands { /// diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestSession.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestSession.cs index ae08babe17f..efee6f3240e 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestSession.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestSession.cs @@ -1,18 +1,48 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System; using System.Collections.Generic; using System.Net; +using System.Net.Http; +using System.Net.Sockets; +using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; +using System.Threading; namespace Microsoft.PowerShell.Commands { /// /// WebRequestSession for holding session infos. /// - public class WebRequestSession + public class WebRequestSession : IDisposable { + #region Fields + + private HttpClient? _client; + private CookieContainer _cookies; + private bool _useDefaultCredentials; + private ICredentials? _credentials; + private X509CertificateCollection? _certificates; + private IWebProxy? _proxy; + private int _maximumRedirection; + private WebSslProtocol _sslProtocol; + private bool _allowAutoRedirect; + private bool _skipCertificateCheck; + private bool _noProxy; + private bool _disposed; + private TimeSpan _connectionTimeout; + private UnixDomainSocketEndPoint? _unixSocket; + + /// + /// Contains true if an existing HttpClient had to be disposed and recreated since the WebSession was last used. + /// + private bool _disposedClient; + + #endregion Fields + /// /// Gets or sets the Header property. /// @@ -27,27 +57,27 @@ public class WebRequestSession /// /// Gets or sets the Cookies property. /// - public CookieContainer Cookies { get; set; } + public CookieContainer Cookies { get => _cookies; set => SetClassVar(ref _cookies, value); } #region Credentials /// /// Gets or sets the UseDefaultCredentials property. /// - public bool UseDefaultCredentials { get; set; } + public bool UseDefaultCredentials { get => _useDefaultCredentials; set => SetStructVar(ref _useDefaultCredentials, value); } /// /// Gets or sets the Credentials property. /// - public ICredentials Credentials { get; set; } + public ICredentials? Credentials { get => _credentials; set => SetClassVar(ref _credentials, value); } /// /// Gets or sets the Certificates property. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] - public X509CertificateCollection Certificates { get; set; } + public X509CertificateCollection? Certificates { get => _certificates; set => SetClassVar(ref _certificates, value); } - #endregion + #endregion Credentials /// /// Gets or sets the UserAgent property. @@ -57,12 +87,23 @@ public class WebRequestSession /// /// Gets or sets the Proxy property. /// - public IWebProxy Proxy { get; set; } + public IWebProxy? Proxy + { + get => _proxy; + set + { + SetClassVar(ref _proxy, value); + if (_proxy is not null) + { + NoProxy = false; + } + } + } /// - /// Gets or sets the RedirectMax property. + /// Gets or sets the MaximumRedirection property. /// - public int MaximumRedirection { get; set; } + public int MaximumRedirection { get => _maximumRedirection; set => SetStructVar(ref _maximumRedirection, value); } /// /// Gets or sets the count of retries for request failures. @@ -79,23 +120,44 @@ public class WebRequestSession /// public WebRequestSession() { - // build the headers collection + // Build the headers collection Headers = new Dictionary(StringComparer.OrdinalIgnoreCase); ContentHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - // build the cookie jar - Cookies = new CookieContainer(); + // Build the cookie jar + _cookies = new CookieContainer(); - // initialize the credential and certificate caches - UseDefaultCredentials = false; - Credentials = null; - Certificates = null; + // Initialize the credential and certificate caches + _useDefaultCredentials = false; + _credentials = null; + _certificates = null; - // setup the default UserAgent + // Setup the default UserAgent UserAgent = PSUserAgent.UserAgent; - Proxy = null; - MaximumRedirection = -1; + _proxy = null; + _maximumRedirection = -1; + _allowAutoRedirect = true; + } + + internal WebSslProtocol SslProtocol { set => SetStructVar(ref _sslProtocol, value); } + + internal bool SkipCertificateCheck { set => SetStructVar(ref _skipCertificateCheck, value); } + + internal TimeSpan ConnectionTimeout { set => SetStructVar(ref _connectionTimeout, value); } + + internal UnixDomainSocketEndPoint UnixSocket { set => SetClassVar(ref _unixSocket, value); } + + internal bool NoProxy + { + set + { + SetStructVar(ref _noProxy, value); + if (_noProxy) + { + Proxy = null; + } + } } /// @@ -104,12 +166,150 @@ public WebRequestSession() /// The certificate to be added. internal void AddCertificate(X509Certificate certificate) { - if (Certificates == null) + Certificates ??= new X509CertificateCollection(); + if (!Certificates.Contains(certificate)) + { + ResetClient(); + Certificates.Add(certificate); + } + } + + /// + /// Gets an existing or creates a new HttpClient for this WebRequest session if none currently exists (either because it was never + /// created, or because changes to the WebSession properties required the existing HttpClient to be disposed). + /// + /// True if the caller does not want the HttpClient to ever handle redirections automatically. + /// Contains true if an existing HttpClient had to be disposed and recreated since the WebSession was last used. + /// The HttpClient cached in the WebSession, based on all current settings. + internal HttpClient GetHttpClient(bool suppressHttpClientRedirects, out bool clientWasReset) + { + // Do not auto redirect if the caller does not want it, or maximum redirections is 0 + SetStructVar(ref _allowAutoRedirect, !(suppressHttpClientRedirects || MaximumRedirection == 0)); + + clientWasReset = _disposedClient; + + if (_client is null) + { + _client = CreateHttpClient(); + _disposedClient = false; + } + + return _client; + } + + private HttpClient CreateHttpClient() + { + SocketsHttpHandler handler = new(); + + if (_unixSocket is not null) { - Certificates = new X509CertificateCollection(); + handler.ConnectCallback = async (context, token) => + { + Socket socket = new(AddressFamily.Unix, SocketType.Stream, ProtocolType.IP); + await socket.ConnectAsync(_unixSocket).ConfigureAwait(false); + + return new NetworkStream(socket, ownsSocket: false); + }; } - Certificates.Add(certificate); + handler.CookieContainer = Cookies; + handler.AutomaticDecompression = DecompressionMethods.All; + + if (Credentials is not null) + { + handler.Credentials = Credentials; + } + else if (UseDefaultCredentials) + { + handler.Credentials = CredentialCache.DefaultCredentials; + } + + if (_noProxy) + { + handler.UseProxy = false; + } + else if (Proxy is not null) + { + handler.Proxy = Proxy; + } + + if (Certificates is not null) + { + handler.SslOptions.ClientCertificates = new X509CertificateCollection(Certificates); + } + + if (_skipCertificateCheck) + { + handler.SslOptions.RemoteCertificateValidationCallback = delegate { return true; }; + } + + handler.AllowAutoRedirect = _allowAutoRedirect; + if (_allowAutoRedirect && MaximumRedirection > 0) + { + handler.MaxAutomaticRedirections = MaximumRedirection; + } + + handler.SslOptions.EnabledSslProtocols = (SslProtocols)_sslProtocol; + + // Check timeout setting (in seconds) + return new HttpClient(handler) + { + Timeout = _connectionTimeout + }; + } + + private void SetClassVar(ref T oldValue, T newValue) where T : class? + { + if (oldValue != newValue) + { + ResetClient(); + oldValue = newValue; + } + } + + private void SetStructVar(ref T oldValue, T newValue) where T : struct + { + if (!oldValue.Equals(newValue)) + { + ResetClient(); + oldValue = newValue; + } + } + + private void ResetClient() + { + if (_client is not null) + { + _disposedClient = true; + _client.Dispose(); + _client = null; + } + } + + /// + /// Dispose the WebRequestSession. + /// + /// True when called from Dispose() and false when called from finalizer. + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + _client?.Dispose(); + } + + _disposed = true; + } + } + + /// + /// Dispose the WebRequestSession. + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); } } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Write.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Write.cs index bf907203544..d20d7d8712b 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Write.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Write.cs @@ -33,15 +33,11 @@ protected override void ProcessRecord() // so we create the DebugRecord here and fill it up with the appropriate InvocationInfo; // then, we call the command runtime directly and pass this record to WriteDebug(). // - MshCommandRuntime mshCommandRuntime = this.CommandRuntime as MshCommandRuntime; - - if (mshCommandRuntime != null) + if (this.CommandRuntime is MshCommandRuntime mshCommandRuntime) { DebugRecord record = new(Message); - InvocationInfo invocationInfo = GetVariableValue(SpecialVariables.MyInvocation) as InvocationInfo; - - if (invocationInfo != null) + if (GetVariableValue(SpecialVariables.MyInvocation) is InvocationInfo invocationInfo) { record.SetInvocationInfo(invocationInfo); } @@ -81,15 +77,11 @@ protected override void ProcessRecord() // so we create the VerboseRecord here and fill it up with the appropriate InvocationInfo; // then, we call the command runtime directly and pass this record to WriteVerbose(). // - MshCommandRuntime mshCommandRuntime = this.CommandRuntime as MshCommandRuntime; - - if (mshCommandRuntime != null) + if (this.CommandRuntime is MshCommandRuntime mshCommandRuntime) { VerboseRecord record = new(Message); - InvocationInfo invocationInfo = GetVariableValue(SpecialVariables.MyInvocation) as InvocationInfo; - - if (invocationInfo != null) + if (GetVariableValue(SpecialVariables.MyInvocation) is InvocationInfo invocationInfo) { record.SetInvocationInfo(invocationInfo); } @@ -129,15 +121,11 @@ protected override void ProcessRecord() // so we create the WarningRecord here and fill it up with the appropriate InvocationInfo; // then, we call the command runtime directly and pass this record to WriteWarning(). // - MshCommandRuntime mshCommandRuntime = this.CommandRuntime as MshCommandRuntime; - - if (mshCommandRuntime != null) + if (this.CommandRuntime is MshCommandRuntime mshCommandRuntime) { WarningRecord record = new(Message); - InvocationInfo invocationInfo = GetVariableValue(SpecialVariables.MyInvocation) as InvocationInfo; - - if (invocationInfo != null) + if (GetVariableValue(SpecialVariables.MyInvocation) is InvocationInfo invocationInfo) { record.SetInvocationInfo(invocationInfo); } @@ -214,7 +202,7 @@ public class WriteOrThrowErrorCommand : PSCmdlet /// /// ErrorRecord.Exception -- if not specified, ErrorRecord.Exception is System.Exception. /// - [Parameter(ParameterSetName = "WithException", Mandatory = true)] + [Parameter(Position = 0, ParameterSetName = "WithException", Mandatory = true)] public Exception Exception { get; set; } /// @@ -232,7 +220,7 @@ public class WriteOrThrowErrorCommand : PSCmdlet /// If Exception is specified, this is ErrorRecord.ErrorDetails.Message; /// otherwise, the Exception is System.Exception, and this is Exception.Message. /// - [Parameter(ParameterSetName = "ErrorRecord", Mandatory = true)] + [Parameter(Position = 0, ParameterSetName = "ErrorRecord", Mandatory = true)] public ErrorRecord ErrorRecord { get; set; } /// @@ -312,10 +300,7 @@ protected override void ProcessRecord() { Exception e = this.Exception; string msg = Message; - if (e == null) - { - e = new WriteErrorException(msg); - } + e ??= new WriteErrorException(msg); string errid = ErrorId; if (string.IsNullOrEmpty(errid)) @@ -339,10 +324,7 @@ protected override void ProcessRecord() string recact = RecommendedAction; if (!string.IsNullOrEmpty(recact)) { - if (errorRecord.ErrorDetails == null) - { - errorRecord.ErrorDetails = new ErrorDetails(errorRecord.ToString()); - } + errorRecord.ErrorDetails ??= new ErrorDetails(errorRecord.ToString()); errorRecord.ErrorDetails.RecommendedAction = recact; } @@ -367,8 +349,7 @@ protected override void ProcessRecord() // 2005/07/14-913791 "write-error output is confusing and misleading" // set InvocationInfo to the script not the command - InvocationInfo myInvocation = GetVariableValue(SpecialVariables.MyInvocation) as InvocationInfo; - if (myInvocation != null) + if (GetVariableValue(SpecialVariables.MyInvocation) is InvocationInfo myInvocation) { errorRecord.SetInvocationInfo(myInvocation); errorRecord.PreserveInvocationInfoOnce = true; @@ -428,7 +409,6 @@ public ThrowErrorCommand() /// when the user only specifies a string and not /// an Exception or ErrorRecord. /// - [Serializable] public class WriteErrorException : SystemException { #region ctor @@ -471,10 +451,11 @@ public WriteErrorException(string message, /// Serialization information. /// Streaming context. /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected WriteErrorException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion Serialization } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteAliasCommandBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteAliasCommandBase.cs index 82c9c9f6557..31670935fcf 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteAliasCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteAliasCommandBase.cs @@ -61,6 +61,7 @@ public SwitchParameter PassThru /// The scope parameter for the command determines which scope the alias is set in. /// [Parameter] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } /// diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteConsoleCmdlet.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteConsoleCmdlet.cs index 2d2009e56bc..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 { @@ -51,9 +52,7 @@ private string ProcessObject(object o) { if (o != null) { - string s = o as string; - IEnumerable enumerable = null; - if (s != null) + if (o is string s) { // strings are IEnumerable, so we special case them if (s.Length > 0) @@ -61,7 +60,11 @@ private string ProcessObject(object o) return s; } } - else if ((enumerable = o as IEnumerable) != null) + 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/WriteProgressCmdlet.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteProgressCmdlet.cs index fe8fccb6e1a..0751954c54e 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteProgressCmdlet.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteProgressCmdlet.cs @@ -17,7 +17,6 @@ public sealed class WriteProgressCommand : PSCmdlet /// [Parameter( Position = 0, - Mandatory = true, HelpMessageBaseName = HelpMessageBaseName, HelpMessageResourceId = "ActivityParameterHelpMessage")] public string Activity { get; set; } @@ -36,7 +35,7 @@ public sealed class WriteProgressCommand : PSCmdlet /// Uniquely identifies this activity for purposes of chaining subordinate activities. /// [Parameter(Position = 2)] - [ValidateRange(0, Int32.MaxValue)] + [ValidateRange(0, int.MaxValue)] public int Id { get; set; } /// @@ -62,7 +61,7 @@ public sealed class WriteProgressCommand : PSCmdlet /// Identifies the parent Id of this activity, or -1 if none. /// [Parameter] - [ValidateRange(-1, Int32.MaxValue)] + [ValidateRange(-1, int.MaxValue)] public int ParentId { get; set; } = -1; /// @@ -96,7 +95,29 @@ protected override void ProcessRecord() { - ProgressRecord pr = new(Id, Activity, Status); + ProgressRecord pr; + if (string.IsNullOrEmpty(Activity)) + { + if (!Completed) + { + ThrowTerminatingError(new ErrorRecord( + new ArgumentException("Missing value for mandatory parameter.", nameof(Activity)), + "MissingActivity", + ErrorCategory.InvalidArgument, + Activity)); + return; + } + else + { + pr = new(Id); + pr.StatusDescription = Status; + } + } + else + { + pr = new(Id, Activity, Status); + } + pr.ParentActivityId = ParentId; pr.PercentComplete = PercentComplete; pr.SecondsRemaining = SecondsRemaining; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs index 8db56196a68..6157849eeec 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. /// [Parameter] - [ArgumentToEncodingTransformationAttribute()] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] public Encoding Encoding { @@ -128,7 +128,7 @@ public Encoding Encoding } } - private Encoding _encoding = ClrFacade.GetDefaultEncoding(); + private Encoding _encoding = Encoding.Default; #endregion Command Line Parameters @@ -208,7 +208,10 @@ private void CreateFileStream() { Dbg.Assert(Path != null, "FileName is mandatory parameter"); - if (!ShouldProcess(Path)) return; + if (!ShouldProcess(Path)) + { + return; + } StreamWriter sw; PathUtils.MasterStreamOpen( @@ -439,7 +442,7 @@ protected override void BeginProcessing() } else { - WriteObject(string.Format(CultureInfo.InvariantCulture, "", Encoding.UTF8.WebName)); + WriteObject(string.Create(CultureInfo.InvariantCulture, $"")); WriteObject(""); } } @@ -453,8 +456,7 @@ protected override void ProcessRecord() { CreateMemoryStream(); - if (_serializer != null) - _serializer.SerializeAsStream(InputObject); + _serializer?.SerializeAsStream(InputObject); if (_serializer != null) { @@ -472,8 +474,7 @@ protected override void ProcessRecord() } else { - if (_serializer != null) - _serializer.Serialize(InputObject); + _serializer?.Serialize(InputObject); } } @@ -631,6 +632,86 @@ private void CleanUp() #endregion IDisposable Members } + /// + /// Implements ConvertTo-CliXml command. + /// + [Cmdlet(VerbsData.ConvertTo, "CliXml", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2280866")] + [OutputType(typeof(string))] + public sealed class ConvertToClixmlCommand : PSCmdlet + { + #region Parameters + + /// + /// Gets or sets input objects to be converted to CliXml object. + /// + [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)] + public PSObject InputObject { get; set; } + + /// + /// Gets or sets depth of serialization. + /// + [Parameter] + [ValidateRange(1, int.MaxValue)] + public int Depth { get; set; } = 2; + + #endregion Parameters + + #region Private Members + + private readonly List _inputObjectBuffer = new(); + + #endregion Private Members + + #region Overrides + + /// + /// Process record. + /// + protected override void ProcessRecord() + { + _inputObjectBuffer.Add(InputObject); + } + + /// + /// End Processing. + /// + protected override void EndProcessing() + { + WriteObject(PSSerializer.Serialize(_inputObjectBuffer, Depth, enumerate: true)); + } + + #endregion Overrides + } + + /// + /// Implements ConvertFrom-CliXml command. + /// + [Cmdlet(VerbsData.ConvertFrom, "CliXml", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2280770")] + public sealed class ConvertFromClixmlCommand : PSCmdlet + { + #region Parameters + + /// + /// Gets or sets input object which is written in CliXml format. + /// + [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)] + public string InputObject { get; set; } + + #endregion Parameters + + #region Overrides + + /// + /// Process record. + /// + protected override void ProcessRecord() + { + WriteObject(PSSerializer.Deserialize(InputObject)); + } + + #endregion Overrides + } + /// /// Helper class to import single XML file. /// @@ -763,12 +844,10 @@ internal void Import() while (!_deserializer.Done() && count < first) { object result = _deserializer.Deserialize(); - PSObject psObject = result as PSObject; - if (psObject != null) + if (result is PSObject psObject) { - ICollection c = psObject.BaseObject as ICollection; - if (c != null) + if (psObject.BaseObject is ICollection c) { foreach (object o in c) { @@ -801,19 +880,13 @@ internal void Import() } } - internal void Stop() - { - if (_deserializer != null) - { - _deserializer.Stop(); - } - } + internal void Stop() => _deserializer?.Stop(); } #region Select-Xml - /// - ///This cmdlet is used to search an xml document based on the XPath Query. - /// + /// + /// This cmdlet is used to search an xml document based on the XPath Query. + /// [Cmdlet(VerbsCommon.Select, "Xml", DefaultParameterSetName = "Xml", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097031")] [OutputType(typeof(SelectXmlInfo))] public class SelectXmlCommand : PSCmdlet diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/GetTracerCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/GetTracerCommand.cs index b37f06b2b2f..491aaf85361 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/GetTracerCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/GetTracerCommand.cs @@ -51,7 +51,7 @@ public string[] Name protected override void ProcessRecord() { var sources = GetMatchingTraceSource(_names, true); - var result = sources.OrderBy(source => source.Name); + var result = sources.OrderBy(static source => source.Name); WriteObject(result, true); } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/SetTracerCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/SetTracerCommand.cs index a17ee0fdbef..9f8694ebd20 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/SetTracerCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/SetTracerCommand.cs @@ -99,7 +99,7 @@ public SwitchParameter Debugger } /// - /// If this parameter is specified the Msh Host trace listener will be added. + /// If this parameter is specified the PSHost trace listener will be added. /// /// [Parameter(ParameterSetName = "optionsSet")] 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 412f4a2d150..91cb0d46abc 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceExpressionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceExpressionCommand.cs @@ -249,13 +249,7 @@ protected override void EndProcessing() /// /// Ensures that the sub-pipeline we created gets stopped as well. /// - protected override void StopProcessing() - { - if (_pipeline != null) - { - _pipeline.Stop(); - } - } + protected override void StopProcessing() => _pipeline?.Stop(); #endregion Cmdlet code @@ -336,15 +330,8 @@ internal TracePipelineWriter( bool writeError, Collection matchingSources) { - if (cmdlet == null) - { - throw new ArgumentNullException(nameof(cmdlet)); - } - - if (matchingSources == null) - { - throw new ArgumentNullException(nameof(matchingSources)); - } + ArgumentNullException.ThrowIfNull(cmdlet); + ArgumentNullException.ThrowIfNull(matchingSources); _cmdlet = cmdlet; _writeError = writeError; @@ -525,8 +512,7 @@ public override int Write(object obj, bool enumerateCollection) private static ErrorRecord ConvertToErrorRecord(object obj) { ErrorRecord result = null; - PSObject mshobj = obj as PSObject; - if (mshobj != null) + if (obj is PSObject mshobj) { object baseObject = mshobj.BaseObject; if (baseObject is not PSCustomObject) @@ -535,8 +521,7 @@ private static ErrorRecord ConvertToErrorRecord(object obj) } } - ErrorRecord errorRecordResult = obj as ErrorRecord; - if (errorRecordResult != null) + if (obj is ErrorRecord errorRecordResult) { result = errorRecordResult; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/AddTypeStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/AddTypeStrings.resx index 75ba47072d2..6b178fb85eb 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/AddTypeStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/AddTypeStrings.resx @@ -120,27 +120,12 @@ The source code was already compiled and loaded. - - The generated type defines no public methods or properties. - - - The generated type is not public. - - - Cannot add type. The -MemberDefinition parameter is not supported for this language. - Cannot add type. The "{0}" extension is not supported. Cannot add type. Input files must all have the same file extension. - - Cannot add type. Specify only the Language or CodeDomProvider parameters. - - - Cannot add type. The assembly name {0} matches both {1} and {2}. - Cannot add type. The assembly '{0}' could not be found. @@ -153,9 +138,6 @@ Cannot add type. Compilation errors occurred. - - Cannot add type. One or more required assemblies are missing. - Cannot add type. The OutputType parameter requires that the OutputAssembly parameter be specified. @@ -168,4 +150,10 @@ Both the assembly types 'ConsoleApplication' and 'WindowsApplication' are not currently supported. + + Add-Type Cmdlet + + + Add-Type cmdlet will not be allowed in ConstrainedLanguage mode. + diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/AliasCommandStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/AliasCommandStrings.resx index 1c3b0e52953..f10c2e64236 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/AliasCommandStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/AliasCommandStrings.resx @@ -135,12 +135,6 @@ Name: {0} Value: {1} - - Cannot export the aliases because path '{0}' referred to a '{1}' provider path. Change the Path parameter to a file system path. - - - Cannot export the aliases because path '{0}' contains wildcard characters that resolved to multiple paths. Aliases can be exported to only one file. Change the value of the Path parameter to a path that resolves to a single file. - Cannot open file {0} to export the alias. {1} diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/ConvertMarkdownStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/ConvertMarkdownStrings.resx index d37509d0f50..d77c7422abe 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/ConvertMarkdownStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/ConvertMarkdownStrings.resx @@ -120,9 +120,6 @@ The type of the input object '{0}' is invalid. - - The file is not found: '{0}'. - Only FileSystem Provider paths are supported. The file path is not supported: '{0}'. diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/GetFormatDataStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/GetFormatDataStrings.resx new file mode 100644 index 00000000000..de231836cd5 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/GetFormatDataStrings.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Processing view defintion '{0}' + + diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/HostStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/HostStrings.resx index 6172bb81951..c01248ed5d7 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/HostStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/HostStrings.resx @@ -120,7 +120,4 @@ Cannot process the color because {0} is not a valid color. - - Cannot evaluate the error because a string is not specified. - diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/HttpCommandStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/HttpCommandStrings.resx index 78ccfb68dc1..a5f3e822ce3 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/HttpCommandStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/HttpCommandStrings.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - The command cannot run because "{0}" is empty or blank. Specify a value, and then run the command again. - This command cannot be completed due to the following error: '{0}'. diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/ImmutableStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/ImmutableStrings.resx index 9ba7fddb0b5..71e978c89b4 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/ImmutableStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/ImmutableStrings.resx @@ -117,31 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Object is not a array with the same initialization state as the array to compare it to. - - - Object is not a array with the same number of elements as the array to compare it to. - - - Cannot find the old value - - - Capacity was less than the current Count of elements. - - - MoveToImmutable can only be performed when Count equals Capacity. - - - Collection was modified; enumeration operation may not execute. - An element with the same key but a different value already exists. Key: {0} - - This operation does not apply to an empty instance. - - - This operation cannot be performed on a default instance of ImmutableArray<T>. Consider initializing the array, or checking the ImmutableArray<T>.IsDefault property. - diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/ImplicitRemotingStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/ImplicitRemotingStrings.resx index 48d1fea7c34..3ee0ae73220 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/ImplicitRemotingStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/ImplicitRemotingStrings.resx @@ -129,9 +129,6 @@ Running the {0} command in a remote session returned no results. - - Cannot create temporary file for implicit remoting module. - No session has been associated with this implicit remoting module. @@ -147,15 +144,6 @@ Proxy creation has been skipped for the '{0}' command, because PowerShell could not verify the safety of the command name. - - Proxy creation has been skipped for the '{0}' command, because PowerShell could not verify the safety of a parameter name: '{1}'. - - - Proxy creation has been skipped for the '{0}' command, because PowerShell could not verify the safety of a parameter set name: '{1}'. - - - Proxy creation has been skipped for the '{0}' command, because PowerShell could not verify the safety of a parameter alias name: '{1}'. - Proxy creation has been skipped for the following command: '{0}', because it would shadow an existing local command. Use the AllowClobber parameter if you want to shadow existing local commands. @@ -204,9 +192,6 @@ Getting formatting and output information from remote session ... {0} objects received - - Generating a proxy command for '{0}' ... - Completed. diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/ImportLocalizedDataStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/ImportLocalizedDataStrings.resx index 8bd19d60ac3..cf792788c6f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/ImportLocalizedDataStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/ImportLocalizedDataStrings.resx @@ -143,4 +143,10 @@ The BindingVariable name '{0}' is invalid. + + Import-LocalizedData Cmdlet + + + Additional supported commands (via SupportedCommand parameter) will not be allowed in ConstrainedLanguage mode. + diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/MeasureObjectStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/MeasureObjectStrings.resx index 8211114c335..e27aa35e747 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/MeasureObjectStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/MeasureObjectStrings.resx @@ -120,9 +120,6 @@ The property "{0}" cannot be found in the input for any objects. - - Property "{0}" is not numeric. - Input object "{0}" is not numeric. diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/NewObjectStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/NewObjectStrings.resx index 07c2a6d8c9e..8c8d59034cc 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/NewObjectStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/NewObjectStrings.resx @@ -147,7 +147,16 @@ Cannot create type. Only core types are supported in {0} language mode on a policy locked down machine. - - {0} Please note that Single-Threaded Apartment is not supported in PowerShell. + + New-Object Cmdlet Type Creation + + + The type '{0}' will not be created in ConstrainedLanguage mode. + + + New-Object Cmdlet COM Object Creation + + + The COM object '{0}' will not be created in ConstrainedLanguage mode. diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/StartSleepStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/StartSleepStrings.resx new file mode 100644 index 00000000000..32804b9e21b --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/StartSleepStrings.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '-Duration' parameter value must not exceed '{0}', provided value was '{1}'. + + diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/TestJsonCmdletStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/TestJsonCmdletStrings.resx index ab105e47fd3..cc607eda418 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/TestJsonCmdletStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/TestJsonCmdletStrings.resx @@ -123,10 +123,13 @@ Cannot parse the JSON. - - The JSON is not valid with the schema. + + The JSON is not valid with the schema: {0} at '{1}' Can not open JSON schema file: {0} + + URI scheme '{0}' is not supported. Only HTTP(S) and local file system URIs are allowed. + diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/TraceCommandStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/TraceCommandStrings.resx index 0eb1e1e9466..c09ba1ea796 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/TraceCommandStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/TraceCommandStrings.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - A file listener with name '{0}' was not found. - Trace output can only be written to the file system. The path '{0}' referred to a '{1}' provider path. diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/UpdateDataStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/UpdateDataStrings.resx index 9d2ccaced94..ba1bdfd6174 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/UpdateDataStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/UpdateDataStrings.resx @@ -135,9 +135,6 @@ Cannot update a member with type "{0}". Specify a different type for the MemberType parameter. - - The value of the SerializationDepth property should not be negative. - The {0} parameter is required for the type "{1}". Please specify the {0} parameter. diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx index 551f0d1fcb8..0eedd2d38d2 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - There are no matching results found for {2}. - {2} has one or more exceptions that are not valid. @@ -165,13 +162,16 @@ Cannot use tag '{0}'. The 'PS' prefix is reserved. - - Algorithm '{0}' is not supported in this system. - The file '{0}' could not be parsed as a PowerShell Data File. Cannot construct a security descriptor from the given SDDL due to the following error: {0} + + Invoke-Expression Cmdlet + + + Invoke-Expression cmdlet script block will be run in ConstrainedLanguage mode. + diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/VariableCommandStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/VariableCommandStrings.resx index bff7eee52af..d385adda038 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/VariableCommandStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/VariableCommandStrings.resx @@ -123,18 +123,15 @@ Name: {0} Value: {1} + + Use a single variable rather than a collection + New variable Name: {0} Value: {1} - - Add variable - - - Name: {0} - Remove variable diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/WebCmdletStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/WebCmdletStrings.resx index e3c64bd5ca3..cb080d37012 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/WebCmdletStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/WebCmdletStrings.resx @@ -129,7 +129,7 @@ The cmdlet cannot run because the following parameter is not specified: Credential. The supplied Authentication type requires a Credential. Specify Credential, then retry. - + The cmdlet cannot run because the following parameter is not specified: Token. The supplied Authentication type requires a Token. Specify Token, then retry. @@ -159,17 +159,14 @@ Cannot convert the JSON string because a dictionary that was converted from the string contains the duplicated key '{0}'. - - 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}'. - - - The ConvertTo-Json and ConvertFrom-Json cmdlets require the installation of the .NET Client Profile, sometimes called the .NET extended profile. - The response content cannot be parsed because the Internet Explorer engine is not available, or Internet Explorer's first-launch configuration is not complete. Specify the UseBasicParsing parameter and try again. - - The converted JSON string is in bad format. + + Cannot follow an insecure redirection by default. Reissue the command specifying the -AllowInsecureRedirect switch. + + + 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}'. The maximum redirection count has been exceeded. To increase the number of redirections allowed, supply a higher value to the -MaximumRedirection parameter. @@ -199,16 +196,16 @@ The cmdlet cannot run because the following parameter is missing: Proxy. Provide a valid proxy URI for the Proxy parameter when using the ProxyCredential or ProxyUseDefaultCredentials parameters, then retry. - Reading web response completed. (Number of bytes read: {0}) + Reading web response stream completed. Bytes downloaded: {0} - Reading web response + Reading web response stream - Reading response stream... (Number of bytes read: {0}) + Downloaded: {0} of {1} - - The operation has timed out. + + The Resume switch can only be used if OutFile targets a file but it resolves to a directory: {0}. The cmdlet cannot run because the following conflicting parameters are specified: Session and SessionVariable. Specify either Session or SessionVariable, then retry. @@ -219,26 +216,14 @@ Web request completed. (Number of bytes processed: {0}) + + Web request cancelled. (Number of bytes processed: {0}) + Web request status - Number of bytes processed: {0} - - - The ConvertTo-Json and ConvertFrom-Json cmdlets require the 'Json.Net' module. {0} - - - The cmdlet cannot run because the 'Json.Net' module cannot be loaded. Import the module manually or set the $PSModuleAutoLoadingPreference variable to enable module auto loading. For more information, see 'get-help about_Preference_Variables'. - - - However, the 'Json.Net' module could not be loaded. For more information, run 'Import-Module Json.Net'. - - - Ensure 'Json.Net.psd1' and 'Newtonsoft.Json.dll' are available in a versioned subdirectory of '{0}'. - - - The maximum depth allowed for serialization is {0}. + Downloaded: {0} of {1} Conversion from JSON failed with error: {0} @@ -249,14 +234,11 @@ Following rel link {0} - - {0} with {1}-byte payload - The remote server indicated it could not resume downloading. The local file will be overwritten. - - received {0}-byte response of content type {1} + + Received HTTP/{0} response of content type {1} of unknown size Retrying after interval of {0} seconds. Status code for previous attempt: {1} @@ -264,4 +246,7 @@ Resulting JSON is truncated as serialization has exceeded the set depth of {0}. + + The WebSession properties were changed between requests forcing all HTTP connections in the session to be recreated. + diff --git a/src/Microsoft.PowerShell.Commands.Utility/singleshell/installer/MshUtilityMshSnapin.cs b/src/Microsoft.PowerShell.Commands.Utility/singleshell/installer/MshUtilityMshSnapin.cs index 1c29b44c603..ebb3325d0b7 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/singleshell/installer/MshUtilityMshSnapin.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/singleshell/installer/MshUtilityMshSnapin.cs @@ -7,10 +7,8 @@ namespace Microsoft.PowerShell { /// - /// MshUtilityMshSnapin (or MshUtilityMshSnapinInstaller) is a class for facilitating registry - /// of necessary information for monad utility mshsnapin. - /// - /// This class will be built with monad utility dll. + /// PSUtilityPSSnapIn is a class for facilitating registry + /// of necessary information for PowerShell utility PSSnapin. /// [RunInstaller(true)] public sealed class PSUtilityPSSnapIn : PSSnapIn @@ -24,7 +22,7 @@ public PSUtilityPSSnapIn() } /// - /// Get name of this mshsnapin. + /// Get name of this PSSnapin. /// public override string Name { @@ -35,7 +33,7 @@ public override string Name } /// - /// Get the default vendor string for this mshsnapin. + /// Get the default vendor string for this PSSnapin. /// public override string Vendor { @@ -57,7 +55,7 @@ public override string VendorResource } /// - /// Get the default description string for this mshsnapin. + /// Get the default description string for this PSSnapin. /// public override string Description { diff --git a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/ComInterfaces.cs b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/ComInterfaces.cs index f5903ded5bc..8cc6bd8ab02 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/ComInterfaces.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/ComInterfaces.cs @@ -20,20 +20,20 @@ internal static class ComInterfaces [StructLayout(LayoutKind.Sequential)] internal readonly struct StartUpInfo { - public readonly UInt32 cb; + public readonly uint cb; private readonly IntPtr lpReserved; public readonly IntPtr lpDesktop; public readonly IntPtr lpTitle; - public readonly UInt32 dwX; - public readonly UInt32 dwY; - public readonly UInt32 dwXSize; - public readonly UInt32 dwYSize; - public readonly UInt32 dwXCountChars; - public readonly UInt32 dwYCountChars; - public readonly UInt32 dwFillAttribute; - public readonly UInt32 dwFlags; - public readonly UInt16 wShowWindow; - private readonly UInt16 cbReserved2; + public readonly uint dwX; + public readonly uint dwY; + public readonly uint dwXSize; + public readonly uint dwYSize; + public readonly uint dwXCountChars; + public readonly uint dwYCountChars; + public readonly uint dwFillAttribute; + public readonly uint dwFlags; + public readonly ushort wShowWindow; + private readonly ushort cbReserved2; private readonly IntPtr lpReserved2; public readonly IntPtr hStdInput; public readonly IntPtr hStdOutput; @@ -160,7 +160,7 @@ internal interface IPropertyStore HResult Commit(); } - [ComImport()] + [ComImport] [Guid("6332DEBF-87B5-4670-90C0-5E57B408A49E")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface ICustomDestinationList @@ -204,7 +204,7 @@ internal enum KnownDestinationCategory Recent } - [ComImport()] + [ComImport] [Guid("92CA9DCD-5622-4BBA-A805-5E9F541BD8C9")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IObjectArray @@ -217,7 +217,7 @@ void GetAt( [Out(), MarshalAs(UnmanagedType.Interface)] out object ppvObject); } - [ComImport()] + [ComImport] [Guid("5632B1A4-E38A-400A-928A-D4CD63230295")] [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] internal interface IObjectCollection @@ -248,13 +248,13 @@ void AddFromArray( internal interface IShellLinkDataListW { [PreserveSig] - Int32 AddDataBlock(IntPtr pDataBlock); + int AddDataBlock(IntPtr pDataBlock); [PreserveSig] - Int32 CopyDataBlock(UInt32 dwSig, out IntPtr ppDataBlock); + int CopyDataBlock(uint dwSig, out IntPtr ppDataBlock); [PreserveSig] - Int32 RemoveDataBlock(UInt32 dwSig); + int RemoveDataBlock(uint dwSig); void GetFlags(out uint pdwFlags); diff --git a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropertyKey.cs b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropertyKey.cs index ecd1918492b..93346424dc5 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropertyKey.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropertyKey.cs @@ -21,7 +21,7 @@ namespace Microsoft.PowerShell /// /// Property identifier (PID) /// - public Int32 PropertyId { get; } + public int PropertyId { get; } #endregion @@ -32,7 +32,7 @@ namespace Microsoft.PowerShell /// /// A unique GUID for the property. /// Property identifier (PID). - internal PropertyKey(Guid formatId, Int32 propertyId) + internal PropertyKey(Guid formatId, int propertyId) { this.FormatId = formatId; this.PropertyId = propertyId; diff --git a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/TaskbarJumpList.cs b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/TaskbarJumpList.cs index 90c6c811bb1..5606eabd567 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/TaskbarJumpList.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/TaskbarJumpList.cs @@ -33,13 +33,12 @@ internal static void CreateRunAsAdministratorJumpList() { try { - TaskbarJumpList.CreateElevatedEntry(ConsoleHostStrings.RunAsAdministrator); + CreateElevatedEntry(ConsoleHostStrings.RunAsAdministrator); } - catch (Exception exception) + catch (Exception) { // Due to COM threading complexity there might still be sporadic failures but they can be // ignored as creating the JumpList is not critical and persists after its first creation. - Debug.Fail($"Creating 'Run as Administrator' JumpList failed. {exception}"); } }); @@ -48,7 +47,7 @@ internal static void CreateRunAsAdministratorJumpList() thread.SetApartmentState(ApartmentState.STA); thread.Start(); } - catch (System.Threading.ThreadStartException) + catch (ThreadStartException) { // STA may not be supported on some platforms } @@ -117,7 +116,6 @@ private static void CreateElevatedEntry(string title) var CLSID_EnumerableObjectCollection = new Guid(@"2d3468c1-36a7-43b6-ac24-d3f02fd9607a"); const uint CLSCTX_INPROC_HANDLER = 2; const uint CLSCTX_INPROC = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER; - var ComSvrInterface_GUID = new Guid(@"555E2D2B-EE00-47AA-AB2B-39F953F6B339"); hResult = CoCreateInstance(ref CLSID_EnumerableObjectCollection, null, CLSCTX_INPROC, ref IID_IUnknown, out object instance); if (hResult < 0) { diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs index ad809b70982..50d2bd77d0f 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. - #nullable enable using System; @@ -175,6 +174,7 @@ internal static int MaxNameLength() "sta", "mta", "command", + "commandwithargs", "configurationname", "custompipename", "encodedcommand", @@ -187,6 +187,7 @@ internal static int MaxNameLength() "nologo", "noninteractive", "noprofile", + "noprofileloadtime", "outputformat", "removeworkingdirectorytrailingcharacter", "settingsfile", @@ -195,6 +196,60 @@ internal static int MaxNameLength() "workingdirectory" }; + /// + /// These represent the parameters that are used when starting pwsh. + /// We can query in our telemetry to determine how pwsh was invoked. + /// + [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 + // Enum values for specified ExecutionPolicy + EPUnrestricted = 0x0000000100000000, // ExecutionPolicy unrestricted + EPRemoteSigned = 0x0000000200000000, // ExecutionPolicy remote signed + EPAllSigned = 0x0000000400000000, // ExecutionPolicy all signed + EPRestricted = 0x0000000800000000, // ExecutionPolicy restricted + EPDefault = 0x0000001000000000, // ExecutionPolicy default + EPBypass = 0x0000002000000000, // ExecutionPolicy bypass + EPUndefined = 0x0000004000000000, // ExecutionPolicy undefined + EPIncorrect = 0x0000008000000000, // ExecutionPolicy incorrect + } + + internal ParameterBitmap ParametersUsed = 0; + + internal double ParametersUsedAsDouble + { + get { return (double)ParametersUsed; } + } + [Conditional("DEBUG")] private void AssertArgumentsParsed() { @@ -320,6 +375,15 @@ internal Collection Args } } + internal string? ConfigurationFile + { + get + { + AssertArgumentsParsed(); + return _configurationFile; + } + } + internal string? ConfigurationName { get @@ -403,6 +467,15 @@ internal bool ShowExtendedHelp } } + internal bool NoProfileLoadTime + { + get + { + AssertArgumentsParsed(); + return _noProfileLoadTime; + } + } + internal bool ShowVersion { get @@ -477,7 +550,7 @@ internal bool StaMode } else { - return true; + return Platform.IsStaSupported; } } } @@ -608,7 +681,8 @@ internal static string GetConfigurationNameFromGroupPolicy() return (switchKey: string.Empty, shouldBreak: false); } - if (!CharExtensions.IsDash(switchKey[0]) && switchKey[0] != '/') + char firstChar = switchKey[0]; + if (!CharExtensions.IsDash(firstChar) && firstChar != '/') { // then it's a file --argIndex; @@ -622,7 +696,7 @@ internal static string GetConfigurationNameFromGroupPolicy() switchKey = switchKey.Substring(1); // chop off the second dash so we're agnostic wrt specifying - or -- - if (!string.IsNullOrEmpty(switchKey) && CharExtensions.IsDash(switchKey[0])) + if (!string.IsNullOrEmpty(switchKey) && CharExtensions.IsDash(firstChar) && switchKey[0] == firstChar) { switchKey = switchKey.Substring(1); } @@ -640,6 +714,53 @@ internal static string NormalizeFilePath(string path) return Path.GetFullPath(path); } + /// + /// Determine the execution policy based on the supplied string. + /// If the string doesn't match to any known execution policy, set it to incorrect. + /// + /// The value provided on the command line. + /// The execution policy. + private static ParameterBitmap GetExecutionPolicy(string? _executionPolicy) + { + if (_executionPolicy is null) + { + return ParameterBitmap.EPUndefined; + } + + ParameterBitmap executionPolicySetting = ParameterBitmap.EPIncorrect; + + if (string.Equals(_executionPolicy, "default", StringComparison.OrdinalIgnoreCase)) + { + executionPolicySetting = ParameterBitmap.EPDefault; + } + else if (string.Equals(_executionPolicy, "remotesigned", StringComparison.OrdinalIgnoreCase)) + { + executionPolicySetting = ParameterBitmap.EPRemoteSigned; + } + else if (string.Equals(_executionPolicy, "bypass", StringComparison.OrdinalIgnoreCase)) + { + executionPolicySetting = ParameterBitmap.EPBypass; + } + else if (string.Equals(_executionPolicy, "allsigned", StringComparison.OrdinalIgnoreCase)) + { + executionPolicySetting = ParameterBitmap.EPAllSigned; + } + else if (string.Equals(_executionPolicy, "restricted", StringComparison.OrdinalIgnoreCase)) + { + executionPolicySetting = ParameterBitmap.EPRestricted; + } + else if (string.Equals(_executionPolicy, "unrestricted", StringComparison.OrdinalIgnoreCase)) + { + executionPolicySetting = ParameterBitmap.EPUnrestricted; + } + else if (string.Equals(_executionPolicy, "undefined", StringComparison.OrdinalIgnoreCase)) + { + executionPolicySetting = ParameterBitmap.EPUndefined; + } + + return executionPolicySetting; + } + private static bool MatchSwitch(string switchKey, string match, string smallestUnambiguousMatch) { Dbg.Assert(!string.IsNullOrEmpty(match), "need a value"); @@ -689,7 +810,6 @@ private void DisplayBanner(PSHostUserInterface hostUI, string? bannerText) if (!string.IsNullOrEmpty(bannerText)) { hostUI.WriteLine(bannerText); - hostUI.WriteLine(); } if (UpdatesNotification.CanNotifyUpdates) @@ -715,10 +835,7 @@ internal void Parse(string[] args) for (int i = 0; i < args.Length; i++) { - if (args[i] is null) - { - throw new ArgumentNullException(nameof(args), CommandLineParameterParserStrings.NullElementInArgs); - } + ArgumentNullException.ThrowIfNull(args[i], CommandLineParameterParserStrings.NullElementInArgs); } // Indicates that we've called this method on this instance, and that when it's done, the state variables @@ -755,6 +872,7 @@ private void ParseHelper(string[] args) _noInteractive = true; _skipUserInit = true; _noExit = false; + ParametersUsed |= ParameterBitmap.Version; break; } @@ -763,48 +881,81 @@ private void ParseHelper(string[] args) _showHelp = true; _showExtendedHelp = true; _abortStartup = true; + ParametersUsed |= ParameterBitmap.Help; } else if (MatchSwitch(switchKey, "login", "l")) { // On Windows, '-Login' does nothing. // On *nix, '-Login' is already handled much earlier to improve startup performance, so we do nothing here. + ParametersUsed |= ParameterBitmap.Login; } else if (MatchSwitch(switchKey, "noexit", "noe")) { _noExit = true; noexitSeen = true; + ParametersUsed |= ParameterBitmap.NoExit; } else if (MatchSwitch(switchKey, "noprofile", "nop")) { _skipUserInit = true; + ParametersUsed |= ParameterBitmap.NoProfile; } else if (MatchSwitch(switchKey, "nologo", "nol")) { _showBanner = false; + ParametersUsed |= ParameterBitmap.NoLogo; } else if (MatchSwitch(switchKey, "noninteractive", "noni")) { _noInteractive = true; + ParametersUsed |= ParameterBitmap.NonInteractive; } else if (MatchSwitch(switchKey, "socketservermode", "so")) { _socketServerMode = true; + _showBanner = false; + ParametersUsed |= ParameterBitmap.SocketServerMode; } else if (MatchSwitch(switchKey, "servermode", "s")) { _serverMode = true; + _showBanner = false; + ParametersUsed |= ParameterBitmap.ServerMode; } else if (MatchSwitch(switchKey, "namedpipeservermode", "nam")) { _namedPipeServerMode = true; + _showBanner = false; + ParametersUsed |= ParameterBitmap.NamedPipeServerMode; } else if (MatchSwitch(switchKey, "sshservermode", "sshs")) { _sshServerMode = true; + _showBanner = false; + ParametersUsed |= ParameterBitmap.SSHServerMode; + } + else if (MatchSwitch(switchKey, "noprofileloadtime", "noprofileloadtime")) + { + _noProfileLoadTime = true; + ParametersUsed |= ParameterBitmap.NoProfileLoadTime; } else if (MatchSwitch(switchKey, "interactive", "i")) { _noInteractive = false; + ParametersUsed |= ParameterBitmap.Interactive; + } + else if (MatchSwitch(switchKey, "configurationfile", "configurationfile")) + { + ++i; + if (i >= args.Length) + { + SetCommandLineError( + CommandLineParameterParserStrings.MissingConfigurationFileArgument); + break; + } + + _configurationFile = args[i]; + ParametersUsed |= ParameterBitmap.ConfigurationFile; } else if (MatchSwitch(switchKey, "configurationname", "config")) { @@ -817,6 +968,7 @@ private void ParseHelper(string[] args) } _configurationName = args[i]; + ParametersUsed |= ParameterBitmap.ConfigurationName; } else if (MatchSwitch(switchKey, "custompipename", "cus")) { @@ -841,7 +993,22 @@ private void ParseHelper(string[] args) break; } #endif + _customPipeName = args[i]; + ParametersUsed |= ParameterBitmap.CustomPipeName; + } + else if (MatchSwitch(switchKey, "commandwithargs", "commandwithargs") || MatchSwitch(switchKey, "cwa", "cwa")) + { + _commandHasArgs = true; + + if (!ParseCommand(args, ref i, noexitSeen, false)) + { + break; + } + + i++; + CollectPSArgs(args, ref i); + ParametersUsed |= ParameterBitmap.CommandWithArgs; } else if (MatchSwitch(switchKey, "command", "c")) { @@ -849,6 +1016,8 @@ private void ParseHelper(string[] args) { break; } + + ParametersUsed |= ParameterBitmap.Command; } else if (MatchSwitch(switchKey, "windowstyle", "w")) { @@ -875,6 +1044,8 @@ private void ParseHelper(string[] args) string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidWindowStyleArgument, args[i], e.Message)); break; } + + ParametersUsed |= ParameterBitmap.WindowStyle; #endif } else if (MatchSwitch(switchKey, "file", "f")) @@ -883,6 +1054,8 @@ private void ParseHelper(string[] args) { break; } + + ParametersUsed |= ParameterBitmap.File; } #if DEBUG else if (MatchSwitch(switchKey, "isswait", "isswait")) @@ -894,14 +1067,27 @@ private void ParseHelper(string[] args) { ParseFormat(args, ref i, ref _outFormat, CommandLineParameterParserStrings.MissingOutputFormatParameter); _outputFormatSpecified = true; + ParametersUsed |= ParameterBitmap.OutputFormat; } else if (MatchSwitch(switchKey, "inputformat", "inp") || MatchSwitch(switchKey, "if", "if")) { ParseFormat(args, ref i, ref _inFormat, CommandLineParameterParserStrings.MissingInputFormatParameter); + ParametersUsed |= ParameterBitmap.InputFormat; } else if (MatchSwitch(switchKey, "executionpolicy", "ex") || MatchSwitch(switchKey, "ep", "ep")) { ParseExecutionPolicy(args, ref i, ref _executionPolicy, CommandLineParameterParserStrings.MissingExecutionPolicyParameter); + ParametersUsed |= ParameterBitmap.ExecutionPolicy; + var executionPolicy = GetExecutionPolicy(_executionPolicy); + if (executionPolicy == ParameterBitmap.EPIncorrect) + { + SetCommandLineError( + string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidExecutionPolicyArgument, _executionPolicy), + showHelp: true); + break; + } + + ParametersUsed |= executionPolicy; } else if (MatchSwitch(switchKey, "encodedcommand", "e") || MatchSwitch(switchKey, "ec", "e")) { @@ -910,6 +1096,8 @@ private void ParseHelper(string[] args) { break; } + + ParametersUsed |= ParameterBitmap.EncodedCommand; } else if (MatchSwitch(switchKey, "encodedarguments", "encodeda") || MatchSwitch(switchKey, "ea", "ea")) { @@ -917,6 +1105,8 @@ private void ParseHelper(string[] args) { break; } + + ParametersUsed |= ParameterBitmap.EncodedArgument; } else if (MatchSwitch(switchKey, "settingsfile", "settings")) { @@ -925,10 +1115,12 @@ private void ParseHelper(string[] args) { break; } + + ParametersUsed |= ParameterBitmap.SettingsFile; } else if (MatchSwitch(switchKey, "sta", "sta")) { - if (!Platform.IsWindowsDesktop) + if (!Platform.IsWindowsDesktop || !Platform.IsStaSupported) { SetCommandLineError( CommandLineParameterParserStrings.STANotImplemented); @@ -944,6 +1136,7 @@ private void ParseHelper(string[] args) } _staMode = true; + ParametersUsed |= ParameterBitmap.STA; } else if (MatchSwitch(switchKey, "mta", "mta")) { @@ -963,6 +1156,7 @@ private void ParseHelper(string[] args) } _staMode = false; + ParametersUsed |= ParameterBitmap.MTA; } else if (MatchSwitch(switchKey, "workingdirectory", "wo") || MatchSwitch(switchKey, "wd", "wd")) { @@ -975,6 +1169,7 @@ private void ParseHelper(string[] args) } _workingDirectory = args[i]; + ParametersUsed |= ParameterBitmap.WorkingDirectory; } #if !UNIX else if (MatchSwitch(switchKey, "removeworkingdirectorytrailingcharacter", "removeworkingdirectorytrailingcharacter")) @@ -990,6 +1185,9 @@ private void ParseHelper(string[] args) { break; } + + // default to filename being the next argument. + ParametersUsed |= ParameterBitmap.File; } } @@ -1023,7 +1221,7 @@ private void SetCommandLineError(string msg, bool showHelp = false, bool showBan private void ParseFormat(string[] args, ref int i, ref Serialization.DataFormat format, string resourceStr) { StringBuilder sb = new StringBuilder(); - foreach (string s in Enum.GetNames(typeof(Serialization.DataFormat))) + foreach (string s in Enum.GetNames()) { sb.Append(s); sb.Append(Environment.NewLine); @@ -1073,15 +1271,6 @@ private void ParseExecutionPolicy(string[] args, ref int i, ref string? executio // treat -command as an argument to the script... private bool ParseFile(string[] args, ref int i, bool noexitSeen) { - // Try parse '$true', 'true', '$false' and 'false' values. - static object ConvertToBoolIfPossible(string arg) - { - // Before parsing we skip '$' if present. - return arg.Length > 0 && bool.TryParse(arg.AsSpan(arg[0] == '$' ? 1 : 0), out bool boolValue) - ? (object)boolValue - : (object)arg; - } - ++i; if (i >= args.Length) { @@ -1158,54 +1347,75 @@ static object ConvertToBoolIfPossible(string arg) showHelp: true); return false; } +#if !UNIX + // Only do the .ps1 extension check on Windows since shebang is not supported + if (!_file.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) + { + SetCommandLineError(string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidFileArgumentExtension, args[i])); + return false; + } +#endif i++; - string? pendingParameter = null; + CollectPSArgs(args, ref i); + } - // Accumulate the arguments to this script... - while (i < args.Length) - { - string arg = args[i]; + return true; + } - // If there was a pending parameter, add a named parameter - // using the pending parameter and current argument - if (pendingParameter != null) - { - _collectedArgs.Add(new CommandParameter(pendingParameter, arg)); - pendingParameter = null; - } - else if (!string.IsNullOrEmpty(arg) && CharExtensions.IsDash(arg[0]) && arg.Length > 1) + private void CollectPSArgs(string[] args, ref int i) + { + // Try parse '$true', 'true', '$false' and 'false' values. + static object ConvertToBoolIfPossible(string arg) + { + // Before parsing we skip '$' if present. + return arg.Length > 0 && bool.TryParse(arg.AsSpan(arg[0] == '$' ? 1 : 0), out bool boolValue) + ? (object)boolValue + : (object)arg; + } + + string? pendingParameter = null; + + while (i < args.Length) + { + string arg = args[i]; + + // If there was a pending parameter, add a named parameter + // using the pending parameter and current argument + if (pendingParameter != null) + { + _collectedArgs.Add(new CommandParameter(pendingParameter, arg)); + pendingParameter = null; + } + else if (!string.IsNullOrEmpty(arg) && CharExtensions.IsDash(arg[0]) && arg.Length > 1) + { + int offset = arg.IndexOf(':'); + if (offset >= 0) { - int offset = arg.IndexOf(':'); - if (offset >= 0) + if (offset == arg.Length - 1) { - if (offset == arg.Length - 1) - { - pendingParameter = arg.TrimEnd(':'); - } - else - { - string argValue = arg.Substring(offset + 1); - string argName = arg.Substring(0, offset); - _collectedArgs.Add(new CommandParameter(argName, ConvertToBoolIfPossible(argValue))); - } + pendingParameter = arg.TrimEnd(':'); } else { - _collectedArgs.Add(new CommandParameter(arg)); + string argValue = arg.Substring(offset + 1); + string argName = arg.Substring(0, offset); + _collectedArgs.Add(new CommandParameter(argName, ConvertToBoolIfPossible(argValue))); } } else { - _collectedArgs.Add(new CommandParameter(null, arg)); + _collectedArgs.Add(new CommandParameter(arg)); } - - ++i; } - } + else + { + _collectedArgs.Add(new CommandParameter(null, arg)); + } - return true; + ++i; + } } private bool ParseCommand(string[] args, ref int i, bool noexitSeen, bool isEncoded) @@ -1261,23 +1471,15 @@ private bool ParseCommand(string[] args, ref int i, bool noexitSeen, bool isEnco } else { - // Collect the remaining parameters and combine them into a single command to be run. - - StringBuilder cmdLineCmdSB = new StringBuilder(); - - while (i < args.Length) + if (_commandHasArgs) { - cmdLineCmdSB.Append(args[i] + " "); - ++i; + _commandLineCommand = args[i]; } - - if (cmdLineCmdSB.Length > 0) + else { - // remove the last blank - cmdLineCmdSB.Remove(cmdLineCmdSB.Length - 1, 1); + _commandLineCommand = string.Join(' ', args, i, args.Length - i); + i = args.Length; } - - _commandLineCommand = cmdLineCmdSB.ToString(); } if (!noexitSeen && !_explicitReadCommandsFromStdin) @@ -1331,7 +1533,9 @@ private bool CollectArgs(string[] args, ref int i) private bool _serverMode; private bool _namedPipeServerMode; private bool _sshServerMode; + private bool _noProfileLoadTime; private bool _showVersion; + private string? _configurationFile; private string? _configurationName; private string? _error; private bool _showHelp; @@ -1347,6 +1551,7 @@ private bool CollectArgs(string[] args, ref int i) private bool _noPrompt; private string? _commandLineCommand; private bool _wasCommandEncoded; + private bool _commandHasArgs; private uint _exitCode = ConsoleHost.ExitCodeSuccess; private bool _dirty; private Serialization.DataFormat _outFormat = Serialization.DataFormat.Text; diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs index a157964c637..8857c64fa50 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs @@ -10,9 +10,9 @@ // On the use of DangerousGetHandle: If the handle has been invalidated, then the API we pass it to will return an error. These // handles should not be exposed to recycling attacks (because they are not exposed at all), but if they were, the worse they // could do is diddle with the console buffer. -#pragma warning disable 1634, 1691 using System; +using System.Buffers; using System.Text; using System.Runtime.InteropServices; using System.Management.Automation; @@ -109,7 +109,7 @@ internal struct COORD public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "{0},{1}", X, Y); + return string.Create(CultureInfo.InvariantCulture, $"{X},{Y}"); } } @@ -161,7 +161,7 @@ internal struct SMALL_RECT public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "{0},{1},{2},{3}", Left, Top, Right, Bottom); + return string.Create(CultureInfo.InvariantCulture, $"{Left},{Top},{Right},{Bottom}"); } } @@ -192,7 +192,7 @@ internal struct CONSOLE_CURSOR_INFO public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "Size: {0}, Visible: {1}", Size, Visible); + return string.Create(CultureInfo.InvariantCulture, $"Size: {Size}, Visible: {Visible}"); } } @@ -275,13 +275,13 @@ internal struct MouseInput /// The absolute position of the mouse, or the amount of motion since the last mouse event was generated, depending on the value of the dwFlags member. /// Absolute data is specified as the x coordinate of the mouse; relative data is specified as the number of pixels moved. /// - internal Int32 X; + internal int X; /// /// The absolute position of the mouse, or the amount of motion since the last mouse event was generated, depending on the value of the dwFlags member. /// Absolute data is specified as the y coordinate of the mouse; relative data is specified as the number of pixels moved. /// - internal Int32 Y; + internal int Y; /// /// If dwFlags contains MOUSEEVENTF_WHEEL, then mouseData specifies the amount of wheel movement. A positive value indicates that the wheel was rotated forward, away from the user; @@ -450,7 +450,7 @@ internal enum KeyboardFlag : uint /// True if it was successful. [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow); + internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); internal static void SetConsoleMode(ProcessWindowStyle style) { @@ -1249,8 +1249,7 @@ internal static void CheckWriteEdges( { if (firstLeftTrailingRow >= 0) { - throw PSTraceSource.NewArgumentException(string.Format(CultureInfo.InvariantCulture, "contents[{0}, {1}]", - firstLeftTrailingRow, contentsRegion.Left)); + throw PSTraceSource.NewArgumentException(string.Create(CultureInfo.InvariantCulture, $"contents[{firstLeftTrailingRow}, {contentsRegion.Left}]")); } } else @@ -1265,8 +1264,7 @@ internal static void CheckWriteEdges( if (leftExisting[r, 0].BufferCellType == BufferCellType.Leading ^ contents[r, contentsRegion.Left].BufferCellType == BufferCellType.Trailing) { - throw PSTraceSource.NewArgumentException(string.Format(CultureInfo.InvariantCulture, "contents[{0}, {1}]", - r, contentsRegion.Left)); + throw PSTraceSource.NewArgumentException(string.Create(CultureInfo.InvariantCulture, $"contents[{r}, {contentsRegion.Left}]")); } } } @@ -1275,8 +1273,7 @@ internal static void CheckWriteEdges( { if (firstRightLeadingRow >= 0) { - throw PSTraceSource.NewArgumentException(string.Format(CultureInfo.InvariantCulture, "contents[{0}, {1}]", - firstRightLeadingRow, contentsRegion.Right)); + throw PSTraceSource.NewArgumentException(string.Create(CultureInfo.InvariantCulture, $"contents[{firstRightLeadingRow}, {contentsRegion.Right}]")); } } else @@ -1291,8 +1288,7 @@ internal static void CheckWriteEdges( if (rightExisting[r, 0].BufferCellType == BufferCellType.Leading ^ contents[r, contentsRegion.Right].BufferCellType == BufferCellType.Leading) { - throw PSTraceSource.NewArgumentException(string.Format(CultureInfo.InvariantCulture, "contents[{0}, {1}]", - r, contentsRegion.Right)); + throw PSTraceSource.NewArgumentException(string.Create(CultureInfo.InvariantCulture, $"contents[{r}, {contentsRegion.Right}]")); } } } @@ -1312,7 +1308,7 @@ private static void CheckWriteConsoleOutputContents(BufferCell[,] contents, Rect contents[r, c].Character != 0) { // trailing character is not 0 - throw PSTraceSource.NewArgumentException(string.Format(CultureInfo.InvariantCulture, "contents[{0}, {1}]", r, c)); + throw PSTraceSource.NewArgumentException(string.Create(CultureInfo.InvariantCulture, $"contents[{r}, {c}]")); } if (contents[r, c].BufferCellType == BufferCellType.Leading) @@ -1327,7 +1323,7 @@ private static void CheckWriteConsoleOutputContents(BufferCell[,] contents, Rect { // for a 2 cell character, either there is no trailing BufferCell or // the trailing BufferCell's character is not 0 - throw PSTraceSource.NewArgumentException(string.Format(CultureInfo.InvariantCulture, "contents[{0}, {1}]", r, c)); + throw PSTraceSource.NewArgumentException(string.Create(CultureInfo.InvariantCulture, $"contents[{r}, {c}]")); } } } @@ -1479,9 +1475,7 @@ private static void WriteConsoleOutputCJK(ConsoleHandle consoleHandle, Coordinat bSize.X++; SMALL_RECT wRegion = writeRegion; wRegion.Right++; - // Suppress the PreFAST warning about not using Marshal.GetLastWin32Error() to - // get the error code. -#pragma warning disable 56523 + result = NativeMethods.WriteConsoleOutput( consoleHandle.DangerousGetHandle(), characterBuffer, @@ -1491,9 +1485,6 @@ private static void WriteConsoleOutputCJK(ConsoleHandle consoleHandle, Coordinat } else { - // Suppress the PreFAST warning about not using Marshal.GetLastWin32Error() to - // get the error code. -#pragma warning disable 56523 result = NativeMethods.WriteConsoleOutput( consoleHandle.DangerousGetHandle(), characterBuffer, @@ -1527,7 +1518,7 @@ private static void WriteConsoleOutputCJK(ConsoleHandle consoleHandle, Coordinat // to write is larger than bufferLimit. In that case, the algorithm writes one row // at a time => bufferSize.Y == 1. Then, we can safely leave bufferSize.Y unchanged // to retry with a smaller bufferSize.X. - Dbg.Assert(bufferSize.Y == 1, string.Format(CultureInfo.InvariantCulture, "bufferSize.Y should be 1, but is {0}", bufferSize.Y)); + Dbg.Assert(bufferSize.Y == 1, string.Create(CultureInfo.InvariantCulture, $"bufferSize.Y should be 1, but is {bufferSize.Y}")); bufferSize.X = (short)Math.Min(colsRemaining, bufferLimit); continue; } @@ -1655,7 +1646,7 @@ private static void WriteConsoleOutputPlain(ConsoleHandle consoleHandle, Coordin // to write is larger than bufferLimit. In that case, the algorithm writes one row // at a time => bufferSize.Y == 1. Then, we can safely leave bufferSize.Y unchanged // to retry with a smaller bufferSize.X. - Dbg.Assert(bufferSize.Y == 1, string.Format(CultureInfo.InvariantCulture, "bufferSize.Y should be 1, but is {0}", bufferSize.Y)); + Dbg.Assert(bufferSize.Y == 1, string.Create(CultureInfo.InvariantCulture, $"bufferSize.Y should be 1, but is {bufferSize.Y}")); bufferSize.X = (short)Math.Min(colsRemaining, bufferLimit); continue; } @@ -1732,10 +1723,7 @@ internal static void ReadConsoleOutput if (origin.X + (contentsRegion.Right - contentsRegion.Left) + 1 < bufferInfo.BufferSize.X && ShouldCheck(contentsRegion.Right, contents, contentsRegion)) { - if (cellArray == null) - { - cellArray = new BufferCell[cellArrayRegion.Bottom + 1, 2]; - } + cellArray ??= new BufferCell[cellArrayRegion.Bottom + 1, 2]; checkOrigin = new Coordinates(origin.X + (contentsRegion.Right - contentsRegion.Left), origin.Y); @@ -1799,9 +1787,7 @@ private static bool ReadConsoleOutputCJKSmall readRegion.Top = (short)origin.Y; readRegion.Right = (short)(origin.X + bufferSize.X - 1); readRegion.Bottom = (short)(origin.Y + bufferSize.Y - 1); - // Suppress the PreFAST warning about not using Marshal.GetLastWin32Error() to - // get the error code. -#pragma warning disable 56523 + bool result = NativeMethods.ReadConsoleOutput( consoleHandle.DangerousGetHandle(), characterBuffer, @@ -1985,7 +1971,7 @@ internal static void ReadConsoleOutputCJK // to write is larger than bufferLimit. In that case, the algorithm reads one row // at a time => bufferSize.Y == 1. Then, we can safely leave bufferSize.Y unchanged // to retry with a smaller bufferSize.X. - Dbg.Assert(bufferSize.Y == 1, string.Format(CultureInfo.InvariantCulture, "bufferSize.Y should be 1, but is {0}", bufferSize.Y)); + Dbg.Assert(bufferSize.Y == 1, string.Create(CultureInfo.InvariantCulture, $"bufferSize.Y should be 1, but is {bufferSize.Y}")); bufferSize.X = (short)Math.Min(colsRemaining, bufferLimit); continue; } @@ -2155,7 +2141,7 @@ private static void ReadConsoleOutputPlain // to write is larger than bufferLimit. In that case, the algorithm reads one row // at a time => bufferSize.Y == 1. Then, we can safely leave bufferSize.Y unchanged // to retry with a smaller bufferSize.X. - Dbg.Assert(bufferSize.Y == 1, string.Format(CultureInfo.InvariantCulture, "bufferSize.Y should be 1, but is {0}", bufferSize.Y)); + Dbg.Assert(bufferSize.Y == 1, string.Create(CultureInfo.InvariantCulture, $"bufferSize.Y should be 1, but is {bufferSize.Y}")); bufferSize.X = (short)Math.Min(colsRemaining, bufferLimit); continue; } @@ -2270,14 +2256,13 @@ Coordinates origin c.X = (short)origin.X; c.Y = (short)origin.Y; - DWORD unused = 0; bool result = NativeMethods.FillConsoleOutputCharacter( consoleHandle.DangerousGetHandle(), character, (DWORD)numberToWrite, c, - out unused); + out _); if (!result) { int err = Marshal.GetLastWin32Error(); @@ -2324,14 +2309,13 @@ Coordinates origin c.X = (short)origin.X; c.Y = (short)origin.Y; - DWORD unused = 0; bool result = NativeMethods.FillConsoleOutputAttribute( consoleHandle.DangerousGetHandle(), attribute, (DWORD)numberToWrite, c, - out unused); + out _); if (!result) { @@ -2479,9 +2463,6 @@ internal static string GetConsoleWindowTitle() DWORD result; StringBuilder consoleTitle = new StringBuilder((int)bufferSize); - // Suppress the PreFAST warning about not using Marshal.GetLastWin32Error() to - // get the error code. -#pragma warning disable 56523 result = NativeMethods.GetConsoleTitle(consoleTitle, bufferSize); // If the result is zero, it may mean and error but it may also mean // that the window title has been set to null. Since we can't tell the @@ -2494,6 +2475,8 @@ internal static string GetConsoleWindowTitle() return consoleTitle.ToString(); } + private static bool s_dontsetConsoleWindowTitle; + /// /// Wraps Win32 SetConsoleTitle. /// @@ -2505,12 +2488,27 @@ internal static string GetConsoleWindowTitle() /// internal static void SetConsoleWindowTitle(string consoleTitle) { + if (s_dontsetConsoleWindowTitle) + { + return; + } + bool result = NativeMethods.SetConsoleTitle(consoleTitle); if (!result) { int err = Marshal.GetLastWin32Error(); + // ERROR_GEN_FAILURE is returned if this api can't be used with the terminal + if (err == 0x1f) + { + tracer.WriteLine("Call to SetConsoleTitle failed: {0}", err); + s_dontsetConsoleWindowTitle = true; + + // We ignore this specific error as the console can still continue to operate + return; + } + HostException e = CreateHostException(err, "SetConsoleWindowTitle", ErrorCategory.ResourceUnavailable, ConsoleControlStrings.SetConsoleWindowTitleExceptionTemplate); throw e; @@ -2549,42 +2547,54 @@ internal static void WriteConsole(ConsoleHandle consoleHandle, ReadOnlySpan outBuffer; + + // In case that a new line is required, we try to write out the last chunk and the new-line string together, + // to avoid one extra call to 'WriteConsole' just for a new line string. + while (cursor + MaxBufferSize < output.Length) + { + outBuffer = output.Slice(cursor, MaxBufferSize); + cursor += MaxBufferSize; + WriteConsole(consoleHandle, outBuffer); + } - while (cursor < output.Length) + outBuffer = output.Slice(cursor); + if (!newLine) { - ReadOnlySpan outBuffer; + WriteConsole(consoleHandle, outBuffer); + return; + } - if (cursor + MaxBufferSize < output.Length) - { - outBuffer = output.Slice(cursor, MaxBufferSize); - cursor += MaxBufferSize; + char[] rentedArray = null; + string lineEnding = Environment.NewLine; + int size = outBuffer.Length + lineEnding.Length; - WriteConsole(consoleHandle, outBuffer); - } - else + // We expect the 'size' will often be small, and thus optimize that case with 'stackalloc'. + Span buffer = size <= MaxStackAllocSize ? stackalloc char[size] : default; + + try + { + if (buffer.IsEmpty) { - outBuffer = output.Slice(cursor); - cursor = output.Length; + rentedArray = ArrayPool.Shared.Rent(size); + buffer = rentedArray.AsSpan().Slice(0, size); + } - if (newLine) - { - var endOfLine = Environment.NewLine.AsSpan(); - var endOfLineLength = endOfLine.Length; -#pragma warning disable CA2014 - Span outBufferLine = stackalloc char[outBuffer.Length + endOfLineLength]; -#pragma warning restore CA2014 - outBuffer.CopyTo(outBufferLine); - endOfLine.CopyTo(outBufferLine.Slice(outBufferLine.Length - endOfLineLength)); - WriteConsole(consoleHandle, outBufferLine); - } - else - { - WriteConsole(consoleHandle, outBuffer); - } + outBuffer.CopyTo(buffer); + lineEnding.CopyTo(buffer.Slice(outBuffer.Length)); + WriteConsole(consoleHandle, buffer); + } + finally + { + if (rentedArray is not null) + { + ArrayPool.Shared.Return(rentedArray); } } } @@ -2654,7 +2664,7 @@ internal static void SetConsoleTextAttribute(ConsoleHandle consoleHandle, WORD a // CSI params? '#' [{}pq] // XTPUSHSGR ('{'), XTPOPSGR ('}'), or their aliases ('p' and 'q') // // Where: - // params: digit+ (';' params)? + // params: digit+ ((';' | ':') params)? // CSI: C0_CSI | C1_CSI // C0_CSI: \x001b '[' // ESC '[' // C1_CSI: \x009b @@ -2699,7 +2709,7 @@ internal static int ControlSequenceLength(string str, ref int offset) { c = str[offset++]; } - while ((offset < str.Length) && (char.IsDigit(c) || c == ';')); + while ((offset < str.Length) && (char.IsDigit(c) || (c == ';') || (c == ':'))); // Finally, handle the command characters for the specific sequences we // handle: @@ -2789,7 +2799,7 @@ internal static int LengthInBufferCells(char c) ((uint)(c - 0xffe0) <= (0xffe6 - 0xffe0))); // We can ignore these ranges because .Net strings use surrogate pairs - // for this range and we do not handle surrogage pairs. + // for this range and we do not handle surrogate pairs. // (c >= 0x20000 && c <= 0x2fffd) || // (c >= 0x30000 && c <= 0x3fffd) return 1 + (isWide ? 1 : 0); @@ -2818,40 +2828,6 @@ internal static bool IsCJKOutputCodePage(out uint codePage) #region Cursor - /// - /// Wraps Win32 SetConsoleCursorPosition. - /// - /// - /// handle for the console where cursor position is set - /// - /// - /// location to which the cursor will be set - /// - /// - /// If Win32's SetConsoleCursorPosition fails - /// - internal static void SetConsoleCursorPosition(ConsoleHandle consoleHandle, Coordinates cursorPosition) - { - Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); - Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed"); - - ConsoleControl.COORD c; - - c.X = (short)cursorPosition.X; - c.Y = (short)cursorPosition.Y; - - bool result = NativeMethods.SetConsoleCursorPosition(consoleHandle.DangerousGetHandle(), c); - - if (!result) - { - int err = Marshal.GetLastWin32Error(); - - HostException e = CreateHostException(err, "SetConsoleCursorPosition", - ErrorCategory.ResourceUnavailable, ConsoleControlStrings.SetConsoleCursorPositionExceptionTemplate); - throw e; - } - } - /// /// Wraps Win32 GetConsoleCursorInfo. /// @@ -3159,10 +3135,6 @@ out DWORD numberOfEventsRead [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SetConsoleCtrlHandler(BreakHandler handlerRoutine, bool add); - [DllImport(PinvokeDllNames.SetConsoleCursorPositionDllName, SetLastError = true, CharSet = CharSet.Unicode)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool SetConsoleCursorPosition(NakedWin32Handle consoleOutput, COORD cursorPosition); - [DllImport(PinvokeDllNames.SetConsoleModeDllName, SetLastError = true, CharSet = CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SetConsoleMode(NakedWin32Handle consoleHandle, DWORD mode); @@ -3213,7 +3185,7 @@ ref CHAR_INFO fill ); [DllImport(PinvokeDllNames.SendInputDllName, SetLastError = true, CharSet = CharSet.Unicode)] - internal static extern UInt32 SendInput(UInt32 inputNumbers, INPUT[] inputs, Int32 sizeOfInput); + internal static extern UInt32 SendInput(UInt32 inputNumbers, INPUT[] inputs, int sizeOfInput); // There is no GetCurrentConsoleFontEx on Core [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs index 660e6774334..ab9bdab568d 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -#pragma warning disable 1634, 1691 - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -16,7 +14,10 @@ using System.Management.Automation.Internal; using System.Management.Automation.Language; using System.Management.Automation.Remoting; +using System.Management.Automation.Remoting.Server; using System.Management.Automation.Runspaces; +using System.Management.Automation.Security; +using System.Management.Automation.Subsystem.Feedback; using System.Management.Automation.Tracing; using System.Reflection; using System.Runtime; @@ -54,6 +55,10 @@ internal sealed partial class ConsoleHost 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)] @@ -68,6 +73,9 @@ internal sealed partial class ConsoleHost /// /// Help text for minishell. This is displayed on 'minishell -?'. /// + /// + /// True when an external caller provides an InitialSessionState object, which can conflict with '-ConfigurationFile' argument. + /// /// /// The exit code for the shell. /// @@ -94,7 +102,10 @@ internal sealed partial class ConsoleHost /// Anyone checking the exit code of the shell or monitor can mask off the high word to determine the exit code passed /// by the script that the shell last executed. /// - internal static int Start(string bannerText, string helpText) + internal static int Start( + string bannerText, + string helpText, + bool issProvidedExternally) { #if DEBUG if (Environment.GetEnvironmentVariable("POWERSHELL_DEBUG_STARTUP") != null) @@ -106,11 +117,29 @@ internal static int Start(string bannerText, string helpText) } #endif - // put PSHOME in front of PATH so that calling `powershell` within `powershell` always starts the same running version + // Check for external InitialSessionState configuration conflict with '-ConfigurationFile' argument. + if (issProvidedExternally && !string.IsNullOrEmpty(s_cpp.ConfigurationFile)) + { + throw new ConsoleHostStartupException(ConsoleHostStrings.ShellCannotBeStartedWithConfigConflict); + } + + // Put PSHOME in front of PATH so that calling `pwsh` within `pwsh` always starts the same running version. string path = Environment.GetEnvironmentVariable("PATH"); - string pshome = Utils.DefaultPowerShellAppBase + Path.PathSeparator; + string pshome = Utils.DefaultPowerShellAppBase; + string dotnetToolsPathSegment = $"{Path.DirectorySeparatorChar}.store{Path.DirectorySeparatorChar}powershell{Path.DirectorySeparatorChar}"; + + int index = pshome.IndexOf(dotnetToolsPathSegment, StringComparison.Ordinal); + if (index > 0) + { + // We're running PowerShell global tool. In this case the real entry executable should be the 'pwsh' + // or 'pwsh.exe' within the tool folder which should be the path right before the '\.store', not what + // PSHome is pointing to. + pshome = pshome[0..index]; + } - // to not impact startup perf, we don't remove duplicates, but we avoid adding a duplicate to the front + pshome += Path.PathSeparator; + + // To not impact startup perf, we don't remove duplicates, but we avoid adding a duplicate to the front // we also don't handle the edge case where PATH only contains $PSHOME if (string.IsNullOrEmpty(path)) { @@ -164,7 +193,7 @@ internal static int Start(string bannerText, string helpText) // Alternatively, we could call s_theConsoleHost.UI.WriteLine(s_theConsoleHost.Version.ToString()); // or start up the engine and retrieve the information via $psversiontable.GitCommitId // but this returns the semantic version and avoids executing a script - s_theConsoleHost.UI.WriteLine("PowerShell " + PSVersionInfo.GitCommitId); + s_theConsoleHost.UI.WriteLine($"PowerShell {PSVersionInfo.GitCommitId}"); return 0; } @@ -172,10 +201,7 @@ internal static int Start(string bannerText, string helpText) if ((s_cpp.ServerMode && s_cpp.NamedPipeServerMode) || (s_cpp.ServerMode && s_cpp.SocketServerMode) || (s_cpp.NamedPipeServerMode && s_cpp.SocketServerMode)) { s_tracer.TraceError("Conflicting server mode parameters, parameters must be used exclusively."); - if (s_theConsoleHost != null) - { - s_theConsoleHost.ui.WriteErrorLine(ConsoleHostStrings.ConflictingServerModeParameters); - } + s_theConsoleHost?.ui.WriteErrorLine(ConsoleHostStrings.ConflictingServerModeParameters); return ExitCodeBadCommandLineParameter; } @@ -183,36 +209,46 @@ internal static int Start(string bannerText, string helpText) #if !UNIX TaskbarJumpList.CreateRunAsAdministratorJumpList(); #endif - // First check for and handle PowerShell running in a server mode. if (s_cpp.ServerMode) { - ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("ServerMode"); + ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("ServerMode", s_cpp.ParametersUsedAsDouble); ProfileOptimization.StartProfile("StartupProfileData-ServerMode"); - System.Management.Automation.Remoting.Server.OutOfProcessMediator.Run(s_cpp.InitialCommand, s_cpp.WorkingDirectory); + StdIOProcessMediator.Run( + initialCommand: s_cpp.InitialCommand, + workingDirectory: s_cpp.WorkingDirectory, + configurationName: null, + configurationFile: s_cpp.ConfigurationFile, + combineErrOutStream: false); exitCode = 0; } - else if (s_cpp.NamedPipeServerMode) + else if (s_cpp.SSHServerMode) { - ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("NamedPipe"); - ProfileOptimization.StartProfile("StartupProfileData-NamedPipeServerMode"); - System.Management.Automation.Remoting.RemoteSessionNamedPipeServer.RunServerMode( - s_cpp.ConfigurationName); + ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("SSHServer", s_cpp.ParametersUsedAsDouble); + ProfileOptimization.StartProfile("StartupProfileData-SSHServerMode"); + StdIOProcessMediator.Run( + initialCommand: s_cpp.InitialCommand, + workingDirectory: null, + configurationName: null, + configurationFile: s_cpp.ConfigurationFile, + combineErrOutStream: true); exitCode = 0; } - else if (s_cpp.SSHServerMode) + else if (s_cpp.NamedPipeServerMode) { - ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("SSHServer"); - ProfileOptimization.StartProfile("StartupProfileData-SSHServerMode"); - System.Management.Automation.Remoting.Server.SSHProcessMediator.Run(s_cpp.InitialCommand); + ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("NamedPipe", s_cpp.ParametersUsedAsDouble); + ProfileOptimization.StartProfile("StartupProfileData-NamedPipeServerMode"); + RemoteSessionNamedPipeServer.RunServerMode( + configurationName: s_cpp.ConfigurationName); exitCode = 0; } else if (s_cpp.SocketServerMode) { - ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("SocketServerMode"); + ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("SocketServerMode", s_cpp.ParametersUsedAsDouble); ProfileOptimization.StartProfile("StartupProfileData-SocketServerMode"); - System.Management.Automation.Remoting.Server.HyperVSocketMediator.Run(s_cpp.InitialCommand, - s_cpp.ConfigurationName); + HyperVSocketMediator.Run( + initialCommand: s_cpp.InitialCommand, + configurationName: s_cpp.ConfigurationName); exitCode = 0; } else @@ -243,20 +279,30 @@ internal static int Start(string bannerText, string helpText) PSHost.IsStdOutputRedirected = Console.IsOutputRedirected; // Send startup telemetry for ConsoleHost startup - ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("Normal"); + ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("Normal", s_cpp.ParametersUsedAsDouble); exitCode = s_theConsoleHost.Run(s_cpp, false); } } finally { +#pragma warning disable IDE0031 if (s_theConsoleHost != null) { #if LEGACYTELEMETRY TelemetryAPI.ReportExitTelemetry(s_theConsoleHost); +#endif +#if UNIX + if (s_theConsoleHost.IsInteractive && s_theConsoleHost.UI.SupportsVirtualTerminal) + { + // https://github.com/dotnet/runtime/issues/27626 leaves terminal in application mode + // for now, we explicitly emit DECRST 1 sequence + s_theConsoleHost.UI.Write(DECCKM_OFF); + } #endif s_theConsoleHost.Dispose(); } +#pragma warning restore IDE0031 } unchecked @@ -281,8 +327,8 @@ internal static void ParseCommandLine(string[] args) PowerShellConfig.Instance.SetSystemConfigFilePath(s_cpp.SettingsFile); } - // Check registry setting for a Group Policy ConfigurationName entry and - // use it to override anything set by the user. + // Check registry setting for a Group Policy ConfigurationName entry, + // and use it to override anything set by the user on the command line. // It depends on setting file so 'SetSystemConfigFilePath()' should be called before. s_cpp.ConfigurationName = CommandLineParameterParser.GetConfigurationNameFromGroupPolicy(); } @@ -398,7 +444,7 @@ private static bool BreakIntoDebugger() /// if true, then flag the parent ConsoleHost that it should shutdown the session. If false, then only the current /// executing instance is stopped. /// - /// + /// private static void SpinUpBreakHandlerThread(bool shouldEndSession) { ConsoleHost host = ConsoleHost.SingletonInstance; @@ -413,7 +459,7 @@ private static void SpinUpBreakHandlerThread(bool shouldEndSession) host.ShouldEndSession = shouldEndSession; } - // Creation of the tread and starting it should be an atomic operation. + // Creation of the thread and starting it should be an atomic operation. // otherwise the code in Run method can get instance of the breakhandlerThread // after it is created and before started and call join on it. This will result // in ThreadStateException. @@ -464,10 +510,7 @@ private static void HandleBreak() if (runspaceRef != null) { var runspace = runspaceRef.Runspace; - if (runspace != null) - { - runspace.Close(); - } + runspace?.Close(); } } } @@ -579,12 +622,18 @@ public override PSHostUserInterface UI /// /// See base class. /// - public void PushRunspace(Runspace newRunspace) + public void PushRunspace(Runspace runspace) { - if (_runspaceRef == null) { return; } + if (_runspaceRef == null) + { + return; + } + + if (runspace is not RemoteRunspace remoteRunspace) + { + throw new ArgumentException(ConsoleHostStrings.PushRunspaceNotRemote, nameof(runspace)); + } - RemoteRunspace remoteRunspace = newRunspace as RemoteRunspace; - Dbg.Assert(remoteRunspace != null, "Expected remoteRunspace != null"); remoteRunspace.StateChanged += HandleRemoteRunspaceStateChanged; // Unsubscribe the local session debugger. @@ -714,7 +763,10 @@ public Runspace Runspace { get { - if (this.RunspaceRef == null) { return null; } + if (this.RunspaceRef == null) + { + return null; + } return this.RunspaceRef.Runspace; } @@ -729,7 +781,10 @@ internal LocalRunspace LocalRunspace return RunspaceRef.OldRunspace as LocalRunspace; } - if (RunspaceRef == null) { return null; } + if (RunspaceRef == null) + { + return null; + } return RunspaceRef.Runspace as LocalRunspace; } @@ -741,7 +796,7 @@ public class ConsoleColorProxy public ConsoleColorProxy(ConsoleHostUserInterface ui) { - if (ui == null) throw new ArgumentNullException(nameof(ui)); + ArgumentNullException.ThrowIfNull(ui); _ui = ui; } @@ -934,7 +989,11 @@ public override PSObject PrivateData { get { - if (ui == null) return null; + if (ui == null) + { + return null; + } + return _consoleColorProxy ??= PSObject.AsPSObject(new ConsoleColorProxy(ui)); } } @@ -1051,18 +1110,16 @@ public override void NotifyBeginApplication() { lock (hostGlobalLock) { - ++_beginApplicationNotifyCount; - if (_beginApplicationNotifyCount == 1) + if (++_beginApplicationNotifyCount == 1) { - // save the window title when first notified. - + // Save the window title when first notified. _savedWindowTitle = ui.RawUI.WindowTitle; #if !UNIX if (_initialConsoleMode != ConsoleControl.ConsoleModes.Unknown) { - var activeScreenBufferHandle = ConsoleControl.GetActiveScreenBufferHandle(); - _savedConsoleMode = ConsoleControl.GetMode(activeScreenBufferHandle); - ConsoleControl.SetMode(activeScreenBufferHandle, _initialConsoleMode); + var outputHandle = ConsoleControl.GetActiveScreenBufferHandle(); + _savedConsoleMode = ConsoleControl.GetMode(outputHandle); + ConsoleControl.SetMode(outputHandle, _initialConsoleMode); } #endif } @@ -1077,17 +1134,26 @@ public override void NotifyEndApplication() { lock (hostGlobalLock) { - Dbg.Assert(_beginApplicationNotifyCount > 0, "Not running an executable - NotifyBeginApplication was not called!"); - --_beginApplicationNotifyCount; - if (_beginApplicationNotifyCount == 0) + if (--_beginApplicationNotifyCount == 0) { - // restore the window title when the last application started has ended. - + // Restore the window title when the last application started has ended. ui.RawUI.WindowTitle = _savedWindowTitle; #if !UNIX if (_savedConsoleMode != ConsoleControl.ConsoleModes.Unknown) { ConsoleControl.SetMode(ConsoleControl.GetActiveScreenBufferHandle(), _savedConsoleMode); + if (_savedConsoleMode.HasFlag(ConsoleControl.ConsoleModes.VirtualTerminal)) + { + // If the console output mode we just set already has 'VirtualTerminal' turned on, + // we don't need to try turn on the VT mode separately. + return; + } + } + + if (ui.SupportsVirtualTerminal) + { + // Re-enable VT mode if it was previously enabled, as a native command may have turned it off. + ui.TryTurnOnVirtualTerminal(); } #endif } @@ -1222,15 +1288,8 @@ private void Dispose(bool isDisposingNotFinalizing) StopTranscribing(); } - if (_outputSerializer != null) - { - _outputSerializer.End(); - } - - if (_errorSerializer != null) - { - _errorSerializer.End(); - } + _outputSerializer?.End(); + _errorSerializer?.End(); if (_runspaceRef != null) { @@ -1346,14 +1405,11 @@ internal WrappedSerializer OutputSerializer { get { - if (_outputSerializer == null) - { - _outputSerializer = - new WrappedSerializer( - OutputFormat, - "Output", - Console.IsOutputRedirected ? Console.Out : ConsoleTextWriter); - } + _outputSerializer ??= + new WrappedSerializer( + OutputFormat, + "Output", + Console.IsOutputRedirected ? Console.Out : ConsoleTextWriter); return _outputSerializer; } @@ -1363,14 +1419,11 @@ internal WrappedSerializer ErrorSerializer { get { - if (_errorSerializer == null) - { - _errorSerializer = - new WrappedSerializer( - ErrorFormat, - "Error", - Console.IsErrorRedirected ? Console.Error : ConsoleTextWriter); - } + _errorSerializer ??= + new WrappedSerializer( + ErrorFormat, + "Error", + Console.IsErrorRedirected ? Console.Error : ConsoleTextWriter); return _errorSerializer; } @@ -1462,7 +1515,7 @@ private uint Run(CommandLineParameterParser cpp, bool isPrestartWarned) // NTRAID#Windows Out Of Band Releases-915506-2005/09/09 // Removed HandleUnexpectedExceptions infrastructure - exitCode = DoRunspaceLoop(cpp.InitialCommand, cpp.SkipProfiles, cpp.Args, cpp.StaMode, cpp.ConfigurationName); + exitCode = DoRunspaceLoop(cpp.InitialCommand, cpp.SkipProfiles, cpp.Args, cpp.StaMode, cpp.ConfigurationName, cpp.ConfigurationFile); } while (false); @@ -1475,16 +1528,25 @@ private uint Run(CommandLineParameterParser cpp, bool isPrestartWarned) /// /// The process exit code to be returned by Main. /// - private uint DoRunspaceLoop(string initialCommand, bool skipProfiles, Collection initialCommandArgs, bool staMode, string configurationName) + private uint DoRunspaceLoop( + string initialCommand, + bool skipProfiles, + Collection initialCommandArgs, + bool staMode, + string configurationName, + string configurationFilePath) { ExitCode = ExitCodeSuccess; while (!ShouldEndSession) { - RunspaceCreationEventArgs args = new RunspaceCreationEventArgs(initialCommand, skipProfiles, staMode, configurationName, initialCommandArgs); + RunspaceCreationEventArgs args = new RunspaceCreationEventArgs(initialCommand, skipProfiles, staMode, configurationName, configurationFilePath, initialCommandArgs); CreateRunspace(args); - if (ExitCode == ExitCodeInitFailure) { break; } + if (ExitCode == ExitCodeInitFailure) + { + break; + } if (!_noExit) { @@ -1557,14 +1619,12 @@ private Exception InitializeRunspaceHelper(string command, Executor exec, Execut return e; } - private void CreateRunspace(object runspaceCreationArgs) + private void CreateRunspace(RunspaceCreationEventArgs runspaceCreationArgs) { - RunspaceCreationEventArgs args = null; try { - args = runspaceCreationArgs as RunspaceCreationEventArgs; - Dbg.Assert(args != null, "Event Arguments to CreateRunspace should not be null"); - DoCreateRunspace(args.InitialCommand, args.SkipProfiles, args.StaMode, args.ConfigurationName, args.InitialCommandArgs); + Dbg.Assert(runspaceCreationArgs != null, "Arguments to CreateRunspace should not be null."); + DoCreateRunspace(runspaceCreationArgs); } catch (ConsoleHostStartupException startupException) { @@ -1576,7 +1636,7 @@ private void CreateRunspace(object runspaceCreationArgs) /// /// 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-firendly enough as of today. + /// since PSReadLine is not accessibility-friendly enough as of today. /// private bool IsScreenReaderActive() { @@ -1589,7 +1649,7 @@ private bool IsScreenReaderActive() 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://docs.microsoft.com/windows/win32/api/winuser/nf-winuser-systemparametersinfoa about the + // 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; @@ -1619,12 +1679,33 @@ private static bool LoadPSReadline() /// Opens and Initializes the Host's sole Runspace. Processes the startup scripts and runs any command passed on the /// command line. /// - private void DoCreateRunspace(string initialCommand, bool skipProfiles, bool staMode, string configurationName, Collection initialCommandArgs) + /// Runspace creation event arguments. + private void DoCreateRunspace(RunspaceCreationEventArgs args) { - Dbg.Assert(_runspaceRef == null, "runspace should be null"); + Dbg.Assert(_runspaceRef == null, "_runspaceRef field should be null"); Dbg.Assert(DefaultInitialSessionState != null, "DefaultInitialSessionState should not be null"); s_runspaceInitTracer.WriteLine("Calling RunspaceFactory.CreateRunspace"); + // Use session configuration file if provided. + bool customConfigurationProvided = false; + if (!string.IsNullOrEmpty(args.ConfigurationFilePath)) + { + try + { + // Replace DefaultInitialSessionState with the initial state configuration defined by the file. + DefaultInitialSessionState = InitialSessionState.CreateFromSessionConfigurationFile( + path: args.ConfigurationFilePath, + roleVerifier: null, + validateFile: true); + } + catch (Exception ex) + { + throw new ConsoleHostStartupException(ConsoleHostStrings.ShellCannotBeStarted, ex); + } + + customConfigurationProvided = true; + } + try { Runspace consoleRunspace = null; @@ -1640,7 +1721,7 @@ private void DoCreateRunspace(string initialCommand, bool skipProfiles, bool sta // powershell -command "Update-Module PSReadline" // This should work just fine as long as no other instances of PowerShell are running. ReadOnlyCollection defaultImportModulesList = null; - if (LoadPSReadline()) + if (!customConfigurationProvided && LoadPSReadline()) { if (IsScreenReaderActive()) { @@ -1655,7 +1736,7 @@ private void DoCreateRunspace(string initialCommand, bool skipProfiles, bool sta consoleRunspace = RunspaceFactory.CreateRunspace(this, DefaultInitialSessionState); try { - OpenConsoleRunspace(consoleRunspace, staMode); + OpenConsoleRunspace(consoleRunspace, args.StaMode); } catch (Exception) { @@ -1675,7 +1756,7 @@ private void DoCreateRunspace(string initialCommand, bool skipProfiles, bool sta } consoleRunspace = RunspaceFactory.CreateRunspace(this, DefaultInitialSessionState); - OpenConsoleRunspace(consoleRunspace, staMode); + OpenConsoleRunspace(consoleRunspace, args.StaMode); } Runspace.PrimaryRunspace = consoleRunspace; @@ -1707,7 +1788,7 @@ private void DoCreateRunspace(string initialCommand, bool skipProfiles, bool sta _readyForInputTimeInMS = (DateTime.Now - Process.GetCurrentProcess().StartTime).TotalMilliseconds; #endif - DoRunspaceInitialization(skipProfiles, initialCommand, configurationName, initialCommandArgs); + DoRunspaceInitialization(args); } private static void OpenConsoleRunspace(Runspace runspace, bool staMode) @@ -1724,7 +1805,7 @@ private static void OpenConsoleRunspace(Runspace runspace, bool staMode) runspace.Open(); } - private void DoRunspaceInitialization(bool skipProfiles, string initialCommand, string configurationName, Collection initialCommandArgs) + private void DoRunspaceInitialization(RunspaceCreationEventArgs args) { if (_runspaceRef.Runspace.Debugger != null) { @@ -1759,13 +1840,13 @@ private void DoRunspaceInitialization(bool skipProfiles, string initialCommand, } } - if (!string.IsNullOrEmpty(configurationName)) + if (!string.IsNullOrEmpty(args.ConfigurationName)) { // If an endpoint configuration is specified then create a loop-back remote runspace targeting // the endpoint and push onto runspace ref stack. Ignore profile and configuration scripts. try { - RemoteRunspace remoteRunspace = HostUtilities.CreateConfiguredRunspace(configurationName, this); + RemoteRunspace remoteRunspace = HostUtilities.CreateConfiguredRunspace(args.ConfigurationName, this); remoteRunspace.ShouldCloseOnPop = true; PushRunspace(remoteRunspace); @@ -1782,8 +1863,36 @@ private void DoRunspaceInitialization(bool skipProfiles, string initialCommand, const string shellId = "Microsoft.PowerShell"; // If the system lockdown policy says "Enforce", do so. Do this after types / formatting, default functions, etc - // are loaded so that they are trusted. (Validation of their signatures is done in F&O) - Utils.EnforceSystemLockDownLanguageMode(_runspaceRef.Runspace.ExecutionContext); + // are loaded so that they are trusted. (Validation of their signatures is done in F&O). + var languageMode = Utils.EnforceSystemLockDownLanguageMode(_runspaceRef.Runspace.ExecutionContext); + // When displaying banner, also display the language mode if running in any restricted mode. + if (s_cpp.ShowBanner) + { + switch (languageMode) + { + case PSLanguageMode.ConstrainedLanguage: + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + s_theConsoleHost.UI.WriteLine(ManagedEntranceStrings.ShellBannerCLMode); + } + else + { + s_theConsoleHost.UI.WriteLine(ManagedEntranceStrings.ShellBannerCLAuditMode); + } + break; + + case PSLanguageMode.NoLanguage: + s_theConsoleHost.UI.WriteLine(ManagedEntranceStrings.ShellBannerNLMode); + break; + + case PSLanguageMode.RestrictedLanguage: + s_theConsoleHost.UI.WriteLine(ManagedEntranceStrings.ShellBannerRLMode); + break; + + default: + break; + } + } string allUsersProfile = HostUtilities.GetFullProfileFileName(null, false); string allUsersHostSpecificProfile = HostUtilities.GetFullProfileFileName(shellId, false); @@ -1800,7 +1909,7 @@ private void DoRunspaceInitialization(bool skipProfiles, string initialCommand, currentUserProfile, currentUserHostSpecificProfile)); - if (!skipProfiles) + if (!args.SkipProfiles) { // Run the profiles. // Profiles are run in the following order: @@ -1818,7 +1927,7 @@ private void DoRunspaceInitialization(bool skipProfiles, string initialCommand, sw.Stop(); var profileLoadTimeInMs = sw.ElapsedMilliseconds; - if (profileLoadTimeInMs > 500 && s_cpp.ShowBanner) + if (s_cpp.ShowBanner && !s_cpp.NoProfileLoadTime && profileLoadTimeInMs > 500) { Console.Error.WriteLine(ConsoleHostStrings.SlowProfileLoadingMessage, profileLoadTimeInMs); } @@ -1845,24 +1954,26 @@ private void DoRunspaceInitialization(bool skipProfiles, string initialCommand, Pipeline tempPipeline = exec.CreatePipeline(); Command c; +#if UNIX // if file doesn't have .ps1 extension, we read the contents and treat it as a script to support shebang with no .ps1 extension usage - if (!Path.GetExtension(filePath).Equals(".ps1", StringComparison.OrdinalIgnoreCase)) + if (!filePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) { string script = File.ReadAllText(filePath); c = new Command(script, isScript: true, useLocalScope: false); } else +#endif { c = new Command(filePath, false, false); } tempPipeline.Commands.Add(c); - if (initialCommandArgs != null) + if (args.InitialCommandArgs != null) { // add the args passed to the command. - foreach (CommandParameter p in initialCommandArgs) + foreach (CommandParameter p in args.InitialCommandArgs) { c.Parameters.Add(p); } @@ -1919,19 +2030,19 @@ private void DoRunspaceInitialization(bool skipProfiles, string initialCommand, ReportException(e1, exec); } } - else if (!string.IsNullOrEmpty(initialCommand)) + else if (!string.IsNullOrEmpty(args.InitialCommand)) { // Run the command passed on the command line s_tracer.WriteLine("running initial command"); - Pipeline tempPipeline = exec.CreatePipeline(initialCommand, true); + Pipeline tempPipeline = exec.CreatePipeline(args.InitialCommand, true); - if (initialCommandArgs != null) + if (args.InitialCommandArgs != null) { // add the args passed to the command. - foreach (CommandParameter p in initialCommandArgs) + foreach (CommandParameter p in args.InitialCommandArgs) { tempPipeline.Commands[0].Parameters.Add(p); } @@ -1947,7 +2058,7 @@ private void DoRunspaceInitialization(bool skipProfiles, string initialCommand, ParseError[] errors; // Detect if they're using input. If so, read from it. - Ast parsedInput = Parser.ParseInput(initialCommand, out tokens, out errors); + Ast parsedInput = Parser.ParseInput(args.InitialCommand, out tokens, out errors); if (AstSearcher.IsUsingDollarInput(parsedInput)) { executionOptions |= Executor.ExecutionOptions.ReadInputObjects; @@ -2053,9 +2164,7 @@ private void ReportException(Exception e, Executor exec) // NTRAID#Windows OS Bugs-1143621-2005/04/08-sburns - IContainsErrorRecord icer = e as IContainsErrorRecord; - - if (icer != null) + if (e is IContainsErrorRecord icer) { error = icer.ErrorRecord; } @@ -2112,8 +2221,7 @@ private void ReportExceptionFallback(Exception e, string header) // See if the exception has an error record attached to it... ErrorRecord er = null; - IContainsErrorRecord icer = e as IContainsErrorRecord; - if (icer != null) + if (e is IContainsErrorRecord icer) er = icer.ErrorRecord; if (e is PSRemotingTransportException) @@ -2156,7 +2264,10 @@ private void OnExecutionSuspended(object sender, DebuggerStopEventArgs e) { // Check local runspace internalHost to see if debugging is enabled. LocalRunspace localrunspace = LocalRunspace; - if ((localrunspace != null) && !localrunspace.ExecutionContext.EngineHostInterface.DebuggerEnabled) { return; } + if ((localrunspace != null) && !localrunspace.ExecutionContext.EngineHostInterface.DebuggerEnabled) + { + return; + } _debuggerStopEventArgs = e; InputLoop baseLoop = null; @@ -2168,10 +2279,7 @@ private void OnExecutionSuspended(object sender, DebuggerStopEventArgs e) // For remote debugging block data coming from the main (not-nested) // running command. baseLoop = InputLoop.GetNonNestedLoop(); - if (baseLoop != null) - { - baseLoop.BlockCommandOutput(); - } + baseLoop?.BlockCommandOutput(); } // @@ -2216,10 +2324,7 @@ private void OnExecutionSuspended(object sender, DebuggerStopEventArgs e) finally { _debuggerStopEventArgs = null; - if (baseLoop != null) - { - baseLoop.ResumeCommandOutput(); - } + baseLoop?.ResumeCommandOutput(); } } @@ -2310,7 +2415,7 @@ private void WriteDebuggerMessage(string line) /// Neither this class' instances nor its static data is threadsafe. Caller is responsible for ensuring threadsafe /// access. /// - private class InputLoop + private sealed class InputLoop { internal static void RunNewInputLoop(ConsoleHost parent, bool isNested) { @@ -2419,25 +2524,18 @@ private void HandleRunspacePopped(object sender, EventArgs eventArgs) /// internal void Run(bool inputLoopIsNested) { - System.Management.Automation.Host.PSHostUserInterface c = _parent.UI; + PSHostUserInterface c = _parent.UI; ConsoleHostUserInterface ui = c as ConsoleHostUserInterface; Dbg.Assert(ui != null, "Host.UI should return an instance."); bool inBlockMode = false; - bool previousResponseWasEmpty = false; - StringBuilder inputBlock = new StringBuilder(); + // Use nullable so that we don't evaluate suggestions at startup. + bool? previousResponseWasEmpty = null; + var inputBlock = new StringBuilder(); while (!_parent.ShouldEndSession && !_shouldExit) { -#if !UNIX - if (ui.SupportsVirtualTerminal) - { - // need to re-enable VT mode if it was previously enabled as native commands may have turned it off - ui.TryTurnOnVtMode(); - } -#endif - try { _parent._isRunningPromptLoop = true; @@ -2450,7 +2548,6 @@ internal void Run(bool inputLoopIsNested) if (inBlockMode) { // use a special prompt that denotes block mode - prompt = ">> "; } else @@ -2461,9 +2558,16 @@ internal void Run(bool inputLoopIsNested) ui.WriteLine(); // Evaluate any suggestions - if (!previousResponseWasEmpty) + if (previousResponseWasEmpty == false) { - EvaluateSuggestions(ui); + if (ExperimentalFeature.IsEnabled(ExperimentalFeature.PSFeedbackProvider)) + { + EvaluateFeedbacks(ui); + } + else + { + EvaluateSuggestions(ui); + } } // Then output the prompt @@ -2472,15 +2576,20 @@ internal void Run(bool inputLoopIsNested) prompt = EvaluateDebugPrompt(); } - if (prompt == null) - { - prompt = EvaluatePrompt(); - } + prompt ??= EvaluatePrompt(); } ui.Write(prompt); } +#if UNIX + if (c.SupportsVirtualTerminal) + { + // enable DECCKM as .NET requires cursor keys to emit VT for Console class + c.Write(DECCKM_ON); + } +#endif + previousResponseWasEmpty = false; // There could be a profile. So there could be a user defined custom readline command line = ui.ReadLineWithTabCompletion(_exec); @@ -2584,6 +2693,14 @@ e is RemoteException || } else { +#if UNIX + if (c.SupportsVirtualTerminal) + { + // disable DECCKM to standard mode as applications may not expect VT for cursor keys + c.Write(DECCKM_OFF); + } +#endif + if (_parent.IsRunningAsync && !_parent.IsNested) { _exec.ExecuteCommandAsync(line, out e, Executor.ExecutionOptions.AddOutputter | Executor.ExecutionOptions.AddToHistory); @@ -2600,10 +2717,7 @@ e is RemoteException || bht = _parent._breakHandlerThread; } - if (bht != null) - { - bht.Join(); - } + bht?.Join(); // Once the pipeline has been executed, we toss any outstanding progress data and // take down the display. @@ -2642,8 +2756,7 @@ e is RemoteException || internal void BlockCommandOutput() { - RemotePipeline rCmdPipeline = _parent.runningCmd as RemotePipeline; - if (rCmdPipeline != null) + if (_parent.runningCmd is RemotePipeline rCmdPipeline) { rCmdPipeline.DrainIncomingData(); rCmdPipeline.SuspendIncomingData(); @@ -2656,8 +2769,7 @@ internal void BlockCommandOutput() internal void ResumeCommandOutput() { - RemotePipeline rCmdPipeline = _parent.runningCmd as RemotePipeline; - if (rCmdPipeline != null) + if (_parent.runningCmd is RemotePipeline rCmdPipeline) { rCmdPipeline.ResumeIncomingData(); } @@ -2685,7 +2797,7 @@ private bool HandleErrors(Exception e, string line, bool inBlockMode, ref String } else { - // an exception ocurred when the command was executed. Tell the user about it. + // an exception occurred when the command was executed. Tell the user about it. _parent.ReportException(e, _exec); } @@ -2747,8 +2859,7 @@ private static bool IsIncompleteParseException(Exception e) } // If it is remote exception ferret out the real exception. - RemoteException remoteException = e as RemoteException; - if (remoteException == null || remoteException.ErrorRecord == null) + if (e is not RemoteException remoteException || remoteException.ErrorRecord == null) { return false; } @@ -2756,6 +2867,29 @@ private static bool IsIncompleteParseException(Exception e) return remoteException.ErrorRecord.CategoryInfo.Reason == nameof(IncompleteParseException); } + private void EvaluateFeedbacks(ConsoleHostUserInterface ui) + { + // Output any training suggestions + try + { + List feedbacks = FeedbackHub.GetFeedback(_parent.Runspace); + if (feedbacks is null || feedbacks.Count is 0) + { + return; + } + + HostUtilities.RenderFeedback(feedbacks, ui); + } + 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 void EvaluateSuggestions(ConsoleHostUserInterface ui) { // Output any training suggestions @@ -2796,8 +2930,7 @@ private void EvaluateSuggestions(ConsoleHostUserInterface ui) private string EvaluatePrompt() { - Exception unused = null; - string promptString = _promptExec.ExecuteCommandAndGetResultAsString("prompt", out unused); + string promptString = _promptExec.ExecuteCommandAndGetResultAsString("prompt", out _); if (string.IsNullOrEmpty(promptString)) { @@ -2807,8 +2940,7 @@ private string EvaluatePrompt() // Check for the pushed runspace scenario. if (_isRunspacePushed) { - RemoteRunspace remoteRunspace = _parent.Runspace as RemoteRunspace; - if (remoteRunspace != null) + if (_parent.Runspace is RemoteRunspace remoteRunspace) { promptString = HostUtilities.GetRemotePrompt(remoteRunspace, promptString, _parent._inPushedConfiguredSession); } @@ -2842,13 +2974,9 @@ private string EvaluateDebugPrompt() PSObject prompt = output.ReadAndRemoveAt0(); string promptString = (prompt != null) ? (prompt.BaseObject as string) : null; - if (promptString != null) + if (promptString != null && _parent.Runspace is RemoteRunspace remoteRunspace) { - RemoteRunspace remoteRunspace = _parent.Runspace as RemoteRunspace; - if (remoteRunspace != null) - { - promptString = HostUtilities.GetRemotePrompt(remoteRunspace, promptString, _parent._inPushedConfiguredSession); - } + promptString = HostUtilities.GetRemotePrompt(remoteRunspace, promptString, _parent._inPushedConfiguredSession); } return promptString; @@ -2871,10 +2999,9 @@ private string EvaluateDebugPrompt() private static readonly Stack s_instanceStack = new Stack(); } - [Serializable] [SuppressMessage("Microsoft.Design", "CA1064:ExceptionsShouldBePublic", Justification = "This exception cannot be used outside of the console host application. It is not thrown by a library routine, only by an application.")] - private class ConsoleHostStartupException : Exception + private sealed class ConsoleHostStartupException : Exception { internal ConsoleHostStartupException() @@ -2888,14 +3015,6 @@ private class ConsoleHostStartupException : Exception { } - protected - ConsoleHostStartupException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) - : base(info, context) - { - } - internal ConsoleHostStartupException(string message, Exception innerException) : base(message, innerException) @@ -2925,7 +3044,7 @@ private class ConsoleHostStartupException : Exception private bool _isDisposed; internal ConsoleHostUserInterface ui; - internal Lazy ConsoleIn { get; } = new Lazy(() => Console.In); + internal Lazy ConsoleIn { get; } = new Lazy(static () => Console.In); private string _savedWindowTitle = string.Empty; private readonly Version _ver = PSVersionInfo.PSVersion; @@ -2984,12 +3103,14 @@ internal RunspaceCreationEventArgs( bool skipProfiles, bool staMode, string configurationName, + string configurationFilePath, Collection initialCommandArgs) { InitialCommand = initialCommand; SkipProfiles = skipProfiles; StaMode = staMode; ConfigurationName = configurationName; + ConfigurationFilePath = configurationFilePath; InitialCommandArgs = initialCommandArgs; } @@ -3001,6 +3122,8 @@ internal RunspaceCreationEventArgs( internal string ConfigurationName { get; set; } + internal string ConfigurationFilePath { get; set; } + internal Collection InitialCommandArgs { get; set; } } } // namespace diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostRawUserInterface.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostRawUserInterface.cs index 2a6ea89176d..58630f5b3c0 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostRawUserInterface.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostRawUserInterface.cs @@ -41,23 +41,16 @@ class ConsoleHostRawUserInterface : System.Management.Automation.Host.PSHostRawU // (we may load resources which can take some time) Task.Run(() => { - WindowsIdentity identity = WindowsIdentity.GetCurrent(); - WindowsPrincipal principal = new WindowsPrincipal(identity); + var identity = WindowsIdentity.GetCurrent(); + var principal = new WindowsPrincipal(identity); if (principal.IsInRole(WindowsBuiltInRole.Administrator)) { - string prefix = ConsoleHostRawUserInterfaceStrings.WindowTitleElevatedPrefix; - - // check using Regex if the window already has Administrator: prefix - // (i.e. from the parent console process) - string titlePattern = ConsoleHostRawUserInterfaceStrings.WindowTitleTemplate; - titlePattern = Regex.Escape(titlePattern) - .Replace(@"\{1}", ".*") - .Replace(@"\{0}", Regex.Escape(prefix)); - if (!Regex.IsMatch(this.WindowTitle, titlePattern)) + // Check if the window already has the "Administrator: " prefix (i.e. from the parent console process). + ReadOnlySpan prefix = ConsoleHostRawUserInterfaceStrings.WindowTitleElevatedPrefix; + ReadOnlySpan windowTitle = WindowTitle; + if (!windowTitle.StartsWith(prefix)) { - this.WindowTitle = StringUtil.Format(ConsoleHostRawUserInterfaceStrings.WindowTitleTemplate, - prefix, - this.WindowTitle); + WindowTitle = string.Concat(prefix, windowTitle); } } }); @@ -85,9 +78,8 @@ public override GetBufferInfo(out bufferInfo); ConsoleColor foreground; - ConsoleColor unused; - ConsoleControl.WORDToColor(bufferInfo.Attributes, out foreground, out unused); + ConsoleControl.WORDToColor(bufferInfo.Attributes, out foreground, out _); return foreground; } @@ -135,9 +127,8 @@ public override GetBufferInfo(out bufferInfo); ConsoleColor background; - ConsoleColor unused; - ConsoleControl.WORDToColor(bufferInfo.Attributes, out unused, out background); + ConsoleControl.WORDToColor(bufferInfo.Attributes, out _, out background); return background; } @@ -190,14 +181,15 @@ public override set { - // cursor position can't be outside the buffer area - - ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo; - - ConsoleHandle handle = GetBufferInfo(out bufferInfo); - - CheckCoordinateWithinBuffer(ref value, ref bufferInfo, "value"); - ConsoleControl.SetConsoleCursorPosition(handle, value); + try + { + Console.SetCursorPosition(value.X, value.Y); + } + catch (ArgumentOutOfRangeException) + { + // if screen buffer has changed, we cannot set it anywhere reasonable as the screen buffer + // might change again, so we ignore this + } } } @@ -362,8 +354,7 @@ public override } catch (HostException e) { - Win32Exception win32exception = e.InnerException as Win32Exception; - if (win32exception != null && + if (e.InnerException is Win32Exception win32exception && win32exception.NativeErrorCode == 0x57) { throw PSTraceSource.NewArgumentOutOfRangeException("value", value, @@ -456,7 +447,7 @@ public override } // if the new size will extend past the edge of screen buffer, then move the window position to try to - // accomodate that. + // accommodate that. ConsoleControl.SMALL_RECT r = bufferInfo.WindowRect; @@ -647,8 +638,7 @@ public override { int actualNumberOfInput = ConsoleControl.ReadConsoleInput(handle, ref inputRecords); Dbg.Assert(actualNumberOfInput == 1, - string.Format(CultureInfo.InvariantCulture, "ReadConsoleInput returns {0} number of input event records", - actualNumberOfInput)); + string.Create(CultureInfo.InvariantCulture, $"ReadConsoleInput returns {actualNumberOfInput} number of input event records")); if (actualNumberOfInput == 1) { if (((ConsoleControl.InputRecordEventTypes)inputRecords[0].EventType) == @@ -1540,7 +1530,7 @@ internal struct COORD public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "{0},{1}", X, Y); + return string.Create(CultureInfo.InvariantCulture, $"{X},{Y}"); } } @@ -1556,7 +1546,7 @@ internal struct SMALL_RECT public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "{0},{1},{2},{3}", Left, Top, Right, Bottom); + return string.Create(CultureInfo.InvariantCulture, $"{Left},{Top},{Right},{Bottom}"); } } diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs index 14425da82c0..9587d6cd553 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs @@ -60,11 +60,38 @@ internal ConsoleHostUserInterface(ConsoleHost parent) _parent = parent; _rawui = new ConsoleHostRawUserInterface(this); - SupportsVirtualTerminal = TryTurnOnVtMode(); + SupportsVirtualTerminal = true; _isInteractiveTestToolListening = false; + + // check if TERM env var is set + // `dumb` means explicitly don't use VT + // `xterm-mono` and `xtermm` means support VT, but emit plaintext + switch (Environment.GetEnvironmentVariable("TERM")) + { + case "dumb": + SupportsVirtualTerminal = false; + break; + case "xterm-mono": + case "xtermm": + PSStyle.Instance.OutputRendering = OutputRendering.PlainText; + break; + default: + break; + } + + // widely supported by CLI tools via https://no-color.org/ + if (Environment.GetEnvironmentVariable("NO_COLOR") != null) + { + PSStyle.Instance.OutputRendering = OutputRendering.PlainText; + } + + if (SupportsVirtualTerminal) + { + SupportsVirtualTerminal = TryTurnOnVirtualTerminal(); + } } - internal bool TryTurnOnVtMode() + internal bool TryTurnOnVirtualTerminal() { #if UNIX return true; @@ -72,16 +99,22 @@ internal bool TryTurnOnVtMode() try { // Turn on virtual terminal if possible. - // This might throw - not sure how exactly (no console), but if it does, we shouldn't fail to start. - var handle = ConsoleControl.GetActiveScreenBufferHandle(); - var m = ConsoleControl.GetMode(handle); - if (ConsoleControl.NativeMethods.SetConsoleMode(handle.DangerousGetHandle(), (uint)(m | ConsoleControl.ConsoleModes.VirtualTerminal))) + var outputHandle = ConsoleControl.GetActiveScreenBufferHandle(); + var outputMode = ConsoleControl.GetMode(outputHandle); + + if (outputMode.HasFlag(ConsoleControl.ConsoleModes.VirtualTerminal)) + { + return true; + } + + outputMode |= ConsoleControl.ConsoleModes.VirtualTerminal; + if (ConsoleControl.NativeMethods.SetConsoleMode(outputHandle.DangerousGetHandle(), (uint)outputMode)) { // We only know if vt100 is supported if the previous call actually set the new flag, older // systems ignore the setting. - m = ConsoleControl.GetMode(handle); - return (m & ConsoleControl.ConsoleModes.VirtualTerminal) != 0; + outputMode = ConsoleControl.GetMode(outputHandle); + return outputMode.HasFlag(ConsoleControl.ConsoleModes.VirtualTerminal); } } catch @@ -174,9 +207,8 @@ public override string ReadLine() HandleThrowOnReadAndPrompt(); // call our internal version such that it does not end input on a tab - ReadLineResult unused; - return ReadLine(false, string.Empty, out unused, true, true); + return ReadLine(false, string.Empty, out _, true, true); } /// @@ -223,7 +255,7 @@ public override SecureString ReadLineAsSecureString() /// the advantage is portability through abstraction. Does not support /// arrow key movement, but supports backspace. /// - /// + /// /// True to specify reading a SecureString; false reading a string /// /// @@ -300,20 +332,19 @@ private object ReadLineSafe(bool isSecureString, char? printToken) Coordinates originalCursorPos = _rawui.CursorPosition; + // + // read one char at a time so that we don't + // end up having a immutable string holding the + // secret in memory. + // + const int CharactersToRead = 1; + Span inputBuffer = stackalloc char[CharactersToRead + 1]; + while (true) { - // - // read one char at a time so that we don't - // end up having a immutable string holding the - // secret in memory. - // #if UNIX ConsoleKeyInfo keyInfo = Console.ReadKey(true); #else - const int CharactersToRead = 1; -#pragma warning disable CA2014 - Span inputBuffer = stackalloc char[CharactersToRead + 1]; -#pragma warning restore CA2014 string key = ConsoleControl.ReadConsole(handle, initialContentLength: 0, inputBuffer, charactersToRead: CharactersToRead, endOnTab: false, out _); #endif @@ -678,9 +709,14 @@ private void WriteLineToConsole() /// 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) @@ -702,7 +738,7 @@ private void WriteImpl(string value, bool newLine) } TextWriter writer = Console.IsOutputRedirected ? Console.Out : _parent.ConsoleTextWriter; - value = Utils.GetOutputString(value, isHost: true, SupportsVirtualTerminal, Console.IsOutputRedirected); + value = GetOutputString(value, SupportsVirtualTerminal); if (_parent.IsRunningAsync) { @@ -814,7 +850,10 @@ private void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, s /// public override void WriteLine(string value) { - this.WriteImpl(value, newLine: true); + lock (_instanceLock) + { + this.WriteImpl(value, newLine: true); + } } /// @@ -831,7 +870,10 @@ public override void WriteLine(string value) /// public override void WriteLine() { - this.WriteImpl(Environment.NewLine, newLine: false); + lock (_instanceLock) + { + this.WriteImpl(Environment.NewLine, newLine: false); + } } #region Word Wrapping @@ -931,7 +973,7 @@ internal List WrapText(string text, int maxWidthInBufferCells) int w = maxWidthInBufferCells - cellCounter; Dbg.Assert(w < e.Current.CellCount, "width remaining should be less than size of word"); - line.Append(e.Current.Text.Substring(0, w)); + line.Append(e.Current.Text.AsSpan(0, w)); l = line.ToString(); Dbg.Assert(RawUI.LengthInBufferCells(l) == maxWidthInBufferCells, "line should exactly fit"); @@ -1175,10 +1217,6 @@ internal string WrapToCurrentWindowWidth(string text) /// public override void WriteDebugLine(string message) { - // don't lock here as WriteLine is already protected. - bool unused; - message = HostUtilities.RemoveGuidFromMessage(message, out unused); - // We should write debug to error stream only if debug is redirected.) if (_parent.ErrorFormat == Serialization.DataFormat.XML) { @@ -1186,9 +1224,9 @@ public override void WriteDebugLine(string message) } else { - if (SupportsVirtualTerminal && ExperimentalFeature.IsEnabled("PSAnsiRendering")) + if (SupportsVirtualTerminal) { - WriteLine(Utils.GetFormatStyleString(Utils.FormatStyle.Debug) + StringUtil.Format(ConsoleHostUserInterfaceStrings.DebugFormatString, message) + PSStyle.Instance.Reset); + WriteLine(GetFormatStyleString(FormatStyle.Debug) + StringUtil.Format(ConsoleHostUserInterfaceStrings.DebugFormatString, message) + PSStyle.Instance.Reset); } else { @@ -1236,10 +1274,6 @@ public override void WriteInformation(InformationRecord record) /// public override void WriteVerboseLine(string message) { - // don't lock here as WriteLine is already protected. - bool unused; - message = HostUtilities.RemoveGuidFromMessage(message, out unused); - // NTRAID#Windows OS Bugs-1061752-2004/12/15-sburns should read a skin setting here...) if (_parent.ErrorFormat == Serialization.DataFormat.XML) { @@ -1247,9 +1281,9 @@ public override void WriteVerboseLine(string message) } else { - if (SupportsVirtualTerminal && ExperimentalFeature.IsEnabled("PSAnsiRendering")) + if (SupportsVirtualTerminal) { - WriteLine(Utils.GetFormatStyleString(Utils.FormatStyle.Verbose) + StringUtil.Format(ConsoleHostUserInterfaceStrings.VerboseFormatString, message) + PSStyle.Instance.Reset); + WriteLine(GetFormatStyleString(FormatStyle.Verbose) + StringUtil.Format(ConsoleHostUserInterfaceStrings.VerboseFormatString, message) + PSStyle.Instance.Reset); } else { @@ -1280,10 +1314,6 @@ public override void WriteVerboseLine(string message) /// public override void WriteWarningLine(string message) { - // don't lock here as WriteLine is already protected. - bool unused; - message = HostUtilities.RemoveGuidFromMessage(message, out unused); - // NTRAID#Windows OS Bugs-1061752-2004/12/15-sburns should read a skin setting here...) if (_parent.ErrorFormat == Serialization.DataFormat.XML) { @@ -1291,9 +1321,9 @@ public override void WriteWarningLine(string message) } else { - if (SupportsVirtualTerminal && ExperimentalFeature.IsEnabled("PSAnsiRendering")) + if (SupportsVirtualTerminal) { - WriteLine(Utils.GetFormatStyleString(Utils.FormatStyle.Warning) + StringUtil.Format(ConsoleHostUserInterfaceStrings.WarningFormatString, message) + PSStyle.Instance.Reset); + WriteLine(GetFormatStyleString(FormatStyle.Warning) + StringUtil.Format(ConsoleHostUserInterfaceStrings.WarningFormatString, message) + PSStyle.Instance.Reset); } else { @@ -1308,24 +1338,10 @@ public override void WriteWarningLine(string message) /// /// Invoked by CommandBase.WriteProgress to display a progress record. /// - public override void WriteProgress(Int64 sourceId, ProgressRecord record) + 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; - } - - bool matchPattern; - string currentOperation = HostUtilities.RemoveIdentifierInfoFromMessage(record.CurrentOperation, out matchPattern); - if (matchPattern) - { - record = new ProgressRecord(record) { CurrentOperation = currentOperation }; - } - - // We allow only one thread at a time to update the progress state.) if (_parent.ErrorFormat == Serialization.DataFormat.XML) { PSObject obj = new PSObject(); @@ -1333,8 +1349,14 @@ public override void WriteProgress(Int64 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); @@ -1363,7 +1385,7 @@ public override void WriteErrorLine(string value) { if (writer == _parent.ConsoleTextWriter) { - if (SupportsVirtualTerminal && ExperimentalFeature.IsEnabled("PSAnsiRendering")) + if (SupportsVirtualTerminal) { WriteLine(value); } @@ -1463,7 +1485,10 @@ internal string ReadLine(bool endOnTab, string initialContent, out ReadLineResul result = ReadLineResult.endedOnEnter; // If the test hook is set, read from it. - if (s_h != null) return s_h.ReadLine(); + if (s_h != null) + { + return s_h.ReadLine(); + } string restOfLine = null; @@ -1508,14 +1533,21 @@ private string ReadLineFromFile(string initialContent) } var c = unchecked((char)inC); - if (!NoPrompt) Console.Out.Write(c); + if (!NoPrompt) + { + Console.Out.Write(c); + } if (c == '\r') { // Treat as newline, but consume \n if there is one. if (consoleIn.Peek() == '\n') { - if (!NoPrompt) Console.Out.Write('\n'); + if (!NoPrompt) + { + Console.Out.Write('\n'); + } + consoleIn.Read(); } @@ -1882,22 +1914,24 @@ private char GetCharacterUnderCursor(Coordinates cursorPosition) #endif /// - /// Strip nulls from a string... + /// Strip nulls from a string. /// /// The string to process. - /// The string with any \0 characters removed... + /// The string with any '\0' characters removed. private static string RemoveNulls(string input) { - if (input.Contains('\0')) + if (!input.Contains('\0')) { return input; } - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(input.Length); foreach (char c in input) { if (c != '\0') + { sb.Append(c); + } } return sb.ToString(); @@ -1989,8 +2023,7 @@ internal string ReadLineWithTabCompletion(Executor exec) var completionResult = commandCompletion.GetNextResult(rlResult == ReadLineResult.endedOnTab); if (completionResult != null) { - completedInput = completionInput.Substring(0, commandCompletion.ReplacementIndex) - + completionResult.CompletionText; + completedInput = string.Concat(completionInput.AsSpan(0, commandCompletion.ReplacementIndex), completionResult.CompletionText); } else { @@ -2087,20 +2120,20 @@ private static void SendLeftArrows(int length) for (int i = 0; i < length; i++) { var down = new ConsoleControl.INPUT(); - down.Type = (UInt32)ConsoleControl.InputType.Keyboard; + down.Type = (uint)ConsoleControl.InputType.Keyboard; down.Data.Keyboard = new ConsoleControl.KeyboardInput(); - down.Data.Keyboard.Vk = (UInt16)ConsoleControl.VirtualKeyCode.Left; + down.Data.Keyboard.Vk = (ushort)ConsoleControl.VirtualKeyCode.Left; down.Data.Keyboard.Scan = 0; down.Data.Keyboard.Flags = 0; down.Data.Keyboard.Time = 0; down.Data.Keyboard.ExtraInfo = IntPtr.Zero; var up = new ConsoleControl.INPUT(); - up.Type = (UInt32)ConsoleControl.InputType.Keyboard; + up.Type = (uint)ConsoleControl.InputType.Keyboard; up.Data.Keyboard = new ConsoleControl.KeyboardInput(); - up.Data.Keyboard.Vk = (UInt16)ConsoleControl.VirtualKeyCode.Left; + up.Data.Keyboard.Vk = (ushort)ConsoleControl.VirtualKeyCode.Left; up.Data.Keyboard.Scan = 0; - up.Data.Keyboard.Flags = (UInt32)ConsoleControl.KeyboardFlag.KeyUp; + up.Data.Keyboard.Flags = (uint)ConsoleControl.KeyboardFlag.KeyUp; up.Data.Keyboard.Time = 0; up.Data.Keyboard.ExtraInfo = IntPtr.Zero; @@ -2228,7 +2261,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/ConsoleHostUserInterfaceProgress.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceProgress.cs index 406c36c8a00..1c9d37ea9fb 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceProgress.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceProgress.cs @@ -3,6 +3,7 @@ using System; using System.Management.Automation; +using System.Management.Automation.Host; using System.Threading; using Dbg = System.Management.Automation.Diagnostics; @@ -10,7 +11,7 @@ namespace Microsoft.PowerShell { internal partial - class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInterface + class ConsoleHostUserInterface : PSHostUserInterface { /// /// Called at the end of a prompt loop to take down any progress display that might have appeared and purge any @@ -48,7 +49,7 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt _pendingProgress = null; - if (ExperimentalFeature.IsEnabled(ExperimentalFeature.PSAnsiProgressFeatureName) && PSStyle.Instance.Progress.UseOSCIndicator) + if (SupportsVirtualTerminal && !PSHost.IsStdOutputRedirected && PSStyle.Instance.Progress.UseOSCIndicator) { // OSC sequence to turn off progress indicator // https://github.com/microsoft/terminal/issues/6700 @@ -63,7 +64,7 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt /// private void - HandleIncomingProgressRecord(Int64 sourceId, ProgressRecord record) + HandleIncomingProgressRecord(long sourceId, ProgressRecord record) { Dbg.Assert(record != null, "record should not be null"); @@ -99,7 +100,7 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt { // Update the progress pane only when the timer set up the update flag or WriteProgress is completed. // As a result, we do not block WriteProgress and whole script and eliminate unnecessary console locks and updates. - if (ExperimentalFeature.IsEnabled(ExperimentalFeature.PSAnsiProgressFeatureName) && PSStyle.Instance.Progress.UseOSCIndicator) + if (SupportsVirtualTerminal && !PSHost.IsStdOutputRedirected && PSStyle.Instance.Progress.UseOSCIndicator) { int percentComplete = record.PercentComplete; if (percentComplete < 0) @@ -114,6 +115,12 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt Console.Write($"\x1b]9;4;1;{percentComplete}\x1b\\"); } + // If VT is not supported, we change ProgressView to classic + if (!SupportsVirtualTerminal) + { + PSStyle.Instance.Progress.View = ProgressView.Classic; + } + _progPane.Show(_pendingProgress); } } @@ -132,20 +139,14 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt void PreWrite() { - if (_progPane != null) - { - _progPane.Hide(); - } + _progPane?.Hide(); } private void PostWrite() { - if (_progPane != null) - { - _progPane.Show(); - } + _progPane?.Show(); } private @@ -171,20 +172,14 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt void PreRead() { - if (_progPane != null) - { - _progPane.Hide(); - } + _progPane?.Hide(); } private void PostRead() { - if (_progPane != null) - { - _progPane.Show(); - } + _progPane?.Show(); } private diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePrompt.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePrompt.cs index 6d7caefec36..5895dcf2b83 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePrompt.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePrompt.cs @@ -140,7 +140,7 @@ public override { throw PSTraceSource.NewArgumentException(nameof(descriptions), ConsoleHostUserInterfaceStrings.NullErrorTemplate, - string.Format(CultureInfo.InvariantCulture, "descriptions[{0}]", descIndex)); + string.Create(CultureInfo.InvariantCulture, $"descriptions[{descIndex}]")); } PSObject inputPSObject = null; @@ -152,7 +152,7 @@ public override if (string.IsNullOrEmpty(desc.ParameterAssemblyFullName)) { string paramName = - string.Format(CultureInfo.InvariantCulture, "descriptions[{0}].AssemblyFullName", descIndex); + string.Create(CultureInfo.InvariantCulture, $"descriptions[{descIndex}].AssemblyFullName"); throw PSTraceSource.NewArgumentException(paramName, ConsoleHostUserInterfaceStrings.NullOrEmptyErrorTemplate, paramName); } @@ -190,8 +190,7 @@ public override { string msg = StringUtil.Format(ConsoleHostUserInterfaceStrings.RankZeroArrayErrorTemplate, desc.Name); ArgumentException innerException = PSTraceSource.NewArgumentException( - string.Format(CultureInfo.InvariantCulture, - "descriptions[{0}].AssemblyFullName", descIndex)); + string.Create(CultureInfo.InvariantCulture, $"descriptions[{descIndex}].AssemblyFullName")); PromptingException e = new PromptingException(msg, innerException, "ZeroRankArray", ErrorCategory.InvalidOperation); throw e; } @@ -203,8 +202,7 @@ public override while (true) { - fieldPromptList.Append( - string.Format(CultureInfo.InvariantCulture, "{0}]: ", inputList.Count)); + fieldPromptList.Append(CultureInfo.InvariantCulture, $"{inputList.Count}]: "); bool endListInput = false; object convertedObj = null; _ = PromptForSingleItem( @@ -356,7 +354,7 @@ out object convertedObj /// True to echo user input. /// True if the field is a list. /// Valid only if listInput is true. set to true if the input signals end of list input. - /// True iff the input is canceled, e.g., by Ctrl-C or Ctrl-Break. + /// True if-and-only-if the input is canceled, e.g., by Ctrl-C or Ctrl-Break. /// Processed input string to be converted with LanguagePrimitives.ConvertTo. private string PromptReadInput(string fieldPrompt, FieldDescription desc, bool fieldEchoOnPrompt, bool listInput, out bool endListInput, out bool cancelled) @@ -486,7 +484,7 @@ private PromptCommonInputErrors PromptTryConvertTo(Type fieldType, bool isFromRe /// !h prints out field's Quick Help, returns null /// All others tilde comments are invalid and return null /// - /// returns null iff there's nothing the caller can process. + /// returns null if-and-only-if there's nothing the caller can process. /// /// /// @@ -495,7 +493,7 @@ private PromptCommonInputErrors PromptTryConvertTo(Type fieldType, bool isFromRe private string PromptCommandMode(string input, FieldDescription desc, out bool inputDone) { Dbg.Assert(input != null && input.StartsWith(PromptCommandPrefix, StringComparison.OrdinalIgnoreCase), - string.Format(CultureInfo.InvariantCulture, "input should start with {0}", PromptCommandPrefix)); + string.Create(CultureInfo.InvariantCulture, $"input should start with {PromptCommandPrefix}")); Dbg.Assert(desc != null, "desc should never be null when PromptCommandMode is called"); string command = input.Substring(1); diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePromptForChoice.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePromptForChoice.cs index 872aaa19a9c..7e3dcfd70e0 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePromptForChoice.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePromptForChoice.cs @@ -345,8 +345,7 @@ private void WriteChoicePrompt(string[,] hotkeysAndPlainLabels, defaultStr = hotkeysAndPlainLabels[1, defaultChoice]; } - defaultChoicesBuilder.Append(string.Format(CultureInfo.InvariantCulture, - "{0}{1}", prepend, defaultStr)); + defaultChoicesBuilder.Append(CultureInfo.InvariantCulture, $"{prepend}{defaultStr}"); prepend = ","; } @@ -427,7 +426,7 @@ private void ShowChoiceHelp(Collection choices, string[,] hot WriteLineToConsole( WrapToCurrentWindowWidth( - string.Format(CultureInfo.InvariantCulture, "{0} - {1}", s, choices[i].HelpMessage))); + string.Create(CultureInfo.InvariantCulture, $"{s} - {choices[i].HelpMessage}"))); } } diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs index 11dd276a52d..ce5d5ecaef4 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs @@ -9,8 +9,8 @@ namespace Microsoft.PowerShell { /// - /// This class provides an entry point which is called by minishell's main - /// to transfer control to Msh console host implementation. + /// This class provides an entry point which is called + /// to transfer control to console host implementation. /// public static class ConsoleShell { @@ -21,7 +21,12 @@ public static class ConsoleShell /// An integer value which should be used as exit code for the process. public static int Start(string? bannerText, string? helpText, string[] args) { - return Start(InitialSessionState.CreateDefault2(), bannerText, helpText, args); + return StartImpl( + initialSessionState: InitialSessionState.CreateDefault2(), + bannerText, + helpText, + args, + issProvided: false); } /// Entry point in to ConsoleShell. Used to create a custom Powershell console application. @@ -31,6 +36,31 @@ public static int Start(string? bannerText, string? helpText, string[] args) /// Commandline parameters specified by user. /// An integer value which should be used as exit code for the process. public static int Start(InitialSessionState initialSessionState, string? bannerText, string? helpText, string[] args) + { + return StartImpl( + initialSessionState, + bannerText, + helpText, + args, + issProvided: true); + } + + /// + /// Implementation of entry point to ConsoleShell. + /// Used to create a custom Powershell console application. + /// + /// InitialSessionState to be used by the ConsoleHost. + /// Banner text to be displayed by ConsoleHost. + /// Help text for the shell. + /// Commandline parameters specified by user. + /// True when the InitialSessionState object is provided by caller. + /// An integer value which should be used as exit code for the process. + private static int StartImpl( + InitialSessionState initialSessionState, + string? bannerText, + string? helpText, + string[] args, + bool issProvided) { if (initialSessionState == null) { @@ -45,7 +75,7 @@ public static int Start(InitialSessionState initialSessionState, string? bannerT ConsoleHost.ParseCommandLine(args); ConsoleHost.DefaultInitialSessionState = initialSessionState; - return ConsoleHost.Start(bannerText, helpText); + return ConsoleHost.Start(bannerText, helpText, issProvided); } } } diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs index 68df51be539..38924f3e397 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs @@ -13,11 +13,11 @@ namespace Microsoft.PowerShell { /// - /// Executor wraps a Pipeline instance, and provides helper methods for executing commands in that pipeline. It is used to + /// 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 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. + /// 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. /// /// The class' instance methods manage a single pipeline. The class' static methods track the outstanding instances to /// ensure that only one instance is 'active' (and therefore cancellable) at a time. @@ -44,8 +44,8 @@ internal enum ExecutionOptions /// /// /// True if the instance will be used to execute the prompt function, which will delay stopping the pipeline by some - /// milliseconds. This we prevent us from stopping the pipeline so quickly that when the user leans on the ctrl-c key - /// that the prompt "stops working" (because it is being stopped faster than it can run to completion). + /// milliseconds. This will prevent us from stopping the pipeline so quickly that, when the user leans on the ctrl-c + /// key, the prompt "stops working" (because it is being stopped faster than it can run to completion). /// internal Executor(ConsoleHost parent, bool useNestedPipelines, bool isPromptFunctionExecutor) { @@ -67,7 +67,7 @@ private void OutputObjectStreamHandler(object sender, EventArgs e) PipelineReader reader = (PipelineReader)sender; - // we use NonBlockingRead instead of Read, as Read would block if the reader has no objects. While it would be + // We use NonBlockingRead instead of Read, as Read would block if the reader has no objects. While it would be // inconsistent for this method to be called when there are no objects, since it will be called synchronously on // the pipeline thread, blocking in this call until an object is streamed would deadlock the pipeline. So we // prefer to take no chance of blocking. @@ -80,7 +80,6 @@ private void OutputObjectStreamHandler(object sender, EventArgs e) } // called on the pipeline thread - private void ErrorObjectStreamHandler(object sender, EventArgs e) { // e is just an empty instance of EventArgs, so we ignore it. sender is the PipelineReader that raised it's @@ -88,7 +87,7 @@ private void ErrorObjectStreamHandler(object sender, EventArgs e) PipelineReader reader = (PipelineReader)sender; - // we use NonBlockingRead instead of Read, as Read would block if the reader has no objects. While it would be + // We use NonBlockingRead instead of Read, as Read would block if the reader has no objects. While it would be // inconsistent for this method to be called when there are no objects, since it will be called synchronously on // the pipeline thread, blocking in this call until an object is streamed would deadlock the pipeline. So we // prefer to take no chance of blocking. @@ -101,30 +100,26 @@ private void ErrorObjectStreamHandler(object sender, EventArgs e) } /// - /// This method handles the failure in executing pipeline asynchronously. + /// This method handles failures in executing the pipeline asynchronously. /// /// private void AsyncPipelineFailureHandler(Exception ex) { ErrorRecord er = null; - IContainsErrorRecord cer = ex as IContainsErrorRecord; - if (cer != null) + if (ex is IContainsErrorRecord cer) { er = cer.ErrorRecord; - // Exception inside the error record is ParentContainsErrorRecordException which - // doesn't have stack trace. Replace it with top level exception. + // The exception inside the error record is ParentContainsErrorRecordException, which + // doesn't have a stack trace. Replace it with the top level exception. er = new ErrorRecord(er, ex); } - if (er == null) - { - er = new ErrorRecord(ex, "ConsoleHostAsyncPipelineFailure", ErrorCategory.NotSpecified, null); - } + er ??= new ErrorRecord(ex, "ConsoleHostAsyncPipelineFailure", ErrorCategory.NotSpecified, null); _parent.ErrorSerializer.Serialize(er); } - private class PipelineFinishedWaitHandle + private sealed class PipelineFinishedWaitHandle { internal PipelineFinishedWaitHandle(Pipeline p) { @@ -232,9 +227,9 @@ internal void ExecuteCommandAsyncHelper(Pipeline tempPipeline, out Exception exc } catch (PipelineClosedException) { - // This exception can occurs when input is closed. This can happen - // for various reasons. For ex:Command in the pipeline is invalid and - // command discovery throws exception which closes the pipeline and + // This Exception can occur when the input is closed. This can happen + // for various reasons. For example: The command in the pipeline is invalid and + // command discovery throws an exception, which closes the pipeline and // hence the Input pipe. break; } @@ -297,46 +292,30 @@ internal Pipeline CreatePipeline(string command, bool addToHistory) /// /// All calls to the Runspace to execute a command line must be done with this function, which properly synchronizes - /// access to the running pipeline between the main thread and the break handler thread. This synchronization is + /// access to the running pipeline between the main thread and the break handler thread. This synchronization is /// necessary so that executions can be aborted with Ctrl-C (including evaluation of the prompt and collection of - /// command-completion candidates. + /// command-completion candidates). /// /// On any given Executor instance, ExecuteCommand should be called at most once at a time by any one thread. It is NOT /// reentrant. /// /// - /// The command line to be executed. Must be non-null. + /// The command line to be executed. Must be non-null. /// /// /// Receives the Exception thrown by the execution of the command, if any. If no exception is thrown, then set to null. /// Can be tested to see if the execution was successful or not. /// /// - /// options to govern the execution + /// Options to govern the execution /// /// - /// the object stream resulting from the execution. May be null. + /// The object stream resulting from the execution. May be null. /// internal Collection ExecuteCommand(string command, out Exception exceptionThrown, ExecutionOptions options) { Dbg.Assert(!string.IsNullOrEmpty(command), "command should have a value"); - // Experimental: - // Check for implicit remoting commands that can be batched, and execute as batched if able. - if (ExperimentalFeature.IsEnabled("PSImplicitRemotingBatching")) - { - var addOutputter = ((options & ExecutionOptions.AddOutputter) > 0); - if (addOutputter && - !_parent.RunspaceRef.IsRunspaceOverridden && - _parent.RunspaceRef.Runspace.ExecutionContext.Modules != null && - _parent.RunspaceRef.Runspace.ExecutionContext.Modules.IsImplicitRemotingModuleLoaded && - Utils.TryRunAsImplicitBatch(command, _parent.RunspaceRef.Runspace)) - { - exceptionThrown = null; - return null; - } - } - Pipeline tempPipeline = CreatePipeline(command, (options & ExecutionOptions.AddToHistory) > 0); return ExecuteCommandHelper(tempPipeline, out exceptionThrown, options); @@ -461,14 +440,14 @@ internal Collection ExecuteCommand(string command) } /// - /// Executes a command (by calling this.ExecuteCommand), and coerces the first result object to a string. Any Exception - /// thrown in the course of execution is returned thru the exceptionThrown parameter. + /// Executes a command (by calling this.ExecuteCommand), and coerces the first result object to a string. Any Exception + /// thrown in the course of execution is returned through the exceptionThrown parameter. /// /// - /// The command to execute. May be any valid monad command. + /// The command to execute. May be any valid monad command. /// /// - /// Receives the Exception thrown by the execution of the command, if any. If no exception is thrown, then set to null. + /// Receives the Exception thrown by the execution of the command, if any. Set to null if no exception is thrown. /// Can be tested to see if the execution was successful or not. /// /// @@ -502,8 +481,7 @@ internal string ExecuteCommandAndGetResultAsString(string command, out Exception // And convert the base object into a string. We can't use the proxied // ToString() on the PSObject because there is no default runspace // available. - PSObject msho = streamResults[0] as PSObject; - if (msho != null) + if (streamResults[0] is PSObject msho) result = msho.BaseObject.ToString(); else result = streamResults[0].ToString(); @@ -514,11 +492,11 @@ internal string ExecuteCommandAndGetResultAsString(string command, out Exception } /// - /// Executes a command (by calling this.ExecuteCommand), and coerces the first result object to a bool. Any Exception + /// Executes a command (by calling this.ExecuteCommand), and coerces the first result object to a bool. Any Exception /// thrown in the course of execution is caught and ignored. /// /// - /// The command to execute. May be any valid monad command. + /// The command to execute. May be any valid monad command. /// /// /// The Nullable`bool representation of the first result object returned, or null if an exception was thrown or no @@ -526,22 +504,20 @@ internal string ExecuteCommandAndGetResultAsString(string command, out Exception /// internal bool? ExecuteCommandAndGetResultAsBool(string command) { - Exception unused = null; - - bool? result = ExecuteCommandAndGetResultAsBool(command, out unused); + bool? result = ExecuteCommandAndGetResultAsBool(command, out _); return result; } /// - /// Executes a command (by calling this.ExecuteCommand), and coerces the first result object to a bool. Any Exception - /// thrown in the course of execution is returned thru the exceptionThrown parameter. + /// Executes a command (by calling this.ExecuteCommand), and coerces the first result object to a bool. Any Exception + /// thrown in the course of execution is returned through the exceptionThrown parameter. /// /// - /// The command to execute. May be any valid monad command. + /// The command to execute. May be any valid monad command. /// /// - /// Receives the Exception thrown by the execution of the command, if any. If no exception is thrown, then set to null. + /// Receives the Exception thrown by the execution of the command, if any. Set to null if no exception is thrown. /// Can be tested to see if the execution was successful or not. /// /// @@ -580,7 +556,7 @@ internal string ExecuteCommandAndGetResultAsString(string command, out Exception } /// - /// Cancels execution of the current instance. If the current instance is not running, then does nothing. Called in + /// Cancels execution of the current instance. Does nothing if the current instance is not running. Called in /// response to a break handler, by the static Executor.Cancel method. /// private void Cancel() @@ -605,8 +581,7 @@ private void Cancel() internal void BlockCommandOutput() { - RemotePipeline remotePipeline = _pipeline as RemotePipeline; - if (remotePipeline != null) + if (_pipeline is RemotePipeline remotePipeline) { // Waits until queued data is handled. remotePipeline.DrainIncomingData(); @@ -619,15 +594,13 @@ internal void BlockCommandOutput() internal void ResumeCommandOutput() { RemotePipeline remotePipeline = _pipeline as RemotePipeline; - if (remotePipeline != null) - { - // Resumes data flow. - remotePipeline.ResumeIncomingData(); - } + + // Resumes data flow. + remotePipeline?.ResumeIncomingData(); } /// - /// Resets the instance to its post-ctor state. Does not cancel execution. + /// Resets the instance to its post-ctor state. Does not cancel execution. /// private void Reset() { @@ -643,7 +616,7 @@ private void Reset() /// handler is triggered and calls the static Cancel method. /// /// - /// The instance to make current. Null is allowed. + /// The instance to make current. Null is allowed. /// /// /// Here are some state-transition cases to illustrate the use of CurrentExecutor @@ -705,8 +678,8 @@ internal static Executor CurrentExecutor } /// - /// Cancels the execution of the current instance (the instance last passed to PushCurrentExecutor), if any. If no - /// instance is Current, then does nothing. + /// Cancels the execution of the current instance (the instance last passed to PushCurrentExecutor), if any. Does + /// nothing if no instance is Current. /// internal static void CancelCurrentExecutor() { @@ -717,10 +690,7 @@ internal static void CancelCurrentExecutor() temp = s_currentExecutor; } - if (temp != null) - { - temp.Cancel(); - } + temp?.Cancel(); } // These statics are threadsafe, as there can be only one instance of ConsoleHost in a process at a time, and access diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs index 6dbc21ab242..6dfd5d54e6f 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs @@ -16,18 +16,18 @@ namespace Microsoft.PowerShell { /// - /// Defines an entry point from unmanaged code to managed Msh. + /// Defines an entry point from unmanaged code to PowerShell. /// public sealed class UnmanagedPSEntry { /// - /// Starts managed MSH. + /// Starts PowerShell. /// /// - /// Deprecated: Console file used to create a runspace configuration to start MSH + /// Deprecated: Console file used to create a runspace configuration to start PowerShell /// /// - /// Command line arguments to the managed MSH + /// Command line arguments to the PowerShell /// /// /// Length of the passed in argument array. @@ -39,20 +39,17 @@ public static int Start(string consoleFilePath, [MarshalAs(UnmanagedType.LPArray } /// - /// Starts managed MSH. + /// Starts PowerShell. /// /// - /// Command line arguments to the managed MSH + /// Command line arguments to PowerShell /// /// /// Length of the passed in argument array. /// public static int Start([MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 1)] string[] args, int argc) { - if (args == null) - { - throw new ArgumentNullException(nameof(args)); - } + ArgumentNullException.ThrowIfNull(args); #if DEBUG if (args.Length > 0 && !string.IsNullOrEmpty(args[0]) && args[0]!.Equals("-isswait", StringComparison.OrdinalIgnoreCase)) @@ -96,7 +93,10 @@ public static int Start([MarshalAs(UnmanagedType.LPArray, ArraySubType = Unmanag ConsoleHost.DefaultInitialSessionState = InitialSessionState.CreateDefault2(); - exitCode = ConsoleHost.Start(banner, ManagedEntranceStrings.UsageHelp); + exitCode = ConsoleHost.Start( + bannerText: banner, + helpText: ManagedEntranceStrings.UsageHelp, + issProvidedExternally: false); } catch (HostException e) { diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs index 0c38f35960f..b16fb5ee147 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs @@ -43,7 +43,7 @@ class PendingProgress /// internal void - Update(Int64 sourceId, ProgressRecord record) + Update(long sourceId, ProgressRecord record) { Dbg.Assert(record != null, "record should not be null"); @@ -119,10 +119,7 @@ class PendingProgress ProgressNode parentNode = FindNodeById(newNode.SourceId, newNode.ParentActivityId); if (parentNode != null) { - if (parentNode.Children == null) - { - parentNode.Children = new ArrayList(); - } + parentNode.Children ??= new ArrayList(); AddNode(parentNode.Children, newNode); break; @@ -265,8 +262,7 @@ class PendingProgress #endif } - private - class FindOldestNodeVisitor : NodeVisitor + private sealed class FindOldestNodeVisitor : NodeVisitor { internal override bool @@ -358,7 +354,7 @@ internal override /// private ProgressNode - FindNodeById(Int64 sourceId, int activityId) + FindNodeById(long sourceId, int activityId) { ArrayList listWhereFound = null; int indexWhereFound = -1; @@ -366,11 +362,10 @@ internal override FindNodeById(sourceId, activityId, out listWhereFound, out indexWhereFound); } - private - class FindByIdNodeVisitor : NodeVisitor + private sealed class FindByIdNodeVisitor : NodeVisitor { internal - FindByIdNodeVisitor(Int64 sourceIdToFind, int activityIdToFind) + FindByIdNodeVisitor(long sourceIdToFind, int activityIdToFind) { _sourceIdToFind = sourceIdToFind; _idToFind = activityIdToFind; @@ -404,7 +399,7 @@ internal override IndexWhereFound = -1; private readonly int _idToFind = -1; - private readonly Int64 _sourceIdToFind; + private readonly long _sourceIdToFind; } /// @@ -428,7 +423,7 @@ internal override /// private ProgressNode - FindNodeById(Int64 sourceId, int activityId, out ArrayList listWhereFound, out int indexWhereFound) + FindNodeById(long sourceId, int activityId, out ArrayList listWhereFound, out int indexWhereFound) { listWhereFound = null; indexWhereFound = -1; @@ -463,9 +458,7 @@ internal override /// /// The found node, or null if no suitable node was located. /// - private - ProgressNode - FindOldestNodeOfGivenStyle(ArrayList nodes, int oldestSoFar, ProgressNode.RenderStyle style) + private static ProgressNode FindOldestNodeOfGivenStyle(ArrayList nodes, int oldestSoFar, ProgressNode.RenderStyle style) { if (nodes == null) { @@ -508,8 +501,7 @@ internal override return found; } - private - class AgeAndResetStyleVisitor : NodeVisitor + private sealed class AgeAndResetStyleVisitor : NodeVisitor { internal override bool @@ -579,7 +571,7 @@ internal override int invisible = 0; if (TallyHeight(rawUI, maxHeight, maxWidth) > maxHeight) { - // This will smash down nodes until the tree will fit into the alloted number of lines. If in the + // This will smash down nodes until the tree will fit into the allotted number of lines. If in the // process some nodes were made invisible, we will add a line to the display to say so. invisible = CompressToFit(rawUI, maxHeight, maxWidth); @@ -637,9 +629,7 @@ internal override /// /// The PSHostRawUserInterface used to gauge string widths in the rendering. /// - private - void - RenderHelper(ArrayList strings, ArrayList nodes, int indentation, int maxWidth, PSHostRawUserInterface rawUI) + private static void RenderHelper(ArrayList strings, ArrayList nodes, int indentation, int maxWidth, PSHostRawUserInterface rawUI) { Dbg.Assert(strings != null, "strings should not be null"); Dbg.Assert(nodes != null, "nodes should not be null"); @@ -666,8 +656,7 @@ internal override } } - private - class HeightTallyer : NodeVisitor + private sealed class HeightTallyer : NodeVisitor { internal HeightTallyer(PSHostRawUserInterface rawUi, int maxHeight, int maxWidth) { @@ -728,9 +717,7 @@ private int TallyHeight(PSHostRawUserInterface rawUi, int maxHeight, int maxWidt /// /// /// - private - bool - AllNodesHaveGivenStyle(ArrayList nodes, ProgressNode.RenderStyle style) + private static bool AllNodesHaveGivenStyle(ArrayList nodes, ProgressNode.RenderStyle style) { if (nodes == null) { @@ -855,18 +842,6 @@ internal override } // If we get all the way to here, then we've compressed all the nodes and we still don't fit. - -#if DEBUG || ASSERTIONS_TRACE - - Dbg.Assert( - nodesCompressed == CountNodes(), - "We should have compressed every node in the tree."); - Dbg.Assert( - AllNodesHaveGivenStyle(_topLevelNodes, newStyle), - "We should have compressed every node in the tree."); - -#endif - return false; } @@ -891,7 +866,7 @@ internal override /// /// The number of nodes that were made invisible during the compression. /// - /// + /// private int CompressToFit(PSHostRawUserInterface rawUi, int maxHeight, int maxWidth) @@ -953,8 +928,6 @@ internal override return nodesCompressed; } - Dbg.Assert(false, "with all nodes invisible, we should never reach this point."); - return 0; } diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs index ca2694bcabc..4f51820524c 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs @@ -54,7 +54,7 @@ namespace Microsoft.PowerShell /// Constructs an instance from a ProgressRecord. /// internal - ProgressNode(Int64 sourceId, ProgressRecord record) + ProgressNode(long sourceId, ProgressRecord record) : base(record.ActivityId, record.Activity, record.StatusDescription) { Dbg.Assert(record.RecordType == ProgressRecordType.Processing, "should only create node for Processing records"); @@ -353,7 +353,7 @@ private static void RenderFullDescription(string description, string indent, int internal static bool IsMinimalProgressRenderingEnabled() { - return ExperimentalFeature.IsEnabled(ExperimentalFeature.PSAnsiProgressFeatureName) && PSStyle.Instance.Progress.View == ProgressView.Minimal; + return PSStyle.Instance.Progress.View == ProgressView.Minimal; } /// @@ -387,16 +387,28 @@ internal static bool IsMinimalProgressRenderingEnabled() maxWidth = PSStyle.Instance.Progress.MaxWidth; } + // if the activity is really long, only use up to half the width + string activity; + if (Activity.Length > maxWidth / 2) + { + activity = Activity.Substring(0, maxWidth / 2) + PSObjectHelper.Ellipsis; + } + else + { + activity = Activity; + } + // 4 is for the extra space and square brackets below and one extra space - int barWidth = maxWidth - Activity.Length - indentation - 4; + int barWidth = maxWidth - activity.Length - indentation - 4; var sb = new StringBuilder(); int padding = maxWidth + PSStyle.Instance.Progress.Style.Length + PSStyle.Instance.Reverse.Length + PSStyle.Instance.ReverseOff.Length; sb.Append(PSStyle.Instance.Reverse); - if (StatusDescription.Length > barWidth - secRemainLength) + int maxStatusLength = barWidth - secRemainLength - 1; + if (maxStatusLength > 0 && StatusDescription.Length > barWidth - secRemainLength) { - sb.Append(StatusDescription.Substring(0, barWidth - secRemainLength - 1)); + sb.Append(StatusDescription.AsSpan(0, barWidth - secRemainLength - 1)); sb.Append(PSObjectHelper.Ellipsis); } else @@ -404,10 +416,15 @@ internal static bool IsMinimalProgressRenderingEnabled() sb.Append(StatusDescription); } - sb.Append(string.Empty.PadRight(barWidth + PSStyle.Instance.Reverse.Length - sb.Length - secRemainLength)); + int emptyPadLength = barWidth + PSStyle.Instance.Reverse.Length - sb.Length - secRemainLength; + if (emptyPadLength > 0) + { + sb.Append(string.Empty.PadRight(emptyPadLength)); + } + sb.Append(secRemain); - if (PercentComplete > 0 && PercentComplete < 100) + if (PercentComplete >= 0 && PercentComplete < 100 && barWidth > 0) { int barLength = PercentComplete * barWidth / 100; if (barLength >= barWidth) @@ -415,7 +432,10 @@ internal static bool IsMinimalProgressRenderingEnabled() barLength = barWidth - 1; } - sb.Insert(barLength + PSStyle.Instance.Reverse.Length, PSStyle.Instance.ReverseOff); + if (barLength < sb.Length) + { + sb.Insert(barLength + PSStyle.Instance.Reverse.Length, PSStyle.Instance.ReverseOff); + } } else { @@ -427,7 +447,7 @@ internal static bool IsMinimalProgressRenderingEnabled() "{0}{1}{2} [{3}]{4}", indent, PSStyle.Instance.Progress.Style, - Activity, + activity, sb.ToString(), PSStyle.Instance.Reset) .PadRight(padding)); @@ -465,7 +485,7 @@ internal static bool IsMinimalProgressRenderingEnabled() /// Identifies the source of the progress record. /// internal - Int64 + long SourceId; /// diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressPane.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressPane.cs index f0cdadc73eb..030a359c2d8 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressPane.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressPane.cs @@ -13,7 +13,7 @@ namespace Microsoft.PowerShell /// ProgressPane is a class that represents the "window" in which outstanding activities for which the host has received /// progress updates are shown. /// - /// + /// internal class ProgressPane { @@ -26,7 +26,7 @@ class ProgressPane internal ProgressPane(ConsoleHostUserInterface ui) { - if (ui == null) throw new ArgumentNullException(nameof(ui)); + ArgumentNullException.ThrowIfNull(ui); _ui = ui; _rawui = ui.RawUI; } @@ -37,7 +37,7 @@ class ProgressPane /// /// true if the pane is visible, false if not. /// - /// + /// internal bool IsShowing @@ -115,7 +115,7 @@ class ProgressPane // create cleared region to clear progress bar later _savedRegion = tempProgressRegion; - if (ExperimentalFeature.IsEnabled(ExperimentalFeature.PSAnsiProgressFeatureName) && PSStyle.Instance.Progress.View != ProgressView.Minimal) + if (PSStyle.Instance.Progress.View != ProgressView.Minimal) { for (int row = 0; row < rows; row++) { @@ -301,7 +301,17 @@ private void WriteContent() { if (_content is not null) { + // On Windows, we can check if the cursor is currently visible and not change it to visible + // if it is intentionally hidden. On Unix, it is not currently supported to read the cursor visibility. +#if UNIX Console.CursorVisible = false; +#else + bool currentCursorVisible = Console.CursorVisible; + if (currentCursorVisible) + { + Console.CursorVisible = false; + } +#endif var currentPosition = _rawui.CursorPosition; _rawui.CursorPosition = _location; @@ -319,7 +329,11 @@ private void WriteContent() } _rawui.CursorPosition = currentPosition; +#if UNIX Console.CursorVisible = true; +#else + Console.CursorVisible = currentCursorVisible; +#endif } } diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/Serialization.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/Serialization.cs index 4c1fb4fa4e9..5181ae63672 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/Serialization.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/Serialization.cs @@ -189,8 +189,7 @@ class WrappedDeserializer : Serialization return null; case DataFormat.XML: - string unused; - o = _xmlDeserializer.Deserialize(out unused); + o = _xmlDeserializer.Deserialize(out _); break; case DataFormat.Text: diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/StartTranscriptCmdlet.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/StartTranscriptCmdlet.cs index dadc5117ab3..18725b5ddb7 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/StartTranscriptCmdlet.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/StartTranscriptCmdlet.cs @@ -96,7 +96,7 @@ public SwitchParameter Append /// /// The read-only attribute will not be replaced when the transcript is done. /// - [Parameter()] + [Parameter] public SwitchParameter Force { get @@ -115,7 +115,7 @@ public SwitchParameter Force /// /// Property that prevents file overwrite. /// - [Parameter()] + [Parameter] [Alias("NoOverwrite")] public SwitchParameter NoClobber { @@ -135,7 +135,7 @@ public SwitchParameter NoClobber /// /// Whether to include command invocation time headers between commands. /// - [Parameter()] + [Parameter] public SwitchParameter IncludeInvocationHeader { get; set; @@ -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 48b392f98e7..71e90854dc4 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/StopTranscriptCmdlet.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/StopTranscriptCmdlet.cs @@ -10,17 +10,22 @@ namespace Microsoft.PowerShell.Commands /// /// Implements the stop-transcript cmdlet. /// - [Cmdlet(VerbsLifecycle.Stop, "Transcript", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096798")] + [Cmdlet(VerbsLifecycle.Stop, "Transcript", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.None, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096798")] [OutputType(typeof(string))] public sealed class StopTranscriptCommand : PSCmdlet { /// - /// Starts the transcription. + /// Stops the transcription. /// protected override void BeginProcessing() { + if (!ShouldProcess(string.Empty)) + { + 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 08e0ed8e1ee..28cd31473dd 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs @@ -108,7 +108,7 @@ internal static void ShowUpdateNotification(PSHostUserInterface hostUI) // We calculate how much whitespace we need to make it look nice if (hostUI.SupportsVirtualTerminal) { - // Use Warning Color + // Swaps foreground and background colors. notificationColor = "\x1B[7m"; resetColor = "\x1B[0m"; @@ -126,6 +126,7 @@ internal static void ShowUpdateNotification(PSHostUserInterface hostUI) string notificationMsg = string.Format(CultureInfo.CurrentCulture, notificationMsgTemplate, releaseTag, notificationColor, resetColor, line2Padding, line3Padding); + hostUI.WriteLine(); hostUI.WriteLine(notificationMsg); } } @@ -352,7 +353,7 @@ private static async Task QueryNewReleaseAsync(SemanticVersion baseline using var client = new HttpClient(); - string userAgent = string.Format(CultureInfo.InvariantCulture, "PowerShell {0}", PSVersionInfo.GitCommitId); + string userAgent = string.Create(CultureInfo.InvariantCulture, $"PowerShell {PSVersionInfo.GitCommitId}"); client.DefaultRequestHeaders.Add("User-Agent", userAgent); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); @@ -411,7 +412,7 @@ private static NotificationType GetNotificationType() private enum NotificationType { /// - /// Turn off the udpate notification. + /// Turn off the update notification. /// Off = 0, @@ -428,7 +429,7 @@ private enum NotificationType LTS = 2 } - private class Release + private sealed class Release { internal Release(string publishAt, string tagName) { diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/CommandLineParameterParserStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/CommandLineParameterParserStrings.resx index ab893b91a0a..34bb696c33c 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/CommandLineParameterParserStrings.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/CommandLineParameterParserStrings.resx @@ -118,10 +118,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Cannot process command because a command is already specified with -Command or -EncodedCommand. - - - Unable to read from file '{0}'. + Cannot process command because a command is already specified with -Command, -CommandWithArgs, or -EncodedCommand. Cannot process the command because of a missing parameter. A command must follow -Command. @@ -187,7 +184,10 @@ Valid formats are: Cannot process the command because -STA and -MTA are both specified. Specify either -STA or -MTA. - Cannot process the command because -Configuration requires an argument that is a remote endpoint configuration name. Specify this argument and try again. + Cannot process the command because -ConfigurationName requires an argument that is a remote endpoint configuration name. Specify this argument and try again. + + + Cannot process the command because -ConfigurationFile requires an argument that is a session configuration (.pssc) file path. Specify this argument and try again. Cannot process the command because -CustomPipeName requires an argument that is a name of the pipe you want to use. Specify this argument and try again. @@ -222,4 +222,7 @@ Valid formats are: The specified arguments must not contain null elements. + + Invalid ExecutionPolicy value '{0}'. + diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleControlStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleControlStrings.resx index f5fc6e4427a..bf1a8f84bfd 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleControlStrings.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleControlStrings.resx @@ -123,12 +123,6 @@ The Win32 internal error "{0}" 0x{1:X} occurred while trying to remove a break handler. Contact Microsoft Customer Support Services. - - The Win32 internal error "{0}" 0x{1:X} occurred while attaching to parent console. Contact Microsoft Customer Support Services. - - - The Win32 internal error "{0}" 0x{1:X} occurred while detaching from the console. Contact Microsoft Customer Support Services. - The Win32 internal error "{0}" 0x{1:X} occurred while getting input about the console handle. Contact Microsoft Customer Support Services. @@ -192,9 +186,6 @@ The Win32 internal error "{0}" 0x{1:X} occurred while setting character attributes for the console output buffer. Contact Microsoft Customer Support Services. - - The Win32 internal error "{0}" 0x{1:X} occurred while trying to set the cursor position. Contact Microsoft Customer Support Services. - The Win32 internal error "{0}" 0x{1:X} occurred while getting cursor information. Contact Microsoft Customer Support Services. @@ -207,7 +198,4 @@ The Win32 internal error "{0}" 0x{1:X} occurred while sending keyboard input. Contact Microsoft Customer Support Services. - - The Win32 internal error "{0}" 0x{1:X} occurred while setting console font information. Contact Microsoft Customer Support Services. - diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostRawUserInterfaceStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostRawUserInterfaceStrings.resx index bf47782fdb0..c640b2c4a89 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostRawUserInterfaceStrings.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostRawUserInterfaceStrings.resx @@ -172,9 +172,6 @@ Window title cannot be longer than {0} characters. - Administrator - - - {0}: {1} + Administrator: diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostStrings.resx index 7296bb30feb..80b3d4aafe0 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostStrings.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostStrings.resx @@ -123,18 +123,15 @@ Cannot process input loop. ExitCurrentLoop was called when no InputLoops were running. - - A nested prompt cannot be entered until the host is running at least one prompt loop. - PS> - - Execution of initialization script has failed. The shell cannot be started. - The shell cannot be started. A failure occurred during initialization: + + The shell cannot be started. An InitialSessionState object has been provided along with a -ConfigurationFile argument. Both configuration directives cannot be used at the same time. + An error has occurred that was not properly handled. Additional information is shown below. The PowerShell process will exit. @@ -152,9 +149,6 @@ PowerShell transcript end End time: {0:yyyyMMddHHmmss} ********************** - - An instance of the ConsoleHost class has already been created for this process. - Command '{0}' could not be run because some PowerShell Snap-Ins did not load. @@ -170,9 +164,6 @@ End time: {0:yyyyMMddHHmmss} {0}:{1,-3} {2} - - An error occurred while running '{0}': {1} - The current session does not support debugging; execution will continue. @@ -191,4 +182,7 @@ The current session does not support debugging; execution will continue. Run as Administrator + + PushRunspace can only push a remote runspace. + diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx index 7b13837b294..a57d044a8be 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx @@ -118,11 +118,19 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - PowerShell {0} -Copyright (c) Microsoft Corporation. - -https://aka.ms/powershell -Type 'help' to get help. + PowerShell {0} + + + [Constrained Language Mode] + + + [Constrained Language AUDIT Mode : No Restrictions] + + + [No Language Mode] + + + [Restricted Language Mode] 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'. @@ -149,12 +157,15 @@ Type 'help' to get help. Usage: pwsh[.exe] [-Login] [[-File] <filePath> [args]] [-Command { - | <script-block> [-args <arg-array>] | <string> [<CommandParameters>] } ] - [-ConfigurationName <string>] [-CustomPipeName <string>] - [-EncodedCommand <Base64EncodedCommand>] + [-CommandWithArgs <string> [<CommandParameters>] + [-ConfigurationName <string>] [-ConfigurationFile <filePath>] + [-CustomPipeName <string>] [-EncodedCommand <Base64EncodedCommand>] [-ExecutionPolicy <ExecutionPolicy>] [-InputFormat {Text | XML}] [-Interactive] [-MTA] [-NoExit] [-NoLogo] [-NonInteractive] [-NoProfile] - [-OutputFormat {Text | XML}] [-SettingsFile <filePath>] [-SSHServerMode] [-STA] - [-Version] [-WindowStyle <style>] [-WorkingDirectory <directoryPath>] + [-NoProfileLoadTime] [-OutputFormat {Text | XML}] + [-SettingsFile <filePath>] [-SSHServerMode] [-STA] + [-Version] [-WindowStyle <style>] + [-WorkingDirectory <directoryPath>] pwsh[.exe] -h | -Help | -? | /? @@ -283,6 +294,25 @@ All parameters are case-insensitive. (runspace-terminating) error, such as a throw or -ErrorAction Stop, occurs or when execution is interrupted with Ctrl-C. +-CommandWithArgs | -cwa + + [Experimental] + Executes a PowerShell command with arguments. Unlike `-Command`, this + parameter populates the `$args built-in variable which can be used by the + command. + + The first string is the command and subsequent strings delimited by whitespace + are the arguments. + + For example: + + pwsh -CommandWithArgs '$args | % { "arg: $_" }' arg1 arg2 + + This example produces the following output: + + arg: arg1 + arg: arg2 + -ConfigurationName | -config Specifies a configuration endpoint in which PowerShell is run. This can be @@ -292,6 +322,14 @@ All parameters are case-insensitive. Example: "pwsh -ConfigurationName AdminRoles" +-ConfigurationFile + + Specifies a session configuration (.pssc) file path. The configuration + contained in the configuration file will be applied to the PowerShell + session. + + Example: "pwsh -ConfigurationFile "C:\ProgramData\PowerShell\MyConfig.pssc" + -CustomPipeName Specifies the name to use for an additional IPC server (named pipe) used @@ -383,18 +421,24 @@ All parameters are case-insensitive. -NoLogo | -nol - Hides the copyright banner at startup of interactive sessions. + Hides the banner text at startup of interactive sessions. -NonInteractive | -noni - Does not present an interactive prompt to the user. Any attempts to use - interactive features, like Read-Host or confirmation prompts, result in - statement-terminating errors. + This switch is used to create sessions that shouldn't require user input. + This is useful for scripts that run in scheduled tasks or CI/CD pipelines. + Any attempts to use interactive features, like 'Read-Host' or confirmation + prompts, result in statement terminating errors rather than hanging. -NoProfile | -nop Does not load the PowerShell profiles. +-NoProfileLoadTime + + Hides the PowerShell profile load time text shown at startup when the load + time exceeds 500 milliseconds. + -OutputFormat | -o | -of Determines how output from PowerShell is formatted. Valid values are "Text" @@ -402,7 +446,7 @@ All parameters are case-insensitive. Example: "pwsh -o XML -c Get-Date" - When called withing a PowerShell session, you get deserialized objects as + When called within a PowerShell session, you get deserialized objects as output rather plain strings. When called from other shells, the output is string data formatted as CLIXML text. @@ -434,7 +478,7 @@ All parameters are case-insensitive. -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.ConsoleHost/resources/TranscriptStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/TranscriptStrings.resx index c2a2826955c..9fe6f988e58 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/TranscriptStrings.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/TranscriptStrings.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - This host does not support transcription. - Transcript started, output file is {0} diff --git a/src/Microsoft.PowerShell.ConsoleHost/singleshell/installer/EngineInstaller.cs b/src/Microsoft.PowerShell.ConsoleHost/singleshell/installer/EngineInstaller.cs index 1b3f939158f..ec2b702a144 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/singleshell/installer/EngineInstaller.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/singleshell/installer/EngineInstaller.cs @@ -12,10 +12,7 @@ namespace Microsoft.PowerShell { /// /// EngineInstaller is a class for facilitating registry of necessary - /// information for monad engine. - /// - /// This class will be built with monad console host dll - /// (System.Management.Automation.dll). + /// information for PowerShell engine. /// /// At install time, installation utilities (like InstallUtil.exe) will /// call install this engine assembly based on the implementation in diff --git a/src/Microsoft.PowerShell.ConsoleHost/singleshell/installer/MshHostMshSnapin.cs b/src/Microsoft.PowerShell.ConsoleHost/singleshell/installer/MshHostMshSnapin.cs index c532aeb190d..e1132d75751 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/singleshell/installer/MshHostMshSnapin.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/singleshell/installer/MshHostMshSnapin.cs @@ -7,11 +7,8 @@ namespace Microsoft.PowerShell { /// - /// PSHostMshSnapin (or PSHostMshSnapinInstaller) is a class for facilitating registry - /// of necessary information for monad host mshsnapin. - /// - /// This class will be built with monad host engine dll - /// (Microsoft.PowerShell.ConsoleHost.dll). + /// PSHostPSSnapIn is a class for facilitating registry + /// of necessary information for PowerShell host PSSnapin. /// [RunInstaller(true)] public sealed class PSHostPSSnapIn : PSSnapIn @@ -25,7 +22,7 @@ public PSHostPSSnapIn() } /// - /// Get name of this mshsnapin. + /// Get name of this PSSnapin. /// public override string Name { @@ -36,7 +33,7 @@ public override string Name } /// - /// Get the default vendor string for this mshsnapin. + /// Get the default vendor string for this PSSnapin. /// public override string Vendor { @@ -58,7 +55,7 @@ public override string VendorResource } /// - /// Get the default description string for this mshsnapin. + /// Get the default description string for this PSSnapin. /// public override string Description { diff --git a/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/EventProvider.cs b/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/EventProvider.cs index d4e566890f0..32ce9993cfb 100644 --- a/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/EventProvider.cs +++ b/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/EventProvider.cs @@ -107,7 +107,7 @@ private unsafe void EtwRegister() } // - // implement Dispose Pattern to early deregister from ETW insted of waiting for + // implement Dispose Pattern to early deregister from ETW instead of waiting for // the finalizer to call deregistration. // Once the user is done with the provider it needs to call Close() or Dispose() // If neither are called the finalizer will unregister the provider anyway @@ -131,7 +131,10 @@ protected virtual void Dispose(bool disposing) // // check if the object has been already disposed // - if (_disposed == 1) return; + if (_disposed == 1) + { + return; + } if (Interlocked.Exchange(ref _disposed, 1) != 0) { @@ -291,8 +294,7 @@ to fill the passed in ETW data descriptor. { dataDescriptor->Reserved = 0; - string sRet = data as string; - if (sRet != null) + if (data is string sRet) { dataDescriptor->Size = (uint)((sRet.Length + 1) * 2); return sRet; @@ -331,10 +333,10 @@ to fill the passed in ETW data descriptor. *uintptr = (uint)data; dataDescriptor->DataPointer = (ulong)uintptr; } - else if (data is UInt64) + else if (data is ulong) { dataDescriptor->Size = (uint)sizeof(ulong); - UInt64* ulongptr = (ulong*)dataBuffer; + ulong* ulongptr = (ulong*)dataBuffer; *ulongptr = (ulong)data; dataDescriptor->DataPointer = (ulong)ulongptr; } @@ -437,10 +439,7 @@ public bool WriteMessageEvent(string eventMessage, byte eventLevel, long eventKe { int status = 0; - if (eventMessage == null) - { - throw new ArgumentNullException(nameof(eventMessage)); - } + ArgumentNullException.ThrowIfNull(eventMessage); if (IsEnabled(eventLevel, eventKeywords)) { @@ -508,10 +507,7 @@ public bool WriteEvent(in EventDescriptor eventDescriptor, string data) { uint status = 0; - if (data == null) - { - throw new ArgumentNullException("dataString"); - } + ArgumentNullException.ThrowIfNull(data); if (IsEnabled(eventDescriptor.Level, eventDescriptor.Keywords)) { diff --git a/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/EventProviderTraceListener.cs b/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/EventProviderTraceListener.cs index 7264e485468..1c82891b654 100644 --- a/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/EventProviderTraceListener.cs +++ b/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/EventProviderTraceListener.cs @@ -41,8 +41,7 @@ public string Delimiter [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] set { - if (value == null) - throw new ArgumentNullException("Delimiter"); + ArgumentNullException.ThrowIfNull(value, nameof(Delimiter)); if (value.Length == 0) throw new ArgumentException(DotNetEventingStrings.Argument_NeedNonemptyDelimiter); @@ -72,8 +71,7 @@ public EventProviderTraceListener(string providerId, string name) public EventProviderTraceListener(string providerId, string name, string delimiter) : base(name) { - if (delimiter == null) - throw new ArgumentNullException(nameof(delimiter)); + ArgumentNullException.ThrowIfNull(delimiter); if (delimiter.Length == 0) throw new ArgumentException(DotNetEventingStrings.Argument_NeedNonemptyDelimiter); diff --git a/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/Reader/NativeWrapper.cs b/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/Reader/NativeWrapper.cs index 007cf74891e..df2b064ebd5 100644 --- a/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/Reader/NativeWrapper.cs +++ b/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/Reader/NativeWrapper.cs @@ -307,7 +307,7 @@ public static void EvtClearLog( [System.Security.SecurityCritical] public static EventLogHandle EvtCreateRenderContext( - Int32 valuePathsCount, + int valuePathsCount, string[] valuePaths, UnsafeNativeMethods.EvtRenderContextFlags flags) { @@ -939,7 +939,7 @@ public static void EvtRenderBufferWithContextSystem(EventLogHandle contextHandle break; } - pointer = new IntPtr(((Int64)pointer + Marshal.SizeOf(varVal))); + pointer = new IntPtr(((long)pointer + Marshal.SizeOf(varVal))); } } finally @@ -984,7 +984,7 @@ public static IList EvtRenderBufferWithContextUserOrValues(EventLogHandl { UnsafeNativeMethods.EvtVariant varVal = Marshal.PtrToStructure(pointer); valuesList.Add(ConvertToObject(varVal)); - pointer = new IntPtr(((Int64)pointer + Marshal.SizeOf(varVal))); + pointer = new IntPtr(((long)pointer + Marshal.SizeOf(varVal))); } } @@ -1106,7 +1106,7 @@ public static IEnumerable EvtFormatMessageRenderKeywords(EventLogHandle break; keywordsList.Add(s); // nr of bytes = # chars * 2 + 2 bytes for character '\0'. - pointer = new IntPtr((Int64)pointer + (s.Length * 2) + 2); + pointer = new IntPtr((long)pointer + (s.Length * 2) + 2); } return keywordsList.AsReadOnly(); @@ -1270,23 +1270,23 @@ private static object ConvertToObject(UnsafeNativeMethods.EvtVariant val) Marshal.Copy(val.Reference, arByte, 0, (int)val.Count); return arByte; case ((int)UnsafeNativeMethods.EvtMasks.EVT_VARIANT_TYPE_ARRAY | (int)UnsafeNativeMethods.EvtVariantType.EvtVarTypeInt16): - if (val.Reference == IntPtr.Zero) return Array.Empty(); - Int16[] arInt16 = new Int16[val.Count]; + if (val.Reference == IntPtr.Zero) return Array.Empty(); + short[] arInt16 = new short[val.Count]; Marshal.Copy(val.Reference, arInt16, 0, (int)val.Count); return arInt16; case ((int)UnsafeNativeMethods.EvtMasks.EVT_VARIANT_TYPE_ARRAY | (int)UnsafeNativeMethods.EvtVariantType.EvtVarTypeInt32): - if (val.Reference == IntPtr.Zero) return Array.Empty(); - Int32[] arInt32 = new Int32[val.Count]; + if (val.Reference == IntPtr.Zero) return Array.Empty(); + int[] arInt32 = new int[val.Count]; Marshal.Copy(val.Reference, arInt32, 0, (int)val.Count); return arInt32; case ((int)UnsafeNativeMethods.EvtMasks.EVT_VARIANT_TYPE_ARRAY | (int)UnsafeNativeMethods.EvtVariantType.EvtVarTypeInt64): - if (val.Reference == IntPtr.Zero) return Array.Empty(); - Int64[] arInt64 = new Int64[val.Count]; + if (val.Reference == IntPtr.Zero) return Array.Empty(); + long[] arInt64 = new long[val.Count]; Marshal.Copy(val.Reference, arInt64, 0, (int)val.Count); return arInt64; case ((int)UnsafeNativeMethods.EvtMasks.EVT_VARIANT_TYPE_ARRAY | (int)UnsafeNativeMethods.EvtVariantType.EvtVarTypeSingle): - if (val.Reference == IntPtr.Zero) return Array.Empty(); - Single[] arSingle = new Single[val.Count]; + if (val.Reference == IntPtr.Zero) return Array.Empty(); + float[] arSingle = new float[val.Count]; Marshal.Copy(val.Reference, arSingle, 0, (int)val.Count); return arSingle; case ((int)UnsafeNativeMethods.EvtMasks.EVT_VARIANT_TYPE_ARRAY | (int)UnsafeNativeMethods.EvtVariantType.EvtVarTypeDouble): @@ -1297,13 +1297,13 @@ private static object ConvertToObject(UnsafeNativeMethods.EvtVariant val) case ((int)UnsafeNativeMethods.EvtMasks.EVT_VARIANT_TYPE_ARRAY | (int)UnsafeNativeMethods.EvtVariantType.EvtVarTypeSByte): return ConvertToArray(val, sizeof(sbyte)); // not CLS-compliant case ((int)UnsafeNativeMethods.EvtMasks.EVT_VARIANT_TYPE_ARRAY | (int)UnsafeNativeMethods.EvtVariantType.EvtVarTypeUInt16): - return ConvertToArray(val, sizeof(UInt16)); + return ConvertToArray(val, sizeof(ushort)); case ((int)UnsafeNativeMethods.EvtMasks.EVT_VARIANT_TYPE_ARRAY | (int)UnsafeNativeMethods.EvtVariantType.EvtVarTypeUInt64): case ((int)UnsafeNativeMethods.EvtMasks.EVT_VARIANT_TYPE_ARRAY | (int)UnsafeNativeMethods.EvtVariantType.EvtVarTypeHexInt64): - return ConvertToArray(val, sizeof(UInt64)); + return ConvertToArray(val, sizeof(ulong)); case ((int)UnsafeNativeMethods.EvtMasks.EVT_VARIANT_TYPE_ARRAY | (int)UnsafeNativeMethods.EvtVariantType.EvtVarTypeUInt32): case ((int)UnsafeNativeMethods.EvtMasks.EVT_VARIANT_TYPE_ARRAY | (int)UnsafeNativeMethods.EvtVariantType.EvtVarTypeHexInt32): - return ConvertToArray(val, sizeof(UInt32)); + return ConvertToArray(val, sizeof(uint)); case ((int)UnsafeNativeMethods.EvtMasks.EVT_VARIANT_TYPE_ARRAY | (int)UnsafeNativeMethods.EvtVariantType.EvtVarTypeString): return ConvertToStringArray(val, false); case ((int)UnsafeNativeMethods.EvtMasks.EVT_VARIANT_TYPE_ARRAY | (int)UnsafeNativeMethods.EvtVariantType.EvtVarTypeAnsiString): @@ -1375,7 +1375,7 @@ public static Array ConvertToArray(UnsafeNativeMethods.EvtVariant val, int si for (int i = 0; i < val.Count; i++) { array.SetValue(Marshal.PtrToStructure(ptr), i); - ptr = new IntPtr((Int64)ptr + size); + ptr = new IntPtr((long)ptr + size); } return array; @@ -1398,7 +1398,7 @@ public static Array ConvertToBoolArray(UnsafeNativeMethods.EvtVariant val) { bool value = Marshal.ReadInt32(ptr) != 0; array[i] = value; - ptr = new IntPtr((Int64)ptr + 4); + ptr = new IntPtr((long)ptr + 4); } return array; @@ -1419,7 +1419,7 @@ public static Array ConvertToFileTimeArray(UnsafeNativeMethods.EvtVariant val) for (int i = 0; i < val.Count; i++) { array[i] = DateTime.FromFileTime(Marshal.ReadInt64(ptr)); - ptr = new IntPtr((Int64)ptr + 8 * sizeof(byte)); // FILETIME values are 8 bytes + ptr = new IntPtr((long)ptr + 8 * sizeof(byte)); // FILETIME values are 8 bytes } return array; @@ -1441,7 +1441,7 @@ public static Array ConvertToSysTimeArray(UnsafeNativeMethods.EvtVariant val) { UnsafeNativeMethods.SystemTime sysTime = Marshal.PtrToStructure(ptr); array[i] = new DateTime(sysTime.Year, sysTime.Month, sysTime.Day, sysTime.Hour, sysTime.Minute, sysTime.Second, sysTime.Milliseconds); - ptr = new IntPtr((Int64)ptr + 16 * sizeof(byte)); // SystemTime values are 16 bytes + ptr = new IntPtr((long)ptr + 16 * sizeof(byte)); // SystemTime values are 16 bytes } return array; diff --git a/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/UnsafeNativeMethods.cs b/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/UnsafeNativeMethods.cs index 38a3f4a60c7..b7beee133ed 100644 --- a/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/UnsafeNativeMethods.cs +++ b/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/UnsafeNativeMethods.cs @@ -268,10 +268,10 @@ internal struct SystemTime internal struct EvtVariant { [FieldOffset(0)] - public UInt32 UInteger; + public uint UInteger; [FieldOffset(0)] - public Int32 Integer; + public int Integer; [FieldOffset(0)] public byte UInt8; @@ -283,7 +283,7 @@ internal struct EvtVariant public ushort UShort; [FieldOffset(0)] - public UInt32 Bool; + public uint Bool; [FieldOffset(0)] public byte ByteVal; @@ -292,13 +292,13 @@ internal struct EvtVariant public byte SByte; [FieldOffset(0)] - public UInt64 ULong; + public ulong ULong; [FieldOffset(0)] - public Int64 Long; + public long Long; [FieldOffset(0)] - public Single Single; + public float Single; [FieldOffset(0)] public double Double; @@ -325,7 +325,7 @@ internal struct EvtVariant public IntPtr GuidReference; [FieldOffset(0)] - public UInt64 FileTime; + public ulong FileTime; [FieldOffset(0)] public IntPtr SystemTime; @@ -334,10 +334,10 @@ internal struct EvtVariant public IntPtr SizeT; [FieldOffset(8)] - public UInt32 Count; // number of elements (not length) in bytes. + public uint Count; // number of elements (not length) in bytes. [FieldOffset(12)] - public UInt32 Type; + public uint Type; } internal enum EvtEventPropertyId @@ -404,15 +404,15 @@ internal enum EvtChannelReferenceFlags internal enum EvtEventMetadataPropertyId { - EventMetadataEventID, // EvtVarTypeUInt32 - EventMetadataEventVersion, // EvtVarTypeUInt32 - EventMetadataEventChannel, // EvtVarTypeUInt32 - EventMetadataEventLevel, // EvtVarTypeUInt32 - EventMetadataEventOpcode, // EvtVarTypeUInt32 - EventMetadataEventTask, // EvtVarTypeUInt32 - EventMetadataEventKeyword, // EvtVarTypeUInt64 - EventMetadataEventMessageID,// EvtVarTypeUInt32 - EventMetadataEventTemplate // EvtVarTypeString + EventMetadataEventID, // EvtVarTypeUInt32 + EventMetadataEventVersion, // EvtVarTypeUInt32 + EventMetadataEventChannel, // EvtVarTypeUInt32 + EventMetadataEventLevel, // EvtVarTypeUInt32 + EventMetadataEventOpcode, // EvtVarTypeUInt32 + EventMetadataEventTask, // EvtVarTypeUInt32 + EventMetadataEventKeyword, // EvtVarTypeUInt64 + EventMetadataEventMessageID, // EvtVarTypeUInt32 + EventMetadataEventTemplate // EvtVarTypeString // EvtEventMetadataPropertyIdEND } @@ -733,7 +733,7 @@ out int publisherIdBufferUsed [SecurityCritical] internal static extern EventLogHandle EvtOpenChannelConfig( EventLogHandle session, - [MarshalAs(UnmanagedType.LPWStr)] String channelPath, + [MarshalAs(UnmanagedType.LPWStr)] string channelPath, int flags ); @@ -823,8 +823,8 @@ int flags [DllImport(WEVTAPI, CharSet = CharSet.Unicode, SetLastError = true)] [SecurityCritical] internal static extern EventLogHandle EvtCreateRenderContext( - Int32 valuePathsCount, - [MarshalAs(UnmanagedType.LPArray,ArraySubType = UnmanagedType.LPWStr)] + int valuePathsCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr)] string[] valuePaths, [MarshalAs(UnmanagedType.I4)] EvtRenderContextFlags flags ); @@ -862,10 +862,10 @@ internal struct EvtStringVariant public string StringVal; [FieldOffset(8)] - public UInt32 Count; + public uint Count; [FieldOffset(12)] - public UInt32 Type; + public uint Type; } [DllImport(WEVTAPI, CharSet = CharSet.Unicode, SetLastError = true)] 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 81c8fc4e9dd..8c284a4c20c 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 @@ - + diff --git a/src/Microsoft.PowerShell.GlobalTool.Shim/GlobalToolShim.cs b/src/Microsoft.PowerShell.GlobalTool.Shim/GlobalToolShim.cs index 47a66ea8767..356cde68152 100644 --- a/src/Microsoft.PowerShell.GlobalTool.Shim/GlobalToolShim.cs +++ b/src/Microsoft.PowerShell.GlobalTool.Shim/GlobalToolShim.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; @@ -10,7 +11,7 @@ namespace Microsoft.PowerShell.GlobalTool.Shim /// /// Shim layer to chose the appropriate runtime for PowerShell DotNet Global tool. /// - public class EntryPoint + public static class EntryPoint { private const string PwshDllName = "pwsh.dll"; @@ -26,13 +27,14 @@ public class EntryPoint public static int Main(string[] args) { var currentPath = new FileInfo(System.Reflection.Assembly.GetEntryAssembly().Location).Directory.FullName; - var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + var isWindows = OperatingSystem.IsWindows(); string platformFolder = isWindows ? WinFolderName : UnixFolderName; - string argsString = args.Length > 0 ? string.Join(" ", args) : null; + var arguments = new List(args.Length + 1); var pwshPath = Path.Combine(currentPath, platformFolder, PwshDllName); - string processArgs = string.IsNullOrEmpty(argsString) ? $"\"{pwshPath}\"" : $"\"{pwshPath}\" {argsString}"; + arguments.Add(pwshPath); + arguments.AddRange(args); if (File.Exists(pwshPath)) { @@ -41,7 +43,7 @@ public static int Main(string[] args) e.Cancel = true; }; - var process = System.Diagnostics.Process.Start("dotnet", processArgs); + var process = System.Diagnostics.Process.Start("dotnet", arguments); process.WaitForExit(); return process.ExitCode; } diff --git a/src/Microsoft.PowerShell.GlobalTool.Shim/Microsoft.PowerShell.GlobalTool.Shim.csproj b/src/Microsoft.PowerShell.GlobalTool.Shim/Microsoft.PowerShell.GlobalTool.Shim.csproj index aa845a7817d..d0203344cc2 100644 --- a/src/Microsoft.PowerShell.GlobalTool.Shim/Microsoft.PowerShell.GlobalTool.Shim.csproj +++ b/src/Microsoft.PowerShell.GlobalTool.Shim/Microsoft.PowerShell.GlobalTool.Shim.csproj @@ -6,6 +6,7 @@ Microsoft.PowerShell.GlobalTool.Shim EXE Microsoft.PowerShell.GlobalTool.Shim + False diff --git a/src/Microsoft.PowerShell.GlobalTool.Shim/runtimeconfig.template.json b/src/Microsoft.PowerShell.GlobalTool.Shim/runtimeconfig.template.json index 8ba6dc2eba9..4a5e3e367ec 100644 --- a/src/Microsoft.PowerShell.GlobalTool.Shim/runtimeconfig.template.json +++ b/src/Microsoft.PowerShell.GlobalTool.Shim/runtimeconfig.template.json @@ -1,4 +1,4 @@ -// This is required to roll forward to runtime 3.x when 2.x is not installed +// This is required to roll forward to supported minor.patch versions of the runtime. { - "rollForwardOnNoCandidateFx": 2 + "rollForwardOnNoCandidateFx": 1 } diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupMemberCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupMemberCommand.cs index a10300e9065..8be09e1ded5 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupMemberCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupMemberCommand.cs @@ -210,7 +210,7 @@ private IEnumerable ProcessesMembership(IEnumerable string.Compare(p1.Name, p2.Name, StringComparison.CurrentCultureIgnoreCase)); + rv.Sort(static (p1, p2) => string.Compare(p1.Name, p2.Name, StringComparison.CurrentCultureIgnoreCase)); return rv; } diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Sam.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Sam.cs index e87147fd35d..3e6bbcafd10 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Sam.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Sam.cs @@ -214,7 +214,7 @@ private enum ContextObjectType /// Used primarily by the private ThrowOnFailure method when building /// Exception objects to throw. /// - private class Context + private sealed class Context { public ContextOperation operation; public ContextObjectType type; @@ -308,7 +308,7 @@ public string MemberName /// AccountInfo is the return type from the private /// LookupAccountInfo method. /// - private class AccountInfo + private sealed class AccountInfo { public string AccountName; public string DomainName; @@ -3145,8 +3145,7 @@ internal sealed class OperatingSystem internal OperatingSystem(Version version, string servicePack) { - if (version == null) - throw new ArgumentNullException("version"); + ArgumentNullException.ThrowIfNull(version); _version = version; _servicePack = servicePack; diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/StringUtil.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/StringUtil.cs index 3534b34cc49..25bbeb49329 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/StringUtil.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/StringUtil.cs @@ -12,7 +12,7 @@ namespace System.Management.Automation.SecurityAccountsManager internal class StringUtil { /// - /// Private constructor to precent auto-generation of a default constructor with greater accessability. + /// Private constructor to present auto-generation of a default constructor with greater accessibility. /// private StringUtil() { diff --git a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj index b6d7fa7f936..02e93e683e2 100644 --- a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj +++ b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj @@ -15,22 +15,29 @@ + + + - + - - - - - - - - - - + + + + + + + + + + + - - + diff --git a/src/Microsoft.PowerShell.ScheduledJob/AssemblyInfo.cs b/src/Microsoft.PowerShell.ScheduledJob/AssemblyInfo.cs deleted file mode 100644 index 3e2e4ff3268..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/AssemblyInfo.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Reflection; -using System.Resources; - -[assembly:AssemblyFileVersionAttribute("3.0.0.0")] -[assembly:AssemblyVersion("3.0.0.0")] - -[assembly:AssemblyCulture("")] -[assembly:NeutralResourcesLanguage("en-US")] diff --git a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJob.cs b/src/Microsoft.PowerShell.ScheduledJob/ScheduledJob.cs deleted file mode 100644 index 4c7dcb146a9..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJob.cs +++ /dev/null @@ -1,1285 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.IO; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Management.Automation.Runspaces; -using System.Runtime.Serialization; -using System.Security.Permissions; -using System.Text; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This is a Job2 derived class that contains a DefinitionJob for - /// running job definition based jobs but can also save and load job - /// results data from file. This class is used to load job result - /// data from previously run jobs so that a user can view results of - /// scheduled job runs. This class also contains the definition of - /// the scheduled job and so can run an instance of the scheduled - /// job and optionally save results to file. - /// - [Serializable] - public sealed class ScheduledJob : Job2, ISerializable - { - #region Private Members - - private ScheduledJobDefinition _jobDefinition; - private Runspace _runspace; - private System.Management.Automation.PowerShell _powerShell; - private Job _job = null; - private bool _asyncJobStop; - private bool _allowSetShouldExit; - private PSHost _host; - - private const string AllowHostSetShouldExit = "AllowSetShouldExitFromRemote"; - - private StatusInfo _statusInfo; - - #endregion - - #region Public Properties - - /// - /// ScheduledJobDefinition. - /// - public ScheduledJobDefinition Definition - { - get { return _jobDefinition; } - - internal set { _jobDefinition = value; } - } - - /// - /// Location of job being run. - /// - public override string Location - { - get - { - return Status.Location; - } - } - - /// - /// Status Message associated with the Job. - /// - public override string StatusMessage - { - get - { - return Status.StatusMessage; - } - } - - /// - /// Indicates whether more data is available from Job. - /// - public override bool HasMoreData - { - get - { - return (_job != null) ? - _job.HasMoreData - : - (Output.Count > 0 || - Error.Count > 0 || - Warning.Count > 0 || - Verbose.Count > 0 || - Progress.Count > 0 || - Debug.Count > 0 || - Information.Count > 0 - ); - } - } - - /// - /// Job command string. - /// - public new string Command - { - get - { - return Status.Command; - } - } - - /// - /// Internal property indicating whether a SetShouldExit is honored - /// while running the scheduled job script. - /// - internal bool AllowSetShouldExit - { - get { return _allowSetShouldExit; } - - set { _allowSetShouldExit = value; } - } - - #endregion - - #region Constructors - - /// - /// Constructor. - /// - /// Job command string for display. - /// Name of job. - /// ScheduledJobDefinition defining job to run. - public ScheduledJob( - string command, - string name, - ScheduledJobDefinition jobDefinition) : - base(command, name) - { - if (command == null) - { - throw new PSArgumentNullException("command"); - } - - if (name == null) - { - throw new PSArgumentNullException("name"); - } - - if (jobDefinition == null) - { - throw new PSArgumentNullException("jobDefinition"); - } - - _jobDefinition = jobDefinition; - - PSJobTypeName = ScheduledJobSourceAdapter.AdapterTypeName; - } - - #endregion - - #region Public Overrides - - /// - /// Starts a job as defined by the contained ScheduledJobDefinition object. - /// - public override void StartJob() - { - lock (SyncRoot) - { - if (_job != null && !IsFinishedState(_job.JobStateInfo.State)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.JobAlreadyRunning, _jobDefinition.Name); - throw new PSInvalidOperationException(msg); - } - - _statusInfo = null; - _asyncJobStop = false; - PSBeginTime = DateTime.Now; - - if (_powerShell == null) - { - InitialSessionState iss = InitialSessionState.CreateDefault2(); - iss.Commands.Clear(); - iss.Formats.Clear(); - iss.Commands.Add( - new SessionStateCmdletEntry("Start-Job", typeof(Microsoft.PowerShell.Commands.StartJobCommand), null)); - - // Get the default host from the default runspace. - _host = GetDefaultHost(); - _runspace = RunspaceFactory.CreateRunspace(_host, iss); - _runspace.Open(); - _powerShell = System.Management.Automation.PowerShell.Create(); - _powerShell.Runspace = _runspace; - - // Indicate SetShouldExit to host. - AddSetShouldExitToHost(); - } - else - { - _powerShell.Commands.Clear(); - } - - _job = StartJobCommand(_powerShell); - - _job.StateChanged += new EventHandler(HandleJobStateChanged); - SetJobState(_job.JobStateInfo.State); - - // Add all child jobs to this object's list so that - // the user and Receive-Job can retrieve results. - foreach (Job childJob in _job.ChildJobs) - { - this.ChildJobs.Add(childJob); - } - - // Add this job to the local repository. - ScheduledJobSourceAdapter.AddToRepository(this); - } - } - - /// - /// Start job asynchronously. - /// - public override void StartJobAsync() - { - // StartJob(); - throw new PSNotSupportedException(); - } - - /// - /// Stop the job. - /// - public override void StopJob() - { - Job job; - JobState state; - lock (SyncRoot) - { - job = _job; - state = Status.State; - _asyncJobStop = false; - } - - if (IsFinishedState(state)) - { - return; - } - - if (job == null) - { - // Set job state to failed so that it can be removed from the - // cache using Remove-Job. - SetJobState(JobState.Failed); - } - else - { - job.StopJob(); - } - } - - /// - /// Stop the job asynchronously. - /// - public override void StopJobAsync() - { - Job job; - JobState state; - lock (SyncRoot) - { - job = _job; - state = Status.State; - _asyncJobStop = true; - } - - if (IsFinishedState(state)) - { - return; - } - - if (job == null) - { - // Set job state to failed so that it can be removed from the - // cache using Remove-Job. - SetJobState(JobState.Failed); - HandleJobStateChanged(this, - new JobStateEventArgs( - new JobStateInfo(JobState.Failed))); - } - else - { - job.StopJob(); - } - } - - /// - /// SuspendJob. - /// - public override void SuspendJob() - { - throw new PSNotSupportedException(); - } - - /// - /// SuspendJobAsync. - /// - public override void SuspendJobAsync() - { - throw new PSNotSupportedException(); - } - - /// - /// ResumeJob. - /// - public override void ResumeJob() - { - throw new PSNotSupportedException(); - } - - /// - /// ResumeJobAsync. - /// - public override void ResumeJobAsync() - { - throw new PSNotSupportedException(); - } - - /// - /// UnblockJob. - /// - public override void UnblockJob() - { - throw new PSNotSupportedException(); - } - - /// - /// UnblockJobAsync. - /// - public override void UnblockJobAsync() - { - throw new PSNotSupportedException(); - } - - /// - /// StopJob. - /// - /// - /// - public override void StopJob(bool force, string reason) - { - throw new PSNotSupportedException(); - } - - /// - /// StopJobAsync. - /// - /// - /// - public override void StopJobAsync(bool force, string reason) - { - throw new PSNotSupportedException(); - } - /// - /// SuspendJob. - /// - /// - /// - public override void SuspendJob(bool force, string reason) - { - throw new PSNotSupportedException(); - } - - /// - /// SuspendJobAsync. - /// - /// - /// - public override void SuspendJobAsync(bool force, string reason) - { - throw new PSNotSupportedException(); - } - - #endregion - - #region Implementation of ISerializable - - /// - /// Deserialize constructor. - /// - /// SerializationInfo. - /// StreamingContext. - private ScheduledJob( - SerializationInfo info, - StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException("info"); - } - - DeserializeStatusInfo(info); - DeserializeResultsInfo(info); - PSJobTypeName = ScheduledJobSourceAdapter.AdapterTypeName; - } - - /// - /// Serialize method. - /// - /// SerializationInfo. - /// StreamingContext. - public void GetObjectData( - SerializationInfo info, - StreamingContext context) - { - if (info == null) - { - throw new PSArgumentException("info"); - } - - SerializeStatusInfo(info); - SerializeResultsInfo(info); - } - - private void SerializeStatusInfo(SerializationInfo info) - { - StatusInfo statusInfo = new StatusInfo( - InstanceId, - Name, - Location, - Command, - StatusMessage, - (_job != null) ? _job.JobStateInfo.State : JobStateInfo.State, - HasMoreData, - PSBeginTime, - PSEndTime, - _jobDefinition); - - info.AddValue("StatusInfo", statusInfo); - } - - private void SerializeResultsInfo(SerializationInfo info) - { - // All other job information is in the child jobs. - Collection output = new Collection(); - Collection error = new Collection(); - Collection warning = new Collection(); - Collection verbose = new Collection(); - Collection progress = new Collection(); - Collection debug = new Collection(); - Collection information = new Collection(); - - if (_job != null) - { - // Collect data from "live" job. - - if (JobStateInfo.Reason != null) - { - error.Add(new ErrorRecord(JobStateInfo.Reason, "ScheduledJobFailedState", ErrorCategory.InvalidResult, null)); - } - - foreach (var item in _job.Error) - { - error.Add(item); - } - - foreach (Job childJob in ChildJobs) - { - if (childJob.JobStateInfo.Reason != null) - { - error.Add(new ErrorRecord(childJob.JobStateInfo.Reason, "ScheduledJobFailedState", ErrorCategory.InvalidResult, null)); - } - - foreach (var item in childJob.Output) - { - output.Add(item); - } - - foreach (var item in childJob.Error) - { - error.Add(item); - } - - foreach (var item in childJob.Warning) - { - warning.Add(item); - } - - foreach (var item in childJob.Verbose) - { - verbose.Add(item); - } - - foreach (var item in childJob.Progress) - { - progress.Add(item); - } - - foreach (var item in childJob.Debug) - { - debug.Add(item); - } - - foreach (var item in childJob.Information) - { - information.Add(item); - } - } - } - else - { - // Collect data from object collections. - - foreach (var item in Output) - { - // Wrap the base object in a new PSObject. This is necessary because the - // source deserialized PSObject doesn't serialize again correctly and breaks - // PS F&O. Not sure if this is a PSObject serialization bug or not. - output.Add(new PSObject(item.BaseObject)); - } - - foreach (var item in Error) - { - error.Add(item); - } - - foreach (var item in Warning) - { - warning.Add(item); - } - - foreach (var item in Verbose) - { - verbose.Add(item); - } - - foreach (var item in Progress) - { - progress.Add(item); - } - - foreach (var item in Debug) - { - debug.Add(item); - } - - foreach (var item in Information) - { - information.Add(item); - } - } - - ResultsInfo resultsInfo = new ResultsInfo( - output, error, warning, verbose, progress, debug, information); - - info.AddValue("ResultsInfo", resultsInfo); - } - - private void DeserializeStatusInfo(SerializationInfo info) - { - StatusInfo statusInfo = (StatusInfo)info.GetValue("StatusInfo", typeof(StatusInfo)); - - Name = statusInfo.Name; - PSBeginTime = statusInfo.StartTime; - PSEndTime = statusInfo.StopTime; - _jobDefinition = statusInfo.Definition; - SetJobState(statusInfo.State, null); - - lock (SyncRoot) - { - _statusInfo = statusInfo; - } - } - - private void DeserializeResultsInfo(SerializationInfo info) - { - ResultsInfo resultsInfo = (ResultsInfo)info.GetValue("ResultsInfo", typeof(ResultsInfo)); - - // Output - CopyOutput(resultsInfo.Output); - - // Error - CopyError(resultsInfo.Error); - - // Warning - CopyWarning(resultsInfo.Warning); - - // Verbose - CopyVerbose(resultsInfo.Verbose); - - // Progress - CopyProgress(resultsInfo.Progress); - - // Debug - CopyDebug(resultsInfo.Debug); - - // Information - CopyInformation(resultsInfo.Information); - } - - #endregion - - #region Internal Methods - - /// - /// Method to update a ScheduledJob based on new state and - /// result data from a provided Job. - /// - /// ScheduledJob to update from. - internal void Update(ScheduledJob fromJob) - { - // We do not update "live" jobs. - if (_job != null || fromJob == null) - { - return; - } - - // - // Update status. - // - PSEndTime = fromJob.PSEndTime; - JobState state = fromJob.JobStateInfo.State; - if (Status.State != state) - { - SetJobState(state, null); - } - - lock (SyncRoot) - { - _statusInfo = new StatusInfo( - fromJob.InstanceId, - fromJob.Name, - fromJob.Location, - fromJob.Command, - fromJob.StatusMessage, - state, - fromJob.HasMoreData, - fromJob.PSBeginTime, - fromJob.PSEndTime, - fromJob._jobDefinition); - } - - // - // Update results. - // - CopyOutput(fromJob.Output); - CopyError(fromJob.Error); - CopyWarning(fromJob.Warning); - CopyVerbose(fromJob.Verbose); - CopyProgress(fromJob.Progress); - CopyDebug(fromJob.Debug); - CopyInformation(fromJob.Information); - } - - #endregion - - #region Private Methods - - private System.Management.Automation.Host.PSHost GetDefaultHost() - { - System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace).AddScript("$host"); - Collection hosts = ps.Invoke(); - if (hosts == null || hosts.Count == 0) - { - System.Diagnostics.Debug.Assert(false, "Current runspace should always return default host."); - return null; - } - - return hosts[0]; - } - - private Job StartJobCommand(System.Management.Automation.PowerShell powerShell) - { - Job job = null; - - // Use PowerShell Start-Job cmdlet to run job. - powerShell.AddCommand("Start-Job"); - - powerShell.AddParameter("Name", _jobDefinition.Name); - - // Add job parameters from the JobInvocationInfo object. - CommandParameterCollection parameters = _jobDefinition.InvocationInfo.Parameters[0]; - foreach (CommandParameter parameter in parameters) - { - switch (parameter.Name) - { - case "ScriptBlock": - powerShell.AddParameter("ScriptBlock", parameter.Value as ScriptBlock); - break; - - case "FilePath": - powerShell.AddParameter("FilePath", parameter.Value as string); - break; - - case "RunAs32": - powerShell.AddParameter("RunAs32", (bool)parameter.Value); - break; - - case "Authentication": - powerShell.AddParameter("Authentication", (AuthenticationMechanism)parameter.Value); - break; - - case "InitializationScript": - powerShell.AddParameter("InitializationScript", parameter.Value as ScriptBlock); - break; - - case "ArgumentList": - powerShell.AddParameter("ArgumentList", parameter.Value as object[]); - break; - } - } - - // Start the job. - Collection rtn = powerShell.Invoke(); - if (rtn != null && rtn.Count == 1) - { - job = rtn[0].BaseObject as Job; - } - - return job; - } - - private void HandleJobStateChanged(object sender, JobStateEventArgs e) - { - SetJobState(e.JobStateInfo.State); - - if (IsFinishedState(e.JobStateInfo.State)) - { - PSEndTime = DateTime.Now; - - // Dispose the PowerShell and Runspace objects. - System.Management.Automation.PowerShell disposePowerShell = null; - Runspace disposeRunspace = null; - lock (SyncRoot) - { - if (_job != null && - IsFinishedState(_job.JobStateInfo.State)) - { - disposePowerShell = _powerShell; - _powerShell = null; - disposeRunspace = _runspace; - _runspace = null; - } - } - - if (disposePowerShell != null) - { - disposePowerShell.Dispose(); - } - - if (disposeRunspace != null) - { - disposeRunspace.Dispose(); - } - - // Raise async job stopped event, if needed. - if (_asyncJobStop) - { - _asyncJobStop = false; - OnStopJobCompleted(new AsyncCompletedEventArgs(null, false, null)); - } - - // Remove AllowSetShouldExit from host. - RemoveSetShouldExitFromHost(); - } - } - - internal bool IsFinishedState(JobState state) - { - return (state == JobState.Completed || state == JobState.Failed || state == JobState.Stopped); - } - - private StatusInfo Status - { - get - { - StatusInfo statusInfo; - lock (SyncRoot) - { - if (_statusInfo != null) - { - // Pass back static status. - statusInfo = _statusInfo; - } - else if (_job != null) - { - // Create current job status. - statusInfo = new StatusInfo( - _job.InstanceId, - _job.Name, - _job.Location, - _job.Command, - _job.StatusMessage, - _job.JobStateInfo.State, - _job.HasMoreData, - PSBeginTime, - PSEndTime, - _jobDefinition); - } - else - { - // Create default static empty status. - _statusInfo = new StatusInfo( - Guid.Empty, - string.Empty, - string.Empty, - string.Empty, - string.Empty, - JobState.NotStarted, - false, - PSBeginTime, - PSEndTime, - _jobDefinition); - - statusInfo = _statusInfo; - } - } - - return statusInfo; - } - } - - private void CopyOutput(ICollection fromOutput) - { - PSDataCollection output = CopyResults(fromOutput); - if (output != null) - { - try - { - Output = output; - } - catch (InvalidJobStateException) { } - } - } - - private void CopyError(ICollection fromError) - { - PSDataCollection error = CopyResults(fromError); - if (error != null) - { - try - { - Error = error; - } - catch (InvalidJobStateException) { } - } - } - - private void CopyWarning(ICollection fromWarning) - { - PSDataCollection warning = CopyResults(fromWarning); - if (warning != null) - { - try - { - Warning = warning; - } - catch (InvalidJobStateException) { } - } - } - - private void CopyVerbose(ICollection fromVerbose) - { - PSDataCollection verbose = CopyResults(fromVerbose); - if (verbose != null) - { - try - { - Verbose = verbose; - } - catch (InvalidJobStateException) { } - } - } - - private void CopyProgress(ICollection fromProgress) - { - PSDataCollection progress = CopyResults(fromProgress); - if (progress != null) - { - try - { - Progress = progress; - } - catch (InvalidJobStateException) { } - } - } - - private void CopyDebug(ICollection fromDebug) - { - PSDataCollection debug = CopyResults(fromDebug); - if (debug != null) - { - try - { - Debug = debug; - } - catch (InvalidJobStateException) { } - } - } - - private void CopyInformation(ICollection fromInformation) - { - PSDataCollection information = CopyResults(fromInformation); - if (information != null) - { - try - { - Information = information; - } - catch (InvalidJobStateException) { } - } - } - - private PSDataCollection CopyResults(ICollection fromResults) - { - if (fromResults != null && fromResults.Count > 0) - { - PSDataCollection returnResults = new PSDataCollection(); - foreach (var item in fromResults) - { - returnResults.Add(item); - } - - return returnResults; - } - - return null; - } - - private void AddSetShouldExitToHost() - { - if (!_allowSetShouldExit || _host == null) { return; } - - PSObject hostPrivateData = _host.PrivateData as PSObject; - if (hostPrivateData != null) - { - // Adds or replaces. - hostPrivateData.Properties.Add(new PSNoteProperty(AllowHostSetShouldExit, true)); - } - } - - private void RemoveSetShouldExitFromHost() - { - if (!_allowSetShouldExit || _host == null) { return; } - - PSObject hostPrivateData = _host.PrivateData as PSObject; - if (hostPrivateData != null) - { - // Removes if exists. - hostPrivateData.Properties.Remove(AllowHostSetShouldExit); - } - } - - #endregion - - #region Private ResultsInfo class - - [Serializable] - private class ResultsInfo : ISerializable - { - // Private Members - private Collection _output; - private Collection _error; - private Collection _warning; - private Collection _verbose; - private Collection _progress; - private Collection _debug; - private Collection _information; - - // Properties - internal Collection Output - { - get { return _output; } - } - - internal Collection Error - { - get { return _error; } - } - - internal Collection Warning - { - get { return _warning; } - } - - internal Collection Verbose - { - get { return _verbose; } - } - - internal Collection Progress - { - get { return _progress; } - } - - internal Collection Debug - { - get { return _debug; } - } - - internal Collection Information - { - get { return _information; } - } - - // Constructors - internal ResultsInfo( - Collection output, - Collection error, - Collection warning, - Collection verbose, - Collection progress, - Collection debug, - Collection information - ) - { - if (output == null) - { - throw new PSArgumentNullException("output"); - } - - if (error == null) - { - throw new PSArgumentNullException("error"); - } - - if (warning == null) - { - throw new PSArgumentNullException("warning"); - } - - if (verbose == null) - { - throw new PSArgumentNullException("verbose"); - } - - if (progress == null) - { - throw new PSArgumentNullException("progress"); - } - - if (debug == null) - { - throw new PSArgumentNullException("debug"); - } - - if (information == null) - { - throw new PSArgumentNullException("information"); - } - - _output = output; - _error = error; - _warning = warning; - _verbose = verbose; - _progress = progress; - _debug = debug; - _information = information; - } - - // ISerializable - private ResultsInfo( - SerializationInfo info, - StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException("info"); - } - - _output = (Collection)info.GetValue("Results_Output", typeof(Collection)); - _error = (Collection)info.GetValue("Results_Error", typeof(Collection)); - _warning = (Collection)info.GetValue("Results_Warning", typeof(Collection)); - _verbose = (Collection)info.GetValue("Results_Verbose", typeof(Collection)); - _progress = (Collection)info.GetValue("Results_Progress", typeof(Collection)); - _debug = (Collection)info.GetValue("Results_Debug", typeof(Collection)); - - try - { - _information = (Collection)info.GetValue("Results_Information", typeof(Collection)); - } - catch(SerializationException) - { - // The job might not have the info stream. Ignore. - _information = new Collection(); - } - } - - public void GetObjectData( - SerializationInfo info, - StreamingContext context) - { - if (info == null) - { - throw new PSArgumentException("info"); - } - - info.AddValue("Results_Output", _output); - info.AddValue("Results_Error", _error); - info.AddValue("Results_Warning", _warning); - info.AddValue("Results_Verbose", _verbose); - info.AddValue("Results_Progress", _progress); - info.AddValue("Results_Debug", _debug); - info.AddValue("Results_Information", _information); - } - } - - #endregion - } - - #region Internal StatusInfo Class - - [Serializable] - internal class StatusInfo : ISerializable - { - // Private Members - private Guid _instanceId; - private string _name; - private string _location; - private string _command; - private string _statusMessage; - private JobState _jobState; - private bool _hasMoreData; - private DateTime? _startTime; - private DateTime? _stopTime; - private ScheduledJobDefinition _definition; - - // Properties - internal Guid InstanceId - { - get { return _instanceId; } - } - - internal string Name - { - get { return _name; } - } - - internal string Location - { - get { return _location; } - } - - internal string Command - { - get { return _command; } - } - - internal string StatusMessage - { - get { return _statusMessage; } - } - - internal JobState State - { - get { return _jobState; } - } - - internal bool HasMoreData - { - get { return _hasMoreData; } - } - - internal DateTime? StartTime - { - get { return _startTime; } - } - - internal DateTime? StopTime - { - get { return _stopTime; } - } - - internal ScheduledJobDefinition Definition - { - get { return _definition; } - } - - // Constructors - internal StatusInfo( - Guid instanceId, - string name, - string location, - string command, - string statusMessage, - JobState jobState, - bool hasMoreData, - DateTime? startTime, - DateTime? stopTime, - ScheduledJobDefinition definition) - { - if (definition == null) - { - throw new PSArgumentNullException("definition"); - } - - _instanceId = instanceId; - _name = name; - _location = location; - _command = command; - _statusMessage = statusMessage; - _jobState = jobState; - _hasMoreData = hasMoreData; - _startTime = startTime; - _stopTime = stopTime; - _definition = definition; - } - - // ISerializable - private StatusInfo( - SerializationInfo info, - StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException("info"); - } - - _instanceId = Guid.Parse(info.GetString("Status_InstanceId")); - _name = info.GetString("Status_Name"); - _location = info.GetString("Status_Location"); - _command = info.GetString("Status_Command"); - _statusMessage = info.GetString("Status_Message"); - _jobState = (JobState)info.GetValue("Status_State", typeof(JobState)); - _hasMoreData = info.GetBoolean("Status_MoreData"); - _definition = (ScheduledJobDefinition)info.GetValue("Status_Definition", typeof(ScheduledJobDefinition)); - - DateTime startTime = info.GetDateTime("Status_StartTime"); - if (startTime != DateTime.MinValue) - { - _startTime = startTime; - } - else - { - _startTime = null; - } - - DateTime stopTime = info.GetDateTime("Status_StopTime"); - if (stopTime != DateTime.MinValue) - { - _stopTime = stopTime; - } - else - { - _stopTime = null; - } - } - - public void GetObjectData( - SerializationInfo info, - StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException("info"); - } - - info.AddValue("Status_InstanceId", _instanceId); - info.AddValue("Status_Name", _name); - info.AddValue("Status_Location", _location); - info.AddValue("Status_Command", _command); - info.AddValue("Status_Message", _statusMessage); - info.AddValue("Status_State", _jobState); - info.AddValue("Status_MoreData", _hasMoreData); - info.AddValue("Status_Definition", _definition); - - if (_startTime != null) - { - info.AddValue("Status_StartTime", _startTime); - } - else - { - info.AddValue("Status_StartTime", DateTime.MinValue); - } - - if (_stopTime != null) - { - info.AddValue("Status_StopTime", _stopTime); - } - else - { - info.AddValue("Status_StopTime", DateTime.MinValue); - } - } - } - - #endregion -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobDefinition.cs b/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobDefinition.cs deleted file mode 100644 index ecb4fc9caa6..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobDefinition.cs +++ /dev/null @@ -1,2585 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Globalization; -using System.IO; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Management.Automation.Tracing; -using System.Runtime.Serialization; -using System.Security.Permissions; -using System.Text.RegularExpressions; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This class contains all information needed to define a PowerShell job that - /// can be scheduled to run through either stand-alone or through the Windows - /// Task Scheduler. - /// - [Serializable] - public sealed class ScheduledJobDefinition : ISerializable, IDisposable - { - #region Private Members - - private JobInvocationInfo _invocationInfo; - private ScheduledJobOptions _options; - private PSCredential _credential; - private Guid _globalId = Guid.NewGuid(); - private string _name = string.Empty; - private int _id = GetCurrentId(); - private int _executionHistoryLength = DefaultExecutionHistoryLength; - private bool _enabled = true; - private Dictionary _triggers = new Dictionary(); - private Int32 _currentTriggerId; - - private string _definitionFilePath; - private string _definitionOutputPath; - - private bool _isDisposed; - - // Task Action strings. - private const string TaskExecutionPath = @"pwsh.exe"; - private const string TaskArguments = @"-NoLogo -NonInteractive -WindowStyle Hidden -Command ""Import-Module PSScheduledJob; $jobDef = [Microsoft.PowerShell.ScheduledJob.ScheduledJobDefinition]::LoadFromStore('{0}', '{1}'); $jobDef.Run()"""; - private static object LockObject = new object(); - private static int CurrentId = 0; - private static int DefaultExecutionHistoryLength = 32; - - internal static ScheduledJobDefinitionRepository Repository = new ScheduledJobDefinitionRepository(); - - // Task Scheduler COM error codes. - private const int TSErrorDisabledTask = -2147216602; - - #endregion - - #region Public Properties - - /// - /// Contains information needed to run the job such as script parameters, - /// job definition, user credentials, etc. - /// - public JobInvocationInfo InvocationInfo - { - get { return _invocationInfo; } - } - - /// - /// Contains the script commands that define the job. - /// - public JobDefinition Definition - { - get { return _invocationInfo.Definition; } - } - - /// - /// Specifies Task Scheduler options for the scheduled job. - /// - public ScheduledJobOptions Options - { - get { return new ScheduledJobOptions(_options); } - } - - /// - /// Credential. - /// - public PSCredential Credential - { - get { return _credential; } - - internal set { _credential = value; } - } - - /// - /// An array of trigger objects that specify a time/condition - /// for when the job is run. - /// - public List JobTriggers - { - get - { - List notFoundIds; - return GetTriggers(null, out notFoundIds); - } - } - - /// - /// Local instance Id for object instance. - /// - public int Id - { - get { return _id; } - } - - /// - /// Global Id for scheduled job definition. - /// - public Guid GlobalId - { - get { return _globalId; } - } - - /// - /// Name of scheduled job definition. - /// - public string Name - { - get { return _name; } - } - - /// - /// Job command. - /// - public string Command - { - get { return _invocationInfo.Command; } - } - - /// - /// Returns the maximum number of job execution data - /// allowed in the job store. - /// - public int ExecutionHistoryLength - { - get { return _executionHistoryLength; } - } - - /// - /// Determines whether this scheduled job definition is enabled - /// in Task Scheduler. - /// - public bool Enabled - { - get { return _enabled; } - } - - /// - /// Returns the PowerShell command line execution path. - /// - public string PSExecutionPath - { - get { return TaskExecutionPath; } - } - - /// - /// Returns PowerShell command line arguments to run - /// the scheduled job. - /// - public string PSExecutionArgs - { - get - { - // Escape single quotes in name. Double quotes are not allowed - // and are caught during name validation. - string nameEscapeQuotes = _invocationInfo.Name.Replace("'", "''"); - - return string.Format(CultureInfo.InvariantCulture, TaskArguments, nameEscapeQuotes, _definitionFilePath); - } - } - - /// - /// Returns the job run output path for this job definition. - /// - internal string OutputPath - { - get { return _definitionOutputPath; } - } - - #endregion - - #region Constructors - - /// - /// Default constructor is not accessible. - /// - private ScheduledJobDefinition() - { } - - /// - /// Constructor. - /// - /// Information to invoke Job. - /// ScheduledJobTriggers. - /// ScheduledJobOptions. - /// Credential. - public ScheduledJobDefinition( - JobInvocationInfo invocationInfo, - IEnumerable triggers, - ScheduledJobOptions options, - PSCredential credential) - { - if (invocationInfo == null) - { - throw new PSArgumentNullException("invocationInfo"); - } - - _name = invocationInfo.Name; - _invocationInfo = invocationInfo; - - SetTriggers(triggers, false); - _options = (options != null) ? new ScheduledJobOptions(options) : - new ScheduledJobOptions(); - _options.JobDefinition = this; - - _credential = credential; - } - - #endregion - - #region ISerializable Implementation - - /// - /// Serialization constructor. - /// - /// SerializationInfo. - /// StreamingContext. - private ScheduledJobDefinition( - SerializationInfo info, - StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException("info"); - } - - _options = (ScheduledJobOptions)info.GetValue("Options_Member", typeof(ScheduledJobOptions)); - _globalId = Guid.Parse(info.GetString("GlobalId_Member")); - _name = info.GetString("Name_Member"); - _executionHistoryLength = info.GetInt32("HistoryLength_Member"); - _enabled = info.GetBoolean("Enabled_Member"); - _triggers = (Dictionary)info.GetValue("Triggers_Member", typeof(Dictionary)); - _currentTriggerId = info.GetInt32("CurrentTriggerId_Member"); - _definitionFilePath = info.GetString("FilePath_Member"); - _definitionOutputPath = info.GetString("OutputPath_Member"); - - object invocationObject = info.GetValue("InvocationInfo_Member", typeof(object)); - _invocationInfo = invocationObject as JobInvocationInfo; - - // Set the JobDefinition reference for the ScheduledJobTrigger and - // ScheduledJobOptions objects. - _options.JobDefinition = this; - foreach (ScheduledJobTrigger trigger in _triggers.Values) - { - trigger.JobDefinition = this; - } - - // Instance information. - _isDisposed = false; - } - - /// - /// Serialization constructor. - /// - /// SerializationInfo. - /// StreamingContext. - public void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException("info"); - } - - info.AddValue("Options_Member", _options); - info.AddValue("GlobalId_Member", _globalId.ToString()); - info.AddValue("Name_Member", _name); - info.AddValue("HistoryLength_Member", _executionHistoryLength); - info.AddValue("Enabled_Member", _enabled); - info.AddValue("Triggers_Member", _triggers); - info.AddValue("CurrentTriggerId_Member", _currentTriggerId); - info.AddValue("FilePath_Member", _definitionFilePath); - info.AddValue("OutputPath_Member", _definitionOutputPath); - - info.AddValue("InvocationInfo_Member", _invocationInfo); - } - - #endregion - - #region Private Methods - - /// - /// Updates existing information if scheduled job already exists. - /// WTS entry includes command line, options, and trigger conditions. - /// - private void UpdateWTSFromDefinition() - { - using (ScheduledJobWTS taskScheduler = new ScheduledJobWTS()) - { - taskScheduler.UpdateTask(this); - } - } - - /// - /// Compares the current ScheduledJobDefinition task scheduler information - /// with the corresponding information stored in Task Scheduler. If the - /// information is different then the task scheduler information in this - /// object is updated to match what is in Task Scheduler, since that information - /// takes precedence. - /// - /// Task Scheduler information: - /// - Triggers - /// - Options - /// - Enabled state. - /// - /// Boolean if this object data is modified. - private bool UpdateDefinitionFromWTS() - { - bool dataModified = false; - - // Get information from Task Scheduler. - using (ScheduledJobWTS taskScheduler = new ScheduledJobWTS()) - { - bool wtsEnabled = taskScheduler.GetTaskEnabled(_name); - ScheduledJobOptions wtsOptions = taskScheduler.GetJobOptions(_name); - Collection wtsTriggers = taskScheduler.GetJobTriggers(_name); - - // - // Compare with existing object data and modify if necessary. - // - - // Enabled. - if (wtsEnabled != _enabled) - { - _enabled = wtsEnabled; - dataModified = true; - } - - // Options. - if (wtsOptions.DoNotAllowDemandStart != _options.DoNotAllowDemandStart || - wtsOptions.IdleDuration != _options.IdleDuration || - wtsOptions.IdleTimeout != _options.IdleTimeout || - wtsOptions.MultipleInstancePolicy != _options.MultipleInstancePolicy || - wtsOptions.RestartOnIdleResume != _options.RestartOnIdleResume || - wtsOptions.RunElevated != _options.RunElevated || - wtsOptions.RunWithoutNetwork != _options.RunWithoutNetwork || - wtsOptions.ShowInTaskScheduler != _options.ShowInTaskScheduler || - wtsOptions.StartIfNotIdle != _options.StartIfNotIdle || - wtsOptions.StartIfOnBatteries != _options.StartIfOnBatteries || - wtsOptions.StopIfGoingOffIdle != _options.StopIfGoingOffIdle || - wtsOptions.StopIfGoingOnBatteries != _options.StopIfGoingOnBatteries || - wtsOptions.WakeToRun != _options.WakeToRun) - { - // Keep the current scheduled job definition reference. - wtsOptions.JobDefinition = _options.JobDefinition; - _options = wtsOptions; - dataModified = true; - } - - // Triggers. - if (_triggers.Count != wtsTriggers.Count) - { - SetTriggers(wtsTriggers, false); - dataModified = true; - } - else - { - bool foundTriggerDiff = false; - - // Compare each trigger object. - foreach (var wtsTrigger in wtsTriggers) - { - if (_triggers.ContainsKey(wtsTrigger.Id) == false) - { - foundTriggerDiff = true; - break; - } - - ScheduledJobTrigger trigger = _triggers[wtsTrigger.Id]; - if (trigger.DaysOfWeek != wtsTrigger.DaysOfWeek || - trigger.Enabled != wtsTrigger.Enabled || - trigger.Frequency != wtsTrigger.Frequency || - trigger.Interval != wtsTrigger.Interval || - trigger.RandomDelay != wtsTrigger.RandomDelay || - trigger.At != wtsTrigger.At || - trigger.User != wtsTrigger.User) - { - foundTriggerDiff = true; - break; - } - } - - if (foundTriggerDiff) - { - SetTriggers(wtsTriggers, false); - dataModified = true; - } - } - } - - return dataModified; - } - - /// - /// Adds this scheduled job definition to the Task Scheduler. - /// - private void AddToWTS() - { - using (ScheduledJobWTS taskScheduler = new ScheduledJobWTS()) - { - taskScheduler.CreateTask(this); - } - } - - /// - /// Removes this scheduled job definition from the Task Scheduler. - /// This operation will fail if a current instance of this job definition - /// is running. - /// If force == true then all current instances will be stopped. - /// - /// Force removal and stop all running instances. - private void RemoveFromWTS(bool force) - { - using (ScheduledJobWTS taskScheduler = new ScheduledJobWTS()) - { - taskScheduler.RemoveTask(this, force); - } - } - - /// - /// Adds this scheduled job definition to the job definition store. - /// - private void AddToJobStore() - { - FileStream fs = null; - try - { - fs = ScheduledJobStore.CreateFileForJobDefinition(Name); - _definitionFilePath = ScheduledJobStore.GetJobDefinitionLocation(); - _definitionOutputPath = ScheduledJobStore.GetJobRunOutputDirectory(Name); - - XmlObjectSerializer serializer = new System.Runtime.Serialization.NetDataContractSerializer(); - serializer.WriteObject(fs, this); - fs.Flush(); - } - finally - { - if (fs != null) - { - fs.Close(); - } - } - - // If credentials are provided then update permissions. - if (Credential != null) - { - UpdateFilePermissions(Credential.UserName); - } - } - - /// - /// Updates existing file with this definition information. - /// - private void UpdateJobStore() - { - FileStream fs = null; - try - { - // Overwrite the existing file. - fs = GetFileStream( - Name, - _definitionFilePath, - FileMode.Create, - FileAccess.Write, - FileShare.None); - - XmlObjectSerializer serializer = new System.Runtime.Serialization.NetDataContractSerializer(); - serializer.WriteObject(fs, this); - fs.Flush(); - } - finally - { - if (fs != null) - { - fs.Close(); - } - } - - // If credentials are provided then update permissions. - if (Credential != null) - { - UpdateFilePermissions(Credential.UserName); - } - } - - /// - /// Updates definition file permissions for provided user account. - /// - /// Account user name. - private void UpdateFilePermissions(string user) - { - Exception ex = null; - try - { - // Add user for read access to the job definition file. - ScheduledJobStore.SetReadAccessOnDefinitionFile(Name, user); - - // Add user for write access to the job run Output directory. - ScheduledJobStore.SetWriteAccessOnJobRunOutput(Name, user); - } - catch (System.Security.Principal.IdentityNotMappedException e) - { - ex = e; - } - catch (IOException e) - { - ex = e; - } - catch (UnauthorizedAccessException e) - { - ex = e; - } - catch (ArgumentNullException e) - { - ex = e; - } - - if (ex != null) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorSettingAccessPermissions, this.Name, Credential.UserName); - throw new ScheduledJobException(msg, ex); - } - } - - /// - /// Removes this scheduled job definition from the job definition store. - /// - private void RemoveFromJobStore() - { - ScheduledJobStore.RemoveJobDefinition(Name); - } - - /// - /// Throws exception if object is disposed. - /// - private void IsDisposed() - { - if (_isDisposed == true) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.DefinitionObjectDisposed, Name); - throw new RuntimeException(msg); - } - } - - /// - /// If repository is empty try refreshing it from the store. - /// - private void LoadRepository() - { - ScheduledJobDefinition.RefreshRepositoryFromStore(); - } - - /// - /// Validates all triggers in collection. An exception is thrown - /// for invalid triggers. - /// - /// - private void ValidateTriggers(IEnumerable triggers) - { - if (triggers != null) - { - foreach (var trigger in triggers) - { - trigger.Validate(); - } - } - } - - /// - /// Validates the job definition name. Since the job definition - /// name is used in the job store as a directory name, make sure - /// it does not contain any invalid characters. - /// - private static void ValidateName(string name) - { - if (name.IndexOfAny(System.IO.Path.GetInvalidFileNameChars()) != -1) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidJobDefName, name); - throw new ScheduledJobException(msg); - } - } - - /// - /// Iterates through all job run files, opens each job - /// run and renames it to the provided new name. - /// - /// New job run name. - private void UpdateJobRunNames( - string newDefName) - { - // Job run results will be under the new scheduled job definition name. - Collection jobRuns = ScheduledJobSourceAdapter.GetJobRuns(newDefName); - if (jobRuns == null) - { - return; - } - - // Load and rename each job. - ScheduledJobDefinition definition = ScheduledJobDefinition.LoadFromStore(newDefName, null); - foreach (DateTime jobRun in jobRuns) - { - ScheduledJob job = null; - try - { - job = ScheduledJobSourceAdapter.LoadJobFromStore(definition.Name, jobRun) as ScheduledJob; - } - catch (ScheduledJobException) - { - continue; - } - catch (DirectoryNotFoundException) - { - continue; - } - catch (FileNotFoundException) - { - continue; - } - catch (UnauthorizedAccessException) - { - continue; - } - catch (IOException) - { - continue; - } - - if (job != null) - { - job.Name = newDefName; - job.Definition = definition; - ScheduledJobSourceAdapter.SaveJobToStore(job); - } - } - } - - /// - /// Handles known Task Scheduler COM error codes. - /// - /// COMException. - /// Error message. - private string ConvertCOMErrorCode(System.Runtime.InteropServices.COMException e) - { - string msg = null; - switch (e.ErrorCode) - { - case TSErrorDisabledTask: - msg = ScheduledJobErrorStrings.ReasonTaskDisabled; - break; - } - - return msg; - } - - #endregion - - #region Internal Methods - - /// - /// Save object to store. - /// - internal void SaveToStore() - { - IsDisposed(); - - UpdateJobStore(); - } - - /// - /// Compares the task scheduler information in this object with - /// what is stored in Task Scheduler. If there is a difference - /// then this object is updated with the information from Task - /// Scheduler and saved to the job store. - /// - internal void SyncWithWTS() - { - Exception notFoundEx = null; - try - { - if (UpdateDefinitionFromWTS()) - { - SaveToStore(); - } - } - catch (DirectoryNotFoundException e) - { - notFoundEx = e; - } - catch (FileNotFoundException e) - { - notFoundEx = e; - } - - if (notFoundEx != null) - { - // There is no corresponding Task Scheduler item for this - // scheduled job definition. Remove this definition from - // the job store for consistency. - Remove(true); - throw notFoundEx; - } - } - - /// - /// Renames scheduled job definition, store directory and task scheduler task. - /// - /// New name of job definition. - internal void RenameAndSave(string newName) - { - if (InvocationInfo.Name.Equals(newName, StringComparison.OrdinalIgnoreCase)) - { - return; - } - - ValidateName(newName); - - // Attempt to rename job store directory. Detect if new name - // is not unique. - string oldName = InvocationInfo.Name; - Exception ex = null; - try - { - ScheduledJobStore.RenameScheduledJobDefDir(oldName, newName); - } - catch (ArgumentException e) - { - ex = e; - } - catch (DirectoryNotFoundException e) - { - ex = e; - } - catch (FileNotFoundException e) - { - ex = e; - } - catch (UnauthorizedAccessException e) - { - ex = e; - } - catch (IOException e) - { - ex = e; - } - - if (ex != null) - { - string msg; - if (!string.IsNullOrEmpty(ex.Message)) - { - msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorRenamingScheduledJobWithMessage, oldName, newName, ex.Message); - } - else - { - msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorRenamingScheduledJob, oldName, newName); - } - - throw new ScheduledJobException(msg, ex); - } - - try - { - // Remove old named Task Scheduler task. - // This also stops any existing running job. - RemoveFromWTS(true); - - // Update job definition names. - _name = newName; - InvocationInfo.Name = newName; - InvocationInfo.Definition.Name = newName; - _definitionOutputPath = ScheduledJobStore.GetJobRunOutputDirectory(Name); - - // Update job definition in new job store location. - UpdateJobStore(); - - // Add new Task Scheduler task with new name. - // Jobs can start running again. - AddToWTS(); - - // Update any existing job run names. - UpdateJobRunNames(newName); - } - catch (ArgumentException e) - { - ex = e; - } - catch (DirectoryNotFoundException e) - { - ex = e; - } - catch (FileNotFoundException e) - { - ex = e; - } - catch (UnauthorizedAccessException e) - { - ex = e; - } - catch (IOException e) - { - ex = e; - } - finally - { - // Clear job run cache since job runs now appear in new directory location. - ScheduledJobSourceAdapter.ClearRepository(); - } - - // If any part of renaming the various scheduled job components fail, - // aggressively remove scheduled job corrupted state and inform user. - if (ex != null) - { - try - { - Remove(true); - } - catch (ScheduledJobException e) - { - ex.Data.Add("SchedJobRemoveError", e); - } - - string msg; - if (!string.IsNullOrEmpty(ex.Message)) - { - msg = StringUtil.Format(ScheduledJobErrorStrings.BrokenRenamingScheduledJobWithMessage, oldName, newName, ex.Message); - } - else - { - msg = StringUtil.Format(ScheduledJobErrorStrings.BrokenRenamingScheduledJob, oldName, newName); - } - - throw new ScheduledJobException(msg, ex); - } - } - - #endregion - - #region Public Methods - - /// - /// Registers this scheduled job definition object by doing the - /// following: - /// a) Writing this object to the scheduled job object store. - /// b) Registering this job as a Windows Task Scheduler task. - /// c) Adding this object to the local repository. - /// - public void Register() - { - IsDisposed(); - - LoadRepository(); - - ValidateName(Name); - - // First add to the job store. If an exception occurs here - // then this method fails with no clean up. - Exception ex = null; - bool corruptedFile = false; - try - { - AddToJobStore(); - } - catch (ArgumentException e) - { - ex = e; - } - catch (DirectoryNotFoundException e) - { - ex = e; - } - catch (FileNotFoundException e) - { - ex = e; - } - catch (UnauthorizedAccessException e) - { - ex = e; - } - catch (IOException e) - { - ex = e; - } - catch (System.Runtime.Serialization.SerializationException e) - { - corruptedFile = true; - ex = e; - } - catch (System.Runtime.Serialization.InvalidDataContractException e) - { - corruptedFile = true; - ex = e; - } - catch (ScheduledJobException e) - { - // Can be thrown for error setting file access permissions with supplied credentials. - // But file is not considered corrupted if it already exists. - corruptedFile = !(e.FQEID.Equals(ScheduledJobStore.ScheduledJobDefExistsFQEID, StringComparison.OrdinalIgnoreCase)); - ex = e; - } - - if (ex != null) - { - if (corruptedFile) - { - // Remove from store. - try - { - ScheduledJobStore.RemoveJobDefinition(Name); - } - catch (DirectoryNotFoundException) - { } - catch (FileNotFoundException) - { } - catch (UnauthorizedAccessException) - { } - catch (IOException) - { } - } - - if (ex is not ScheduledJobException) - { - // Wrap in ScheduledJobException type. - string msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorRegisteringDefinitionStore, this.Name); - throw new ScheduledJobException(msg, ex); - } - else - { - // Otherwise just re-throw. - throw ex; - } - } - - // Next register with the Task Scheduler. - ex = null; - try - { - AddToWTS(); - } - catch (ArgumentException e) - { - ex = e; - } - catch (DirectoryNotFoundException e) - { - ex = e; - } - catch (FileNotFoundException e) - { - ex = e; - } - catch (UnauthorizedAccessException e) - { - ex = e; - } - catch (IOException e) - { - ex = e; - } - catch (System.Runtime.InteropServices.COMException e) - { - ex = e; - } - - if (ex != null) - { - // Clean up job store. - RemoveFromJobStore(); - - string msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorRegisteringDefinitionTask, - this.Name, - (string.IsNullOrEmpty(ex.Message) == false) ? ex.Message : string.Empty); - throw new ScheduledJobException(msg, ex); - } - - // Finally add to the local repository. - Repository.AddOrReplace(this); - } - - /// - /// Saves this scheduled job definition object: - /// a) Rewrites this object to the scheduled job object store. - /// b) Updates the Windows Task Scheduler task. - /// - public void Save() - { - IsDisposed(); - - LoadRepository(); - - ValidateName(Name); - - // First update the Task Scheduler. If an exception occurs here then - // we fail with no clean up. - Exception ex = null; - try - { - UpdateWTSFromDefinition(); - } - catch (ArgumentException e) - { - ex = e; - } - catch (DirectoryNotFoundException e) - { - ex = e; - } - catch (FileNotFoundException e) - { - ex = e; - } - catch (UnauthorizedAccessException e) - { - ex = e; - } - catch (IOException e) - { - ex = e; - } - catch (System.Runtime.InteropServices.COMException e) - { - ex = e; - } - - if (ex != null) - { - // We want this object to remain synchronized with what is in WTS. - SyncWithWTS(); - - string msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorUpdatingDefinitionTask, this.Name); - throw new ScheduledJobException(msg, ex); - } - - // Next save to job store. - ex = null; - try - { - UpdateJobStore(); - } - catch (ArgumentException e) - { - ex = e; - } - catch (DirectoryNotFoundException e) - { - ex = e; - } - catch (FileNotFoundException e) - { - ex = e; - } - catch (UnauthorizedAccessException e) - { - ex = e; - } - catch (IOException e) - { - ex = e; - } - - if (ex != null) - { - // Remove this from WTS for consistency. - RemoveFromWTS(true); - - string msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorUpdatingDefinitionStore, this.Name); - throw new ScheduledJobException(msg, ex); - } - - // Finally update this object in the local repository. - ScheduledJobDefinition.RefreshRepositoryFromStore(); - Repository.AddOrReplace(this); - } - - /// - /// Removes this definition object: - /// a) Removes from the Task Scheduler - /// or fails if an instance is currently running. - /// or stops any running instances if force is true. - /// b) Removes from the scheduled job definition store. - /// c) Removes from the local repository. - /// d) Disposes this object. - /// - public void Remove(bool force) - { - IsDisposed(); - - // First remove from Task Scheduler. Catch not found - // exceptions and continue. - try - { - RemoveFromWTS(force); - } - catch (System.IO.DirectoryNotFoundException) - { - // Continue with removal. - } - catch (System.IO.FileNotFoundException) - { - // Continue with removal. - } - - // Remove from the Job Store. Catch exceptions and continue - // with removal. - Exception ex = null; - try - { - RemoveFromJobStore(); - } - catch (DirectoryNotFoundException) - { - } - catch (FileNotFoundException) - { - } - catch (ArgumentException e) - { - ex = e; - } - catch (UnauthorizedAccessException e) - { - ex = e; - } - catch (IOException e) - { - ex = e; - } - finally - { - // Remove from the local repository. - Repository.Remove(this); - - // Remove job runs for this definition from local repository. - ScheduledJobSourceAdapter.ClearRepositoryForDefinition(this.Name); - - // Dispose this object. - Dispose(); - } - - if (ex != null) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorRemovingDefinitionStore, this.Name); - throw new ScheduledJobException(msg, ex); - } - } - - /// - /// Starts the scheduled job immediately. A ScheduledJob object is - /// returned that represents the running command, and this returned - /// job is also added to the local job repository. Job results are - /// not written to the job store. - /// - /// ScheduledJob object for running job. - public ScheduledJob StartJob() - { - IsDisposed(); - - ScheduledJob job = new ScheduledJob(_invocationInfo.Command, _invocationInfo.Name, this); - job.StartJob(); - - return job; - } - - /// - /// Starts registered job definition running from the Task Scheduler. - /// - public void RunAsTask() - { - IsDisposed(); - - Exception ex = null; - string reason = null; - try - { - using (ScheduledJobWTS taskScheduler = new ScheduledJobWTS()) - { - taskScheduler.RunTask(this); - } - } - catch (System.IO.DirectoryNotFoundException e) - { - reason = ScheduledJobErrorStrings.reasonJobNotFound; - ex = e; - } - catch (System.IO.FileNotFoundException e) - { - reason = ScheduledJobErrorStrings.reasonJobNotFound; - ex = e; - } - catch (System.Runtime.InteropServices.COMException e) - { - reason = ConvertCOMErrorCode(e); - ex = e; - } - - if (ex != null) - { - string msg; - if (reason != null) - { - msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorRunningAsTaskWithReason, this.Name, reason); - } - else - { - msg = StringUtil.Format(ScheduledJobErrorStrings.ErrorRunningAsTask, this.Name); - } - - throw new ScheduledJobException(msg, ex); - } - } - - #endregion - - #region Public Trigger Methods - - /// - /// Adds new ScheduledJobTriggers. - /// - /// Collection of ScheduledJobTrigger objects. - /// Update Windows Task Scheduler and save to store. - public void AddTriggers( - IEnumerable triggers, - bool save) - { - IsDisposed(); - - if (triggers == null) - { - throw new PSArgumentNullException("triggers"); - } - - // First validate all triggers. - ValidateTriggers(triggers); - - Collection newTriggerIds = new Collection(); - foreach (ScheduledJobTrigger trigger in triggers) - { - ScheduledJobTrigger newTrigger = new ScheduledJobTrigger(trigger); - - newTrigger.Id = ++_currentTriggerId; - newTriggerIds.Add(newTrigger.Id); - newTrigger.JobDefinition = this; - _triggers.Add(newTrigger.Id, newTrigger); - } - - if (save) - { - Save(); - } - } - - /// - /// Removes triggers matching passed in trigger Ids. - /// - /// Trigger Ids to remove. - /// Update Windows Task Scheduler and save to store. - /// Trigger Ids not found. - public List RemoveTriggers( - IEnumerable triggerIds, - bool save) - { - IsDisposed(); - - List idsNotFound = new List(); - bool triggerFound = false; - - // triggerIds is null then remove all triggers. - if (triggerIds == null) - { - _currentTriggerId = 0; - if (_triggers.Count > 0) - { - triggerFound = true; - - foreach (ScheduledJobTrigger trigger in _triggers.Values) - { - trigger.Id = 0; - trigger.JobDefinition = null; - } - - // Create new empty trigger collection. - _triggers = new Dictionary(); - } - } - else - { - foreach (Int32 removeId in triggerIds) - { - if (_triggers.ContainsKey(removeId)) - { - _triggers[removeId].JobDefinition = null; - _triggers[removeId].Id = 0; - _triggers.Remove(removeId); - triggerFound = true; - } - else - { - idsNotFound.Add(removeId); - } - } - } - - if (save && triggerFound) - { - Save(); - } - - return idsNotFound; - } - - /// - /// Updates triggers with provided trigger objects, matching passed in - /// trigger Id with existing trigger Id. - /// - /// Collection of ScheduledJobTrigger objects to update. - /// Update Windows Task Scheduler and save to store. - /// Trigger Ids not found. - public List UpdateTriggers( - IEnumerable triggers, - bool save) - { - IsDisposed(); - - if (triggers == null) - { - throw new PSArgumentNullException("triggers"); - } - - // First validate all triggers. - ValidateTriggers(triggers); - - List idsNotFound = new List(); - bool triggerFound = false; - foreach (ScheduledJobTrigger updateTrigger in triggers) - { - if (_triggers.ContainsKey(updateTrigger.Id)) - { - // Disassociate old trigger from this definition. - _triggers[updateTrigger.Id].JobDefinition = null; - - // Replace older trigger object with new updated one. - ScheduledJobTrigger newTrigger = new ScheduledJobTrigger(updateTrigger); - newTrigger.Id = updateTrigger.Id; - newTrigger.JobDefinition = this; - _triggers[newTrigger.Id] = newTrigger; - triggerFound = true; - } - else - { - idsNotFound.Add(updateTrigger.Id); - } - } - - if (save && triggerFound) - { - Save(); - } - - return idsNotFound; - } - - /// - /// Creates a new set of ScheduledJobTriggers for this object. - /// - /// Array of ScheduledJobTrigger objects to set. - /// Update Windows Task Scheduler and save to store. - public void SetTriggers( - IEnumerable newTriggers, - bool save) - { - IsDisposed(); - - // First validate all triggers. - ValidateTriggers(newTriggers); - - // Disassociate any old trigger objects from this definition. - foreach (ScheduledJobTrigger trigger in _triggers.Values) - { - trigger.JobDefinition = null; - } - - _currentTriggerId = 0; - _triggers = new Dictionary(); - if (newTriggers != null) - { - foreach (ScheduledJobTrigger trigger in newTriggers) - { - ScheduledJobTrigger newTrigger = new ScheduledJobTrigger(trigger); - - newTrigger.Id = ++_currentTriggerId; - newTrigger.JobDefinition = this; - _triggers.Add(newTrigger.Id, newTrigger); - } - } - - if (save) - { - Save(); - } - } - - /// - /// Returns a list of new ScheduledJobTrigger objects corresponding - /// to the passed in trigger Ids. Also returns an array of trigger Ids - /// that were not found in an out parameter. - /// - /// List of trigger Ids. - /// List of not found trigger Ids. - /// List of ScheduledJobTrigger objects. - public List GetTriggers( - IEnumerable triggerIds, - out List notFoundIds) - { - IsDisposed(); - - List newTriggers; - List notFoundList = new List(); - if (triggerIds == null) - { - // Return all triggers. - newTriggers = new List(); - foreach (ScheduledJobTrigger trigger in _triggers.Values) - { - newTriggers.Add(new ScheduledJobTrigger(trigger)); - } - } - else - { - // Filter returned triggers to match requested. - newTriggers = new List(); - foreach (Int32 triggerId in triggerIds) - { - if (_triggers.ContainsKey(triggerId)) - { - newTriggers.Add(new ScheduledJobTrigger(_triggers[triggerId])); - } - else - { - notFoundList.Add(triggerId); - } - } - } - - notFoundIds = notFoundList; - - // Return array of ScheduledJobTrigger objects sorted by Id. - newTriggers.Sort((firstTrigger, secondTrigger) => - { - return ((int)firstTrigger.Id - (int)secondTrigger.Id); - }); - - return newTriggers; - } - - /// - /// Finds and returns a copy of the ScheduledJobTrigger corresponding to - /// the passed in trigger Id. - /// - /// Trigger Id. - /// ScheduledJobTrigger object. - public ScheduledJobTrigger GetTrigger( - Int32 triggerId) - { - IsDisposed(); - - if (_triggers.ContainsKey(triggerId)) - { - return new ScheduledJobTrigger(_triggers[triggerId]); - } - - return null; - } - - #endregion - - #region Public Update Methods - - /// - /// Updates scheduled job options. - /// - /// ScheduledJobOptions or null for default. - /// Update Windows Task Scheduler and save to store. - public void UpdateOptions( - ScheduledJobOptions options, - bool save) - { - IsDisposed(); - - // Disassociate current options object from this definition. - _options.JobDefinition = null; - - // options == null is allowed and signals the use default - // Task Scheduler options. - _options = (options != null) ? new ScheduledJobOptions(options) : - new ScheduledJobOptions(); - _options.JobDefinition = this; - - if (save) - { - Save(); - } - } - - /// - /// Sets the execution history length property. - /// - /// Execution history length. - /// Save to store. - public void SetExecutionHistoryLength( - int executionHistoryLength, - bool save) - { - IsDisposed(); - - _executionHistoryLength = executionHistoryLength; - - if (save) - { - SaveToStore(); - } - } - - /// - /// Clears all execution results in the job store. - /// - public void ClearExecutionHistory() - { - IsDisposed(); - - ScheduledJobStore.RemoveAllJobRuns(Name); - ScheduledJobSourceAdapter.ClearRepositoryForDefinition(Name); - } - - /// - /// Updates the JobInvocationInfo object. - /// - /// JobInvocationInfo. - /// Save to store. - public void UpdateJobInvocationInfo( - JobInvocationInfo jobInvocationInfo, - bool save) - { - IsDisposed(); - - if (jobInvocationInfo == null) - { - throw new PSArgumentNullException("jobInvocationInfo"); - } - - _invocationInfo = jobInvocationInfo; - _name = jobInvocationInfo.Name; - - if (save) - { - SaveToStore(); - } - } - - /// - /// Sets the enabled state of this object. - /// - /// True if enabled. - /// Update Windows Task Scheduler and save to store. - public void SetEnabled( - bool enabled, - bool save) - { - IsDisposed(); - - _enabled = enabled; - - if (save) - { - Save(); - } - } - - /// - /// Sets the name of this scheduled job definition. - /// - /// Name. - /// Update Windows Task Scheduler and save to store. - public void SetName( - string name, - bool save) - { - IsDisposed(); - - _name = (name != null) ? name : string.Empty; - - if (save) - { - Save(); - } - } - - #endregion - - #region IDisposable - - /// - /// Dispose. - /// - public void Dispose() - { - _isDisposed = true; - - GC.SuppressFinalize(this); - } - - #endregion - - #region Static Methods - - /// - /// Synchronizes the local ScheduledJobDefinition repository with the - /// scheduled job definitions in the job store. - /// - /// Callback delegate for each discovered item. - /// Dictionary of errors. - internal static Dictionary RefreshRepositoryFromStore( - Action itemFound = null) - { - Dictionary errors = new Dictionary(); - - // Get current list of job definition files in store, and create hash - // table for quick look up. - IEnumerable jobDefinitionPathNames = ScheduledJobStore.GetJobDefinitions(); - HashSet jobDefinitionNamesHash = new HashSet(); - foreach (string pathName in jobDefinitionPathNames) - { - // Remove path information and use job definition name only. - int indx = pathName.LastIndexOf('\\'); - string jobDefName = (indx != -1) ? pathName.Substring(indx + 1) : pathName; - jobDefinitionNamesHash.Add(jobDefName); - } - - // First remove definition objects not in store. - // Repository.Definitions returns a *copy* of current repository items. - foreach (ScheduledJobDefinition jobDef in Repository.Definitions) - { - if (jobDefinitionNamesHash.Contains(jobDef.Name) == false) - { - Repository.Remove(jobDef); - } - else - { - jobDefinitionNamesHash.Remove(jobDef.Name); - - if (itemFound != null) - { - itemFound(jobDef); - } - } - } - - // Next add definition items not in local repository. - foreach (string jobDefinitionName in jobDefinitionNamesHash) - { - try - { - // Read the job definition object from file and add to local repository. - ScheduledJobDefinition jobDefinition = ScheduledJobDefinition.LoadDefFromStore(jobDefinitionName, null); - Repository.AddOrReplace(jobDefinition); - - if (itemFound != null) - { - itemFound(jobDefinition); - } - } - catch (System.IO.IOException e) - { - errors.Add(jobDefinitionName, e); - } - catch (System.Xml.XmlException e) - { - errors.Add(jobDefinitionName, e); - } - catch (System.TypeInitializationException e) - { - errors.Add(jobDefinitionName, e); - } - catch (System.Runtime.Serialization.SerializationException e) - { - errors.Add(jobDefinitionName, e); - } - catch (System.ArgumentNullException e) - { - errors.Add(jobDefinitionName, e); - } - catch (System.UnauthorizedAccessException e) - { - errors.Add(jobDefinitionName, e); - } - } - - return errors; - } - - /// - /// Reads a ScheduledJobDefinition object from file and - /// returns object. - /// - /// Name of definition to load. - /// Path to definition file. - /// ScheduledJobDefinition object. - internal static ScheduledJobDefinition LoadDefFromStore( - string definitionName, - string definitionPath) - { - ScheduledJobDefinition definition = null; - FileStream fs = null; - try - { - fs = GetFileStream( - definitionName, - definitionPath, - FileMode.Open, - FileAccess.Read, - FileShare.Read); - - XmlObjectSerializer serializer = new System.Runtime.Serialization.NetDataContractSerializer(); - definition = serializer.ReadObject(fs) as ScheduledJobDefinition; - } - finally - { - if (fs != null) - { - fs.Close(); - } - } - - return definition; - } - - /// - /// Creates a new ScheduledJobDefinition object from a file. - /// - /// Name of definition to load. - /// Path to definition file. - /// ScheduledJobDefinition object. - public static ScheduledJobDefinition LoadFromStore( - string definitionName, - string definitionPath) - { - if (string.IsNullOrEmpty(definitionName)) - { - throw new PSArgumentNullException("definitionName"); - } - - ScheduledJobDefinition definition = null; - bool corruptedFile = false; - Exception ex = null; - - try - { - definition = LoadDefFromStore(definitionName, definitionPath); - } - catch (DirectoryNotFoundException e) - { - ex = e; - } - catch (FileNotFoundException e) - { - ex = e; - corruptedFile = true; - } - catch (UnauthorizedAccessException e) - { - ex = e; - } - catch (IOException e) - { - ex = e; - } - catch (System.Xml.XmlException e) - { - ex = e; - corruptedFile = true; - } - catch (System.TypeInitializationException e) - { - ex = e; - corruptedFile = true; - } - catch (System.ArgumentNullException e) - { - ex = e; - corruptedFile = true; - } - catch (System.Runtime.Serialization.SerializationException e) - { - ex = e; - corruptedFile = true; - } - - if (ex != null) - { - // - // Remove definition if corrupted. - // But only if the corrupted file is in the default scheduled jobs - // path for the current user. - // - if (corruptedFile && - (definitionPath == null || - ScheduledJobStore.IsDefaultUserPath(definitionPath))) - { - // Remove corrupted scheduled job definition. - RemoveDefinition(definitionName); - - // Throw exception for corrupted/removed job definition. - throw new ScheduledJobException( - StringUtil.Format(ScheduledJobErrorStrings.CantLoadDefinitionFromStore, definitionName), - ex); - } - - // Throw exception for not found job definition. - throw new ScheduledJobException( - StringUtil.Format(ScheduledJobErrorStrings.CannotFindJobDefinition, definitionName), - ex); - } - - // Make sure the deserialized ScheduledJobDefinition object contains the same - // Task Scheduler information that is stored in Task Scheduler. - definition.SyncWithWTS(); - - return definition; - } - - /// - /// Internal helper method to remove a scheduled job definition - /// by name from job store and Task Scheduler. - /// - /// Scheduled job definition name. - internal static void RemoveDefinition( - string definitionName) - { - // Remove from store. - try - { - ScheduledJobStore.RemoveJobDefinition(definitionName); - } - catch (DirectoryNotFoundException) - { } - catch (FileNotFoundException) - { } - catch (UnauthorizedAccessException) - { } - catch (IOException) - { } - - // Check and remove from Task Scheduler. - using (ScheduledJobWTS taskScheduler = new ScheduledJobWTS()) - { - try - { - taskScheduler.RemoveTaskByName(definitionName, true, true); - } - catch (UnauthorizedAccessException) - { } - catch (IOException) - { } - } - } - - private static int GetCurrentId() - { - lock (LockObject) - { - return ++CurrentId; - } - } - - /// - /// Starts a scheduled job based on definition name and returns the - /// running job object. Returned job is also added to the local - /// job repository. Job results are not written to store. - /// - /// ScheduledJobDefinition name. - public static Job2 StartJob( - string DefinitionName) - { - // Load scheduled job definition. - ScheduledJobDefinition jobDefinition = ScheduledJobDefinition.LoadFromStore(DefinitionName, null); - - // Start job. - return jobDefinition.StartJob(); - } - - private static FileStream GetFileStream( - string definitionName, - string definitionPath, - FileMode fileMode, - FileAccess fileAccess, - FileShare fileShare) - { - FileStream fs; - - if (definitionPath == null) - { - // Look for definition in default current user location. - fs = ScheduledJobStore.GetFileForJobDefinition( - definitionName, - fileMode, - fileAccess, - fileShare); - } - else - { - // Look for definition in known path. - fs = ScheduledJobStore.GetFileForJobDefinition( - definitionName, - definitionPath, - fileMode, - fileAccess, - fileShare); - } - - return fs; - } - - #endregion - - #region Running Job - - /// - /// Create a Job2 job, runs it and waits for it to complete. - /// Job status and results are written to the job store. - /// - /// Job2 job object that was run. - public Job2 Run() - { - Job2 job = null; - - using (PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource()) - { - Exception ex = null; - try - { - JobManager jobManager = Runspace.DefaultRunspace.JobManager; - - job = jobManager.NewJob(InvocationInfo); - - // If this is a scheduled job type then include this object so - // so that ScheduledJobSourceAdapter knows where the results are - // to be stored. - ScheduledJob schedJob = job as ScheduledJob; - if (schedJob != null) - { - schedJob.Definition = this; - schedJob.AllowSetShouldExit = true; - } - - // Update job store data when job begins. - job.StateChanged += (object sender, JobStateEventArgs e) => - { - if (e.JobStateInfo.State == JobState.Running) - { - // Write job to store with this running state. - jobManager.PersistJob(job, Definition); - } - }; - - job.StartJob(); - - // Log scheduled job start. - _tracer.WriteScheduledJobStartEvent( - job.Name, - job.PSBeginTime.ToString()); - - // Wait for job to finish. - job.Finished.WaitOne(); - - // Ensure that the job run results are persisted to store. - jobManager.PersistJob(job, Definition); - - // Perform a Receive-Job on the job object. Output data will be dropped - // but we do this to execute any client method calls, in particular we - // want SetShouldExit to set the correct exit code on the process for - // use inside Task Scheduler. - using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create()) - { - // Run on the default runspace. - ps.AddCommand("Receive-Job").AddParameter("Job", job).AddParameter("Keep", true); - ps.Invoke(); - } - - // Log scheduled job finish. - _tracer.WriteScheduledJobCompleteEvent( - job.Name, - job.PSEndTime.ToString(), - job.JobStateInfo.State.ToString()); - } - catch (RuntimeException e) - { - ex = e; - } - catch (InvalidOperationException e) - { - ex = e; - } - catch (System.Security.SecurityException e) - { - ex = e; - } - catch (IOException e) - { - ex = e; - } - catch (UnauthorizedAccessException e) - { - ex = e; - } - catch (ArgumentException e) - { - ex = e; - } - catch (ScriptCallDepthException e) - { - ex = e; - } - catch (System.Runtime.Serialization.SerializationException e) - { - ex = e; - } - catch (System.Runtime.Serialization.InvalidDataContractException e) - { - ex = e; - } - catch (System.Xml.XmlException e) - { - ex = e; - } - catch (Microsoft.PowerShell.ScheduledJob.ScheduledJobException e) - { - ex = e; - } - - if (ex != null) - { - // Log error. - _tracer.WriteScheduledJobErrorEvent( - this.Name, - ex.Message, - ex.StackTrace.ToString(), - (ex.InnerException != null) ? ex.InnerException.Message : string.Empty); - - throw ex; - } - } - - return job; - } - - #endregion - } - - #region ScheduledJobDefinition Repository - - /// - /// Collection of ScheduledJobDefinition objects. - /// - internal class ScheduledJobDefinitionRepository - { - #region Private Members - - private object _syncObject = new object(); - private Dictionary _definitions = new Dictionary(); - - #endregion - - #region Public Properties - - /// - /// Returns all definition objects in the repository as a List. - /// - public List Definitions - { - get - { - lock (_syncObject) - { - // Sort returned list by Ids. - List rtnList = - new List(_definitions.Values); - - rtnList.Sort((firstJob, secondJob) => - { - if (firstJob.Id > secondJob.Id) - { - return 1; - } - else if (firstJob.Id < secondJob.Id) - { - return -1; - } - else - { - return 0; - } - }); - - return rtnList; - } - } - } - - /// - /// Returns count of object in repository. - /// - public int Count - { - get - { - lock (_syncObject) - { - return _definitions.Count; - } - } - } - - #endregion - - #region Public Methods - - /// - /// Add ScheduledJobDefinition to repository. - /// - /// - public void Add(ScheduledJobDefinition jobDef) - { - if (jobDef == null) - { - throw new PSArgumentNullException("jobDef"); - } - - lock (_syncObject) - { - if (_definitions.ContainsKey(jobDef.Name)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.DefinitionAlreadyExistsInLocal, jobDef.Name, jobDef.GlobalId); - throw new ScheduledJobException(msg); - } - - _definitions.Add(jobDef.Name, jobDef); - } - } - - /// - /// Add or replace passed in ScheduledJobDefinition object to repository. - /// - /// - public void AddOrReplace(ScheduledJobDefinition jobDef) - { - if (jobDef == null) - { - throw new PSArgumentNullException("jobDef"); - } - - lock (_syncObject) - { - if (_definitions.ContainsKey(jobDef.Name)) - { - _definitions.Remove(jobDef.Name); - } - - _definitions.Add(jobDef.Name, jobDef); - } - } - - /// - /// Remove ScheduledJobDefinition from repository. - /// - /// - public void Remove(ScheduledJobDefinition jobDef) - { - if (jobDef == null) - { - throw new PSArgumentNullException("jobDef"); - } - - lock (_syncObject) - { - if (_definitions.ContainsKey(jobDef.Name)) - { - _definitions.Remove(jobDef.Name); - } - } - } - - /// - /// Checks to see if a ScheduledJobDefinition object exists with - /// the provided definition name. - /// - /// Definition name. - /// True if definition exists. - public bool Contains(string jobDefName) - { - lock (_syncObject) - { - return _definitions.ContainsKey(jobDefName); - } - } - - /// - /// Clears all ScheduledJobDefinition items from the repository. - /// - public void Clear() - { - lock (_syncObject) - { - _definitions.Clear(); - } - } - - #endregion - } - - #endregion - - #region Exceptions - - /// - /// Exception thrown for errors in Scheduled Jobs. - /// - [Serializable] - public class ScheduledJobException : SystemException - { - /// - /// Creates a new instance of ScheduledJobException class. - /// - public ScheduledJobException() - : base - ( - StringUtil.Format(ScheduledJobErrorStrings.GeneralWTSError) - ) - { - } - - /// - /// Creates a new instance of ScheduledJobException class. - /// - /// - /// The error message that explains the reason for the exception. - /// - public ScheduledJobException(string message) - : base(message) - { - } - - /// - /// Creates a new instance of ScheduledJobException class. - /// - /// - /// The error message that explains the reason for the exception. - /// - /// - /// The exception that is the cause of the current exception. - /// - public ScheduledJobException(string message, Exception innerException) - : base(message, innerException) - { - } - - /// - /// Fully qualified error id for exception. - /// - internal string FQEID - { - get { return _fqeid; } - - set { _fqeid = value ?? string.Empty; } - } - - private string _fqeid = string.Empty; - } - - #endregion - - #region Utilities - - /// - /// Simple string formatting helper. - /// - internal class StringUtil - { - internal static string Format(string formatSpec, object o) - { - return string.Format(System.Threading.Thread.CurrentThread.CurrentCulture, formatSpec, o); - } - - internal static string Format(string formatSpec, params object[] o) - { - return string.Format(System.Threading.Thread.CurrentThread.CurrentCulture, formatSpec, o); - } - } - - #endregion - - #region ScheduledJobInvocationInfo Class - - /// - /// This class defines the JobInvocationInfo class for PowerShell jobs - /// for job scheduling. The following parameters are supported: - /// - /// "ScriptBlock" -> ScriptBlock - /// "FilePath" -> String - /// "InitializationScript" -> ScriptBlock - /// "ArgumentList" -> object[] - /// "RunAs32" -> Boolean - /// "Authentication" -> AuthenticationMechanism. - /// - [Serializable] - public sealed class ScheduledJobInvocationInfo : JobInvocationInfo - { - #region Constructors - - /// - /// Constructor. - /// - /// JobDefinition. - /// Dictionary of parameters. - public ScheduledJobInvocationInfo(JobDefinition definition, Dictionary parameters) - : base(definition, parameters) - { - if (definition == null) - { - throw new PSArgumentNullException("definition"); - } - - Name = definition.Name; - } - - #endregion - - #region Public Strings - - /// - /// ScriptBlock parameter. - /// - public const string ScriptBlockParameter = "ScriptBlock"; - - /// - /// FilePath parameter. - /// - public const string FilePathParameter = "FilePath"; - - /// - /// RunAs32 parameter. - /// - public const string RunAs32Parameter = "RunAs32"; - - /// - /// Authentication parameter. - /// - public const string AuthenticationParameter = "Authentication"; - - /// - /// InitializationScript parameter. - /// - public const string InitializationScriptParameter = "InitializationScript"; - - /// - /// ArgumentList parameter. - /// - public const string ArgumentListParameter = "ArgumentList"; - - #endregion - - #region ISerializable Implementation - - /// - /// Serialization constructor. - /// - /// SerializationInfo. - /// StreamingContext. - internal ScheduledJobInvocationInfo( - SerializationInfo info, - StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException("info"); - } - - DeserializeInvocationInfo(info); - } - - /// - /// Serialization implementation. - /// - /// SerializationInfo. - /// StreamingContext. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException("info"); - } - - SerializeInvocationInfo(info); - } - - #endregion - - #region Private Methods - - private void SerializeInvocationInfo(SerializationInfo info) - { - info.AddValue("InvocationInfo_Command", this.Command); - info.AddValue("InvocationInfo_Name", this.Name); - info.AddValue("InvocationInfo_AdapterType", this.Definition.JobSourceAdapterType); - info.AddValue("InvocationInfo_ModuleName", this.Definition.ModuleName); - info.AddValue("InvocationInfo_AdapterTypeName", this.Definition.JobSourceAdapterTypeName); - - // Get the job parameters. - Dictionary parameters = new Dictionary(); - foreach (var commandParam in this.Parameters[0]) - { - if (!parameters.ContainsKey(commandParam.Name)) - { - parameters.Add(commandParam.Name, commandParam.Value); - } - } - - // - // Serialize only parameters that scheduled job knows about. - // - - // ScriptBlock - if (parameters.ContainsKey(ScriptBlockParameter)) - { - ScriptBlock scriptBlock = (ScriptBlock)parameters[ScriptBlockParameter]; - info.AddValue("InvocationParam_ScriptBlock", scriptBlock.ToString()); - } - else - { - info.AddValue("InvocationParam_ScriptBlock", null); - } - - // FilePath - if (parameters.ContainsKey(FilePathParameter)) - { - string filePath = (string)parameters[FilePathParameter]; - info.AddValue("InvocationParam_FilePath", filePath); - } - else - { - info.AddValue("InvocationParam_FilePath", string.Empty); - } - - // InitializationScript - if (parameters.ContainsKey(InitializationScriptParameter)) - { - ScriptBlock scriptBlock = (ScriptBlock)parameters[InitializationScriptParameter]; - info.AddValue("InvocationParam_InitScript", scriptBlock.ToString()); - } - else - { - info.AddValue("InvocationParam_InitScript", string.Empty); - } - - // RunAs32 - if (parameters.ContainsKey(RunAs32Parameter)) - { - bool runAs32 = (bool)parameters[RunAs32Parameter]; - info.AddValue("InvocationParam_RunAs32", runAs32); - } - else - { - info.AddValue("InvocationParam_RunAs32", false); - } - - // Authentication - if (parameters.ContainsKey(AuthenticationParameter)) - { - AuthenticationMechanism authentication = (AuthenticationMechanism)parameters[AuthenticationParameter]; - info.AddValue("InvocationParam_Authentication", authentication); - } - else - { - info.AddValue("InvocationParam_Authentication", AuthenticationMechanism.Default); - } - - // ArgumentList - if (parameters.ContainsKey(ArgumentListParameter)) - { - object[] argList = (object[])parameters[ArgumentListParameter]; - info.AddValue("InvocationParam_ArgList", argList); - } - else - { - info.AddValue("InvocationParam_ArgList", null); - } - } - - private void DeserializeInvocationInfo(SerializationInfo info) - { - string command = info.GetString("InvocationInfo_Command"); - string name = info.GetString("InvocationInfo_Name"); - string moduleName = info.GetString("InvocationInfo_ModuleName"); - string adapterTypeName = info.GetString("InvocationInfo_AdapterTypeName"); - - // - // Parameters - Dictionary parameters = new Dictionary(); - - // ScriptBlock - string script = info.GetString("InvocationParam_ScriptBlock"); - if (script != null) - { - parameters.Add(ScriptBlockParameter, ScriptBlock.Create(script)); - } - - // FilePath - string filePath = info.GetString("InvocationParam_FilePath"); - if (!string.IsNullOrEmpty(filePath)) - { - parameters.Add(FilePathParameter, filePath); - } - - // InitializationScript - script = info.GetString("InvocationParam_InitScript"); - if (!string.IsNullOrEmpty(script)) - { - parameters.Add(InitializationScriptParameter, ScriptBlock.Create(script)); - } - - // RunAs32 - bool runAs32 = info.GetBoolean("InvocationParam_RunAs32"); - parameters.Add(RunAs32Parameter, runAs32); - - // Authentication - AuthenticationMechanism authentication = (AuthenticationMechanism)info.GetValue("InvocationParam_Authentication", - typeof(AuthenticationMechanism)); - parameters.Add(AuthenticationParameter, authentication); - - // ArgumentList - object[] argList = (object[])info.GetValue("InvocationParam_ArgList", typeof(object[])); - if (argList != null) - { - parameters.Add(ArgumentListParameter, argList); - } - - JobDefinition jobDefinition = new JobDefinition(null, command, name); - jobDefinition.ModuleName = moduleName; - jobDefinition.JobSourceAdapterTypeName = adapterTypeName; - - // Convert to JobInvocationParameter collection - CommandParameterCollection paramCollection = new CommandParameterCollection(); - foreach (KeyValuePair param in parameters) - { - CommandParameter paramItem = new CommandParameter(param.Key, param.Value); - paramCollection.Add(paramItem); - } - - this.Definition = jobDefinition; - this.Name = name; - this.Command = command; - this.Parameters.Add(paramCollection); - } - - #endregion - } - - #endregion -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobOptions.cs b/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobOptions.cs deleted file mode 100644 index 8f171cd02ba..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobOptions.cs +++ /dev/null @@ -1,405 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Management.Automation; -using System.Runtime.Serialization; -using System.Security.Permissions; -using System.Text; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This class contains Windows Task Scheduler options. - /// - [Serializable] - public sealed class ScheduledJobOptions : ISerializable - { - #region Private Members - - // Power settings - private bool _startIfOnBatteries; - private bool _stopIfGoingOnBatteries; - private bool _wakeToRun; - - // Idle settings - private bool _startIfNotIdle; - private bool _stopIfGoingOffIdle; - private bool _restartOnIdleResume; - private TimeSpan _idleDuration; - private TimeSpan _idleTimeout; - - // Security settings - private bool _showInTaskScheduler; - private bool _runElevated; - - // Misc - private bool _runWithoutNetwork; - private bool _donotAllowDemandStart; - private TaskMultipleInstancePolicy _multipleInstancePolicy; - - // ScheduledJobDefinition object associated with this options object. - private ScheduledJobDefinition _jobDefAssociation; - - #endregion - - #region Public Properties - - /// - /// Start task if on batteries. - /// - public bool StartIfOnBatteries - { - get { return _startIfOnBatteries; } - - set { _startIfOnBatteries = value; } - } - - /// - /// Stop task if computer is going on batteries. - /// - public bool StopIfGoingOnBatteries - { - get { return _stopIfGoingOnBatteries; } - - set { _stopIfGoingOnBatteries = value; } - } - - /// - /// Wake computer to run task. - /// - public bool WakeToRun - { - get { return _wakeToRun; } - - set { _wakeToRun = value; } - } - - /// - /// Start task only if computer is not idle. - /// - public bool StartIfNotIdle - { - get { return _startIfNotIdle; } - - set { _startIfNotIdle = value; } - } - - /// - /// Stop task if computer is no longer idle. - /// - public bool StopIfGoingOffIdle - { - get { return _stopIfGoingOffIdle; } - - set { _stopIfGoingOffIdle = value; } - } - /// - /// Restart task on idle resuming. - /// - public bool RestartOnIdleResume - { - get { return _restartOnIdleResume; } - - set { _restartOnIdleResume = value; } - } - - /// - /// How long computer must be idle before task starts. - /// - public TimeSpan IdleDuration - { - get { return _idleDuration; } - - set { _idleDuration = value; } - } - - /// - /// How long task manager will wait for required idle duration. - /// - public TimeSpan IdleTimeout - { - get { return _idleTimeout; } - - set { _idleTimeout = value; } - } - - /// - /// When true task is not shown in Task Scheduler UI. - /// - public bool ShowInTaskScheduler - { - get { return _showInTaskScheduler; } - - set { _showInTaskScheduler = value; } - } - - /// - /// Run task with elevated privileges. - /// - public bool RunElevated - { - get { return _runElevated; } - - set { _runElevated = value; } - } - - /// - /// Run task even if network is not available. - /// - public bool RunWithoutNetwork - { - get { return _runWithoutNetwork; } - - set { _runWithoutNetwork = value; } - } - - /// - /// Do not allow a task to be started on demand. - /// - public bool DoNotAllowDemandStart - { - get { return _donotAllowDemandStart; } - - set { _donotAllowDemandStart = value; } - } - - /// - /// Multiple task instance policy. - /// - public TaskMultipleInstancePolicy MultipleInstancePolicy - { - get { return _multipleInstancePolicy; } - - set { _multipleInstancePolicy = value; } - } - - /// - /// ScheduledJobDefinition object associated with this options object. - /// - public ScheduledJobDefinition JobDefinition - { - get { return _jobDefAssociation; } - - internal set { _jobDefAssociation = value; } - } - - #endregion - - #region Constructors - - /// - /// Default constructor. - /// - public ScheduledJobOptions() - { - _startIfOnBatteries = false; - _stopIfGoingOnBatteries = true; - _wakeToRun = false; - _startIfNotIdle = true; - _stopIfGoingOffIdle = false; - _restartOnIdleResume = false; - _idleDuration = new TimeSpan(0, 10, 0); - _idleTimeout = new TimeSpan(1, 0, 0); - _showInTaskScheduler = true; - _runElevated = false; - _runWithoutNetwork = true; - _donotAllowDemandStart = false; - _multipleInstancePolicy = TaskMultipleInstancePolicy.IgnoreNew; - } - - /// - /// Constructor. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - internal ScheduledJobOptions( - bool startIfOnBatteries, - bool stopIfGoingOnBatters, - bool wakeToRun, - bool startIfNotIdle, - bool stopIfGoingOffIdle, - bool restartOnIdleResume, - TimeSpan idleDuration, - TimeSpan idleTimeout, - bool showInTaskScheduler, - bool runElevated, - bool runWithoutNetwork, - bool donotAllowDemandStart, - TaskMultipleInstancePolicy multipleInstancePolicy) - { - _startIfOnBatteries = startIfOnBatteries; - _stopIfGoingOnBatteries = stopIfGoingOnBatters; - _wakeToRun = wakeToRun; - _startIfNotIdle = startIfNotIdle; - _stopIfGoingOffIdle = stopIfGoingOffIdle; - _restartOnIdleResume = restartOnIdleResume; - _idleDuration = idleDuration; - _idleTimeout = idleTimeout; - _showInTaskScheduler = showInTaskScheduler; - _runElevated = runElevated; - _runWithoutNetwork = runWithoutNetwork; - _donotAllowDemandStart = donotAllowDemandStart; - _multipleInstancePolicy = multipleInstancePolicy; - } - - /// - /// Copy Constructor. - /// - /// Copy from. - internal ScheduledJobOptions( - ScheduledJobOptions copyOptions) - { - if (copyOptions == null) - { - throw new PSArgumentNullException("copyOptions"); - } - - _startIfOnBatteries = copyOptions.StartIfOnBatteries; - _stopIfGoingOnBatteries = copyOptions.StopIfGoingOnBatteries; - _wakeToRun = copyOptions.WakeToRun; - _startIfNotIdle = copyOptions.StartIfNotIdle; - _stopIfGoingOffIdle = copyOptions.StopIfGoingOffIdle; - _restartOnIdleResume = copyOptions.RestartOnIdleResume; - _idleDuration = copyOptions.IdleDuration; - _idleTimeout = copyOptions.IdleTimeout; - _showInTaskScheduler = copyOptions.ShowInTaskScheduler; - _runElevated = copyOptions.RunElevated; - _runWithoutNetwork = copyOptions.RunWithoutNetwork; - _donotAllowDemandStart = copyOptions.DoNotAllowDemandStart; - _multipleInstancePolicy = copyOptions.MultipleInstancePolicy; - - _jobDefAssociation = copyOptions.JobDefinition; - } - - #endregion - - #region ISerializable Implementation - - /// - /// Serialization constructor. - /// - /// SerializationInfo. - /// StreamingContext. - private ScheduledJobOptions( - SerializationInfo info, - StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException("info"); - } - - _startIfOnBatteries = info.GetBoolean("StartIfOnBatteries_Value"); - _stopIfGoingOnBatteries = info.GetBoolean("StopIfGoingOnBatteries_Value"); - _wakeToRun = info.GetBoolean("WakeToRun_Value"); - _startIfNotIdle = info.GetBoolean("StartIfNotIdle_Value"); - _stopIfGoingOffIdle = info.GetBoolean("StopIfGoingOffIdle_Value"); - _restartOnIdleResume = info.GetBoolean("RestartOnIdleResume_Value"); - _idleDuration = (TimeSpan)info.GetValue("IdleDuration_Value", typeof(TimeSpan)); - _idleTimeout = (TimeSpan)info.GetValue("IdleTimeout_Value", typeof(TimeSpan)); - _showInTaskScheduler = info.GetBoolean("ShowInTaskScheduler_Value"); - _runElevated = info.GetBoolean("RunElevated_Value"); - _runWithoutNetwork = info.GetBoolean("RunWithoutNetwork_Value"); - _donotAllowDemandStart = info.GetBoolean("DoNotAllowDemandStart_Value"); - _multipleInstancePolicy = (TaskMultipleInstancePolicy)info.GetValue("TaskMultipleInstancePolicy_Value", typeof(TaskMultipleInstancePolicy)); - - // Runtime reference and not saved to store. - _jobDefAssociation = null; - } - - /// - /// GetObjectData for ISerializable implementation. - /// - /// SerializationInfo. - /// StreamingContext. - public void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException("info"); - } - - info.AddValue("StartIfOnBatteries_Value", _startIfOnBatteries); - info.AddValue("StopIfGoingOnBatteries_Value", _stopIfGoingOnBatteries); - info.AddValue("WakeToRun_Value", _wakeToRun); - info.AddValue("StartIfNotIdle_Value", _startIfNotIdle); - info.AddValue("StopIfGoingOffIdle_Value", _stopIfGoingOffIdle); - info.AddValue("RestartOnIdleResume_Value", _restartOnIdleResume); - info.AddValue("IdleDuration_Value", _idleDuration); - info.AddValue("IdleTimeout_Value", _idleTimeout); - info.AddValue("ShowInTaskScheduler_Value", _showInTaskScheduler); - info.AddValue("RunElevated_Value", _runElevated); - info.AddValue("RunWithoutNetwork_Value", _runWithoutNetwork); - info.AddValue("DoNotAllowDemandStart_Value", _donotAllowDemandStart); - info.AddValue("TaskMultipleInstancePolicy_Value", _multipleInstancePolicy); - } - - #endregion - - #region Public Methods - - /// - /// Update the associated ScheduledJobDefinition object with the - /// current properties of this object. - /// - public void UpdateJobDefinition() - { - if (_jobDefAssociation == null) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.NoAssociatedJobDefinitionForOption); - - throw new RuntimeException(msg); - } - - _jobDefAssociation.UpdateOptions(this, true); - } - - #endregion - } - - #region Public Enums - - /// - /// Enumerates Task Scheduler options for multiple instance polices of - /// scheduled tasks (jobs). - /// - public enum TaskMultipleInstancePolicy - { - /// - /// None. - /// - None = 0, - /// - /// Ignore a new instance of the task (job) - /// - IgnoreNew = 1, - /// - /// Allow parallel running of a task (job) - /// - Parallel = 2, - /// - /// Queue up multiple instances of a task (job) - /// - Queue = 3, - /// - /// Stop currently running task (job) and start a new one. - /// - StopExisting = 4 - } - - #endregion -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobSourceAdapter.cs b/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobSourceAdapter.cs deleted file mode 100644 index 53434c729e7..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobSourceAdapter.cs +++ /dev/null @@ -1,1088 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Runtime.Serialization; -using System.Text; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This class provides functionality for retrieving scheduled job run results - /// from the scheduled job store. An instance of this object will be registered - /// with the PowerShell JobManager so that GetJobs commands will retrieve schedule - /// job runs from the file based scheduled job store. This allows scheduled job - /// runs to be managed from PowerShell in the same way workflow jobs are managed. - /// - public sealed class ScheduledJobSourceAdapter : JobSourceAdapter - { - #region Private Members - - private static FileSystemWatcher StoreWatcher; - private static object SyncObject = new object(); - private static ScheduledJobRepository JobRepository = new ScheduledJobRepository(); - internal const string AdapterTypeName = "PSScheduledJob"; - - #endregion - - #region Public Strings - - /// - /// BeforeFilter. - /// - public const string BeforeFilter = "Before"; - - /// - /// AfterFilter. - /// - public const string AfterFilter = "After"; - - /// - /// NewestFilter. - /// - public const string NewestFilter = "Newest"; - - #endregion - - #region Constructor - - /// - /// Constructor. - /// - public ScheduledJobSourceAdapter() - { - Name = AdapterTypeName; - } - - #endregion - - #region JobSourceAdapter Implementation - - /// - /// Create a new Job2 results instance. - /// - /// Job specification. - /// Job2. - public override Job2 NewJob(JobInvocationInfo specification) - { - if (specification == null) - { - throw new PSArgumentNullException("specification"); - } - - ScheduledJobDefinition scheduledJobDef = new ScheduledJobDefinition( - specification, null, null, null); - - return new ScheduledJob( - specification.Command, - specification.Name, - scheduledJobDef); - } - - /// - /// Creates a new Job2 object based on a definition name - /// that can be run manually. If the path parameter is - /// null then a default location will be used to find the - /// job definition by name. - /// - /// ScheduledJob definition name. - /// ScheduledJob definition file path. - /// Job2 object. - public override Job2 NewJob(string definitionName, string definitionPath) - { - if (string.IsNullOrEmpty(definitionName)) - { - throw new PSArgumentException("definitionName"); - } - - Job2 rtnJob = null; - try - { - ScheduledJobDefinition scheduledJobDef = - ScheduledJobDefinition.LoadFromStore(definitionName, definitionPath); - - rtnJob = new ScheduledJob( - scheduledJobDef.Command, - scheduledJobDef.Name, - scheduledJobDef); - } - catch (FileNotFoundException) - { - // Return null if no job definition exists. - } - - return rtnJob; - } - - /// - /// Get the list of jobs that are currently available in this - /// store. - /// - /// Collection of job objects. - public override IList GetJobs() - { - RefreshRepository(); - - List rtnJobs = new List(); - foreach (var job in JobRepository.Jobs) - { - rtnJobs.Add(job); - } - - return rtnJobs; - } - - /// - /// Get list of jobs that matches the specified names. - /// - /// names to match, can support - /// wildcard if the store supports - /// - /// Collection of jobs that match the specified - /// criteria. - public override IList GetJobsByName(string name, bool recurse) - { - if (string.IsNullOrEmpty(name)) - { - throw new PSArgumentException("name"); - } - - RefreshRepository(); - - WildcardPattern namePattern = new WildcardPattern(name, WildcardOptions.IgnoreCase); - List rtnJobs = new List(); - foreach (var job in JobRepository.Jobs) - { - if (namePattern.IsMatch(job.Name)) - { - rtnJobs.Add(job); - } - } - - return rtnJobs; - } - - /// - /// Get list of jobs that run the specified command. - /// - /// Command to match. - /// - /// Collection of jobs that match the specified - /// criteria. - public override IList GetJobsByCommand(string command, bool recurse) - { - if (string.IsNullOrEmpty(command)) - { - throw new PSArgumentException("command"); - } - - RefreshRepository(); - - WildcardPattern commandPattern = new WildcardPattern(command, WildcardOptions.IgnoreCase); - List rtnJobs = new List(); - foreach (var job in JobRepository.Jobs) - { - if (commandPattern.IsMatch(job.Command)) - { - rtnJobs.Add(job); - } - } - - return rtnJobs; - } - - /// - /// Get job that has the specified id. - /// - /// Guid to match. - /// - /// Job with the specified guid. - public override Job2 GetJobByInstanceId(Guid instanceId, bool recurse) - { - RefreshRepository(); - - foreach (var job in JobRepository.Jobs) - { - if (Guid.Equals(job.InstanceId, instanceId)) - { - return job; - } - } - - return null; - } - - /// - /// Get job that has specific session id. - /// - /// Id to match. - /// - /// Job with the specified id. - public override Job2 GetJobBySessionId(int id, bool recurse) - { - RefreshRepository(); - - foreach (var job in JobRepository.Jobs) - { - if (id == job.Id) - { - return job; - } - } - - return null; - } - - /// - /// Get list of jobs that are in the specified state. - /// - /// State to match. - /// - /// Collection of jobs with the specified - /// state. - public override IList GetJobsByState(JobState state, bool recurse) - { - RefreshRepository(); - - List rtnJobs = new List(); - foreach (var job in JobRepository.Jobs) - { - if (state == job.JobStateInfo.State) - { - rtnJobs.Add(job); - } - } - - return rtnJobs; - } - - /// - /// Get list of jobs based on the adapter specific - /// filter parameters. - /// - /// dictionary containing name value - /// pairs for adapter specific filters - /// - /// Collection of jobs that match the - /// specified criteria. - public override IList GetJobsByFilter(Dictionary filter, bool recurse) - { - if (filter == null) - { - throw new PSArgumentNullException("filter"); - } - - List rtnJobs = new List(); - foreach (var filterItem in filter) - { - switch (filterItem.Key) - { - case BeforeFilter: - GetJobsBefore((DateTime)filterItem.Value, ref rtnJobs); - break; - - case AfterFilter: - GetJobsAfter((DateTime)filterItem.Value, ref rtnJobs); - break; - - case NewestFilter: - GetNewestJobs((int)filterItem.Value, ref rtnJobs); - break; - } - } - - return rtnJobs; - } - - /// - /// Remove a job from the store. - /// - /// Job object to remove. - public override void RemoveJob(Job2 job) - { - if (job == null) - { - throw new PSArgumentNullException("job"); - } - - RefreshRepository(); - - try - { - JobRepository.Remove(job); - ScheduledJobStore.RemoveJobRun( - job.Name, - job.PSBeginTime ?? DateTime.MinValue); - } - catch (DirectoryNotFoundException) - { - } - catch (FileNotFoundException) - { - } - } - - /// - /// Saves job to scheduled job run store. - /// - /// ScheduledJob. - public override void PersistJob(Job2 job) - { - if (job == null) - { - throw new PSArgumentNullException("job"); - } - - SaveJobToStore(job as ScheduledJob); - } - - #endregion - - #region Save Job - - /// - /// Serializes a ScheduledJob and saves it to store. - /// - /// ScheduledJob. - internal static void SaveJobToStore(ScheduledJob job) - { - string outputPath = job.Definition.OutputPath; - if (string.IsNullOrEmpty(outputPath)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.CantSaveJobNoFilePathSpecified, - job.Name); - throw new ScheduledJobException(msg); - } - - FileStream fsStatus = null; - FileStream fsResults = null; - try - { - // Check the job store results and if maximum number of results exist - // remove the oldest results folder to make room for these new results. - CheckJobStoreResults(outputPath, job.Definition.ExecutionHistoryLength); - - fsStatus = ScheduledJobStore.CreateFileForJobRunItem( - outputPath, - job.PSBeginTime ?? DateTime.MinValue, - ScheduledJobStore.JobRunItem.Status); - - // Save status only in status file stream. - SaveStatusToFile(job, fsStatus); - - fsResults = ScheduledJobStore.CreateFileForJobRunItem( - outputPath, - job.PSBeginTime ?? DateTime.MinValue, - ScheduledJobStore.JobRunItem.Results); - - // Save entire job in results file stream. - SaveResultsToFile(job, fsResults); - } - finally - { - if (fsStatus != null) - { - fsStatus.Close(); - } - - if (fsResults != null) - { - fsResults.Close(); - } - } - } - - /// - /// Writes the job status information to the provided - /// file stream. - /// - /// ScheduledJob job to save. - /// FileStream. - private static void SaveStatusToFile(ScheduledJob job, FileStream fs) - { - StatusInfo statusInfo = new StatusInfo( - job.InstanceId, - job.Name, - job.Location, - job.Command, - job.StatusMessage, - job.JobStateInfo.State, - job.HasMoreData, - job.PSBeginTime, - job.PSEndTime, - job.Definition); - - XmlObjectSerializer serializer = new System.Runtime.Serialization.NetDataContractSerializer(); - serializer.WriteObject(fs, statusInfo); - fs.Flush(); - } - - /// - /// Writes the job (which implements ISerializable) to the provided - /// file stream. - /// - /// ScheduledJob job to save. - /// FileStream. - private static void SaveResultsToFile(ScheduledJob job, FileStream fs) - { - XmlObjectSerializer serializer = new System.Runtime.Serialization.NetDataContractSerializer(); - serializer.WriteObject(fs, job); - fs.Flush(); - } - - /// - /// Check the job store results and if maximum number of results exist - /// remove the oldest results folder to make room for these new results. - /// - /// Output path. - /// Maximum size of stored job results. - private static void CheckJobStoreResults(string outputPath, int executionHistoryLength) - { - // Get current results for this job definition. - Collection jobRuns = ScheduledJobStore.GetJobRunsForDefinitionPath(outputPath); - if (jobRuns.Count <= executionHistoryLength) - { - // There is room for another job run in the store. - return; - } - - // Remove the oldest job run from the store. - DateTime jobRunToRemove = DateTime.MaxValue; - foreach (DateTime jobRun in jobRuns) - { - jobRunToRemove = (jobRun < jobRunToRemove) ? jobRun : jobRunToRemove; - } - - try - { - ScheduledJobStore.RemoveJobRunFromOutputPath(outputPath, jobRunToRemove); - } - catch (UnauthorizedAccessException) - { } - } - - #endregion - - #region Retrieve Job - - /// - /// Finds and load the Job associated with this ScheduledJobDefinition object - /// having the job run date time provided. - /// - /// DateTime of job run to load. - /// ScheduledJobDefinition name. - /// Job2 job loaded from store. - internal static Job2 LoadJobFromStore(string definitionName, DateTime jobRun) - { - FileStream fsResults = null; - Exception ex = null; - bool corruptedFile = false; - Job2 job = null; - - try - { - // Results - fsResults = ScheduledJobStore.GetFileForJobRunItem( - definitionName, - jobRun, - ScheduledJobStore.JobRunItem.Results, - FileMode.Open, - FileAccess.Read, - FileShare.Read); - - job = LoadResultsFromFile(fsResults); - } - catch (ArgumentException e) - { - ex = e; - } - catch (DirectoryNotFoundException e) - { - ex = e; - } - catch (FileNotFoundException e) - { - ex = e; - corruptedFile = true; - } - catch (UnauthorizedAccessException e) - { - ex = e; - } - catch (IOException e) - { - ex = e; - } - catch (System.Runtime.Serialization.SerializationException) - { - corruptedFile = true; - } - catch (System.Runtime.Serialization.InvalidDataContractException) - { - corruptedFile = true; - } - catch (System.Xml.XmlException) - { - corruptedFile = true; - } - catch (System.TypeInitializationException) - { - corruptedFile = true; - } - finally - { - if (fsResults != null) - { - fsResults.Close(); - } - } - - if (corruptedFile) - { - // Remove the corrupted job results file. - ScheduledJobStore.RemoveJobRun(definitionName, jobRun); - } - - if (ex != null) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.CantLoadJobRunFromStore, definitionName, jobRun); - throw new ScheduledJobException(msg, ex); - } - - return job; - } - - /// - /// Loads the Job2 object from provided files stream. - /// - /// FileStream from which to read job object. - /// Created Job2 from file stream. - private static Job2 LoadResultsFromFile(FileStream fs) - { - XmlObjectSerializer serializer = new System.Runtime.Serialization.NetDataContractSerializer(); - return (Job2)serializer.ReadObject(fs); - } - - #endregion - - #region Static Methods - - /// - /// Adds a Job2 object to the repository. - /// - /// Job2. - internal static void AddToRepository(Job2 job) - { - if (job == null) - { - throw new PSArgumentNullException("job"); - } - - JobRepository.AddOrReplace(job); - } - - /// - /// Clears all items in the repository. - /// - internal static void ClearRepository() - { - JobRepository.Clear(); - } - - /// - /// Clears all items for given job definition name in the - /// repository. - /// - /// Scheduled job definition name. - internal static void ClearRepositoryForDefinition(string definitionName) - { - if (string.IsNullOrEmpty(definitionName)) - { - throw new PSArgumentException("definitionName"); - } - - // This returns a new list object of repository jobs. - List jobList = JobRepository.Jobs; - foreach (var job in jobList) - { - if (string.Compare(definitionName, job.Name, - StringComparison.OrdinalIgnoreCase) == 0) - { - JobRepository.Remove(job); - } - } - } - - #endregion - - #region Private Methods - - private void RefreshRepository() - { - ScheduledJobStore.CreateDirectoryIfNotExists(); - CreateFileSystemWatcher(); - - IEnumerable jobDefinitions = ScheduledJobStore.GetJobDefinitions(); - foreach (string definitionName in jobDefinitions) - { - // Create Job2 objects for each job run in store. - Collection jobRuns = GetJobRuns(definitionName); - if (jobRuns == null) - { - continue; - } - - ScheduledJobDefinition definition = null; - foreach (DateTime jobRun in jobRuns) - { - if (jobRun > JobRepository.GetLatestJobRun(definitionName)) - { - Job2 job; - try - { - if (definition == null) - { - definition = ScheduledJobDefinition.LoadFromStore(definitionName, null); - } - - job = LoadJobFromStore(definition.Name, jobRun); - } - catch (ScheduledJobException) - { - continue; - } - catch (DirectoryNotFoundException) - { - continue; - } - catch (FileNotFoundException) - { - continue; - } - catch (UnauthorizedAccessException) - { - continue; - } - catch (IOException) - { - continue; - } - - JobRepository.AddOrReplace(job); - JobRepository.SetLatestJobRun(definitionName, jobRun); - } - } - } - } - - private void CreateFileSystemWatcher() - { - // Lazily create the static file system watcher - // on first use. - if (StoreWatcher == null) - { - lock (SyncObject) - { - if (StoreWatcher == null) - { - StoreWatcher = new FileSystemWatcher(ScheduledJobStore.GetJobDefinitionLocation()); - StoreWatcher.IncludeSubdirectories = true; - StoreWatcher.NotifyFilter = NotifyFilters.LastWrite; - StoreWatcher.Filter = "Results.xml"; - StoreWatcher.EnableRaisingEvents = true; - StoreWatcher.Changed += (object sender, FileSystemEventArgs e) => - { - UpdateRepositoryObjects(e); - }; - } - } - } - } - - private static void UpdateRepositoryObjects(FileSystemEventArgs e) - { - // Extract job run information from change file path. - string updateDefinitionName; - DateTime updateJobRun; - if (!GetJobRunInfo(e.Name, out updateDefinitionName, out updateJobRun)) - { - System.Diagnostics.Debug.Assert(false, "All job run updates should have valid directory names."); - return; - } - - // Find corresponding job in repository. - ScheduledJob updateJob = JobRepository.GetJob(updateDefinitionName, updateJobRun); - if (updateJob == null) - { - return; - } - - // Load updated job information from store. - Job2 job = null; - try - { - job = LoadJobFromStore(updateDefinitionName, updateJobRun); - } - catch (ScheduledJobException) - { } - catch (DirectoryNotFoundException) - { } - catch (FileNotFoundException) - { } - catch (UnauthorizedAccessException) - { } - catch (IOException) - { } - - // Update job in repository based on new job store data. - if (job != null) - { - updateJob.Update(job as ScheduledJob); - } - } - - /// - /// Parses job definition name and job run DateTime from provided path string. - /// Example: - /// path = "ScheduledJob1\\Output\\20111219-200921-369\\Results.xml" - /// 'ScheduledJob1' is the definition name. - /// '20111219-200921-369' is the jobRun DateTime. - /// - /// - /// - /// - /// - private static bool GetJobRunInfo( - string path, - out string definitionName, - out DateTime jobRunReturn) - { - // Parse definition name from path. - string[] pathItems = path.Split(System.IO.Path.DirectorySeparatorChar); - if (pathItems.Length == 4) - { - definitionName = pathItems[0]; - return ScheduledJobStore.ConvertJobRunNameToDateTime(pathItems[2], out jobRunReturn); - } - - definitionName = null; - jobRunReturn = DateTime.MinValue; - return false; - } - - internal static Collection GetJobRuns(string definitionName) - { - Collection jobRuns = null; - try - { - jobRuns = ScheduledJobStore.GetJobRunsForDefinition(definitionName); - } - catch (DirectoryNotFoundException) - { } - catch (FileNotFoundException) - { } - catch (UnauthorizedAccessException) - { } - catch (IOException) - { } - - return jobRuns; - } - - private void GetJobsBefore( - DateTime dateTime, - ref List jobList) - { - foreach (var job in JobRepository.Jobs) - { - if (job.PSEndTime < dateTime && - !jobList.Contains(job)) - { - jobList.Add(job); - } - } - } - - private void GetJobsAfter( - DateTime dateTime, - ref List jobList) - { - foreach (var job in JobRepository.Jobs) - { - if (job.PSEndTime > dateTime && - !jobList.Contains(job)) - { - jobList.Add(job); - } - } - } - - private void GetNewestJobs( - int maxNumber, - ref List jobList) - { - List allJobs = JobRepository.Jobs; - - // Sort descending. - allJobs.Sort((firstJob, secondJob) => - { - if (firstJob.PSEndTime > secondJob.PSEndTime) - { - return -1; - } - else if (firstJob.PSEndTime < secondJob.PSEndTime) - { - return 1; - } - else - { - return 0; - } - }); - - int count = 0; - foreach (var job in allJobs) - { - if (++count > maxNumber) - { - break; - } - - if (!jobList.Contains(job)) - { - jobList.Add(job); - } - } - } - - #endregion - - #region Private Repository Class - - /// - /// Collection of Job2 objects. - /// - internal class ScheduledJobRepository - { - #region Private Members - - private object _syncObject = new object(); - private Dictionary _jobs = new Dictionary(); - private Dictionary _latestJobRuns = new Dictionary(); - - #endregion - - #region Public Properties - - /// - /// Returns all job objects in the repository as a List. - /// - public List Jobs - { - get - { - lock (_syncObject) - { - return new List(_jobs.Values); - } - } - } - - /// - /// Returns count of jobs in repository. - /// - public int Count - { - get - { - lock (_syncObject) - { - return _jobs.Count; - } - } - } - - #endregion - - #region Public Methods - - /// - /// Add Job2 to repository. - /// - /// Job2 to add. - public void Add(Job2 job) - { - if (job == null) - { - throw new PSArgumentNullException("job"); - } - - lock (_syncObject) - { - if (_jobs.ContainsKey(job.InstanceId)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.ScheduledJobAlreadyExistsInLocal, job.Name, job.InstanceId); - throw new ScheduledJobException(msg); - } - - _jobs.Add(job.InstanceId, job); - } - } - - /// - /// Add or replace passed in Job2 object to repository. - /// - /// Job2 to add. - public void AddOrReplace(Job2 job) - { - if (job == null) - { - throw new PSArgumentNullException("job"); - } - - lock (_syncObject) - { - if (_jobs.ContainsKey(job.InstanceId)) - { - _jobs.Remove(job.InstanceId); - } - - _jobs.Add(job.InstanceId, job); - } - } - - /// - /// Remove Job2 from repository. - /// - /// - public void Remove(Job2 job) - { - if (job == null) - { - throw new PSArgumentNullException("job"); - } - - lock (_syncObject) - { - if (_jobs.ContainsKey(job.InstanceId) == false) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.ScheduledJobNotInRepository, job.Name); - throw new ScheduledJobException(msg); - } - - _jobs.Remove(job.InstanceId); - } - } - - /// - /// Clears all Job2 items from the repository. - /// - public void Clear() - { - lock (_syncObject) - { - _jobs.Clear(); - } - } - - /// - /// Gets the latest job run Date/Time for the given definition name. - /// - /// ScheduledJobDefinition name. - /// Job Run DateTime. - public DateTime GetLatestJobRun(string definitionName) - { - if (string.IsNullOrEmpty(definitionName)) - { - throw new PSArgumentException("definitionName"); - } - - lock (_syncObject) - { - if (_latestJobRuns.ContainsKey(definitionName)) - { - return _latestJobRuns[definitionName]; - } - else - { - DateTime startJobRun = DateTime.MinValue; - _latestJobRuns.Add(definitionName, startJobRun); - return startJobRun; - } - } - } - - /// - /// Sets the latest job run Date/Time for the given definition name. - /// - /// - /// - public void SetLatestJobRun(string definitionName, DateTime jobRun) - { - if (string.IsNullOrEmpty(definitionName)) - { - throw new PSArgumentException("definitionName"); - } - - lock (_syncObject) - { - if (_latestJobRuns.ContainsKey(definitionName)) - { - _latestJobRuns.Remove(definitionName); - _latestJobRuns.Add(definitionName, jobRun); - } - else - { - _latestJobRuns.Add(definitionName, jobRun); - } - } - } - - /// - /// Search repository for specific job run. - /// - /// Definition name. - /// Job run DateTime. - /// Scheduled job if found. - public ScheduledJob GetJob(string definitionName, DateTime jobRun) - { - lock (_syncObject) - { - foreach (ScheduledJob job in _jobs.Values) - { - if (job.PSBeginTime == null) - { - continue; - } - - DateTime PSBeginTime = job.PSBeginTime ?? DateTime.MinValue; - if (definitionName.Equals(job.Definition.Name, StringComparison.OrdinalIgnoreCase) && - jobRun.Year == PSBeginTime.Year && - jobRun.Month == PSBeginTime.Month && - jobRun.Day == PSBeginTime.Day && - jobRun.Hour == PSBeginTime.Hour && - jobRun.Minute == PSBeginTime.Minute && - jobRun.Second == PSBeginTime.Second && - jobRun.Millisecond == PSBeginTime.Millisecond) - { - return job; - } - } - } - - return null; - } - - #endregion - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobStore.cs b/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobStore.cs deleted file mode 100644 index bd9d7439696..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobStore.cs +++ /dev/null @@ -1,683 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Globalization; -using System.IO; -using System.Management.Automation; -using System.Security.AccessControl; -using System.Security.Principal; -using System.Text; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This class encapsulates the work of determining the file location where - /// a job definition will be stored and retrieved and where job runs will - /// be stored and retrieved. Scheduled job definitions are stored in a - /// location based on the current user. Job runs are stored in the - /// corresponding scheduled job definition location under an "Output" - /// directory, where each run will have a subdirectory with a name derived - /// from the job run date/time. - /// - /// File Structure for "JobDefinitionFoo": - /// $env:User\AppData\Local\Windows\PowerShell\ScheduledJobs\JobDefinitionFoo\ - /// ScheduledJobDefinition.xml - /// Output\ - /// 110321-130942\ - /// Status.xml - /// Results.xml - /// 110319-173502\ - /// Status.xml - /// Results.xml - /// ... - /// - internal class ScheduledJobStore - { - #region Public Enums - - public enum JobRunItem - { - None = 0, - Status = 1, - Results = 2 - } - - #endregion - - #region Public Strings - - public const string ScheduledJobsPath = @"Microsoft\Windows\PowerShell\ScheduledJobs"; - public const string DefinitionFileName = "ScheduledJobDefinition"; - public const string JobRunOutput = "Output"; - public const string ScheduledJobDefExistsFQEID = "ScheduledJobDefExists"; - - #endregion - - #region Public Methods - - /// - /// Returns FileStream object for existing scheduled job definition. - /// Definition file is looked for in the default user local appdata path. - /// - /// Scheduled job definition name. - /// File mode. - /// File access. - /// File share. - /// FileStream object. - public static FileStream GetFileForJobDefinition( - string definitionName, - FileMode fileMode, - FileAccess fileAccess, - FileShare fileShare) - { - if (string.IsNullOrEmpty(definitionName)) - { - throw new PSArgumentException("definitionName"); - } - - string filePathName = GetFilePathName(definitionName, DefinitionFileName); - return File.Open(filePathName, fileMode, fileAccess, fileShare); - } - - /// - /// Returns FileStream object for existing scheduled job definition. - /// Definition file is looked for in the path provided. - /// - /// Scheduled job definition name. - /// Scheduled job definition file path. - /// File mode. - /// File share. - /// File share. - /// - public static FileStream GetFileForJobDefinition( - string definitionName, - string definitionPath, - FileMode fileMode, - FileAccess fileAccess, - FileShare fileShare) - { - if (string.IsNullOrEmpty(definitionName)) - { - throw new PSArgumentException("definitionName"); - } - - if (string.IsNullOrEmpty(definitionPath)) - { - throw new PSArgumentException("definitionPath"); - } - - string filePathName = string.Format(CultureInfo.InvariantCulture, @"{0}\{1}\{2}.xml", - definitionPath, definitionName, DefinitionFileName); - return File.Open(filePathName, fileMode, fileAccess, fileShare); - } - - /// - /// Checks the provided path against the the default path of scheduled jobs - /// for the current user. - /// - /// Path for scheduled job definitions. - /// True if paths are equal. - public static bool IsDefaultUserPath(string definitionPath) - { - return definitionPath.Equals(GetJobDefinitionLocation(), StringComparison.OrdinalIgnoreCase); - } - - /// - /// Returns a FileStream object for a new scheduled job definition name. - /// - /// Scheduled job definition name. - /// FileStream object. - public static FileStream CreateFileForJobDefinition( - string definitionName) - { - if (string.IsNullOrEmpty(definitionName)) - { - throw new PSArgumentException("definitionName"); - } - - string filePathName = CreateFilePathName(definitionName, DefinitionFileName); - return File.Create(filePathName); - } - - /// - /// Returns an IEnumerable object of scheduled job definition names in - /// the job store. - /// - /// IEnumerable of job definition names. - public static IEnumerable GetJobDefinitions() - { - // Directory names are identical to the corresponding scheduled job definition names. - string directoryPath = GetDirectoryPath(); - IEnumerable definitions = Directory.EnumerateDirectories(directoryPath); - return (definitions != null) ? definitions : new Collection() as IEnumerable; - } - - /// - /// Returns a FileStream object for an existing scheduled job definition - /// run. - /// - /// Scheduled job definition name. - /// DateTime of job run start time. - /// Job run item. - /// File access. - /// File mode. - /// File share. - /// FileStream object. - public static FileStream GetFileForJobRunItem( - string definitionName, - DateTime runStart, - JobRunItem runItem, - FileMode fileMode, - FileAccess fileAccess, - FileShare fileShare) - { - if (string.IsNullOrEmpty(definitionName)) - { - throw new PSArgumentException("definitionName"); - } - - string filePathName = GetRunFilePathName(definitionName, runItem, runStart); - return File.Open(filePathName, fileMode, fileAccess, fileShare); - } - - /// - /// Returns a FileStream object for a new scheduled job definition run. - /// - /// Scheduled job definition path. - /// DateTime of job run start time. - /// Job run item. - /// FileStream object. - public static FileStream CreateFileForJobRunItem( - string definitionOutputPath, - DateTime runStart, - JobRunItem runItem) - { - if (string.IsNullOrEmpty(definitionOutputPath)) - { - throw new PSArgumentException("definitionOutputPath"); - } - - string filePathName = GetRunFilePathNameFromPath(definitionOutputPath, runItem, runStart); - - // If the file already exists, we overwrite it because the job run - // can be updated multiple times while the job is running. - return File.Create(filePathName); - } - - /// - /// Returns a collection of DateTime objects which specify job run directories - /// that are currently in the store. - /// - /// Scheduled job definition name. - /// Collection of DateTime objects. - public static Collection GetJobRunsForDefinition( - string definitionName) - { - if (string.IsNullOrEmpty(definitionName)) - { - throw new PSArgumentException("definitionName"); - } - - string definitionOutputPath = GetJobRunOutputDirectory(definitionName); - - return GetJobRunsForDefinitionPath(definitionOutputPath); - } - - /// - /// Returns a collection of DateTime objects which specify job run directories - /// that are currently in the store. - /// - /// Scheduled job definition job run Output path. - /// Collection of DateTime objects. - public static Collection GetJobRunsForDefinitionPath( - string definitionOutputPath) - { - if (string.IsNullOrEmpty(definitionOutputPath)) - { - throw new PSArgumentException("definitionOutputPath"); - } - - Collection jobRunInfos = new Collection(); - IEnumerable jobRuns = Directory.EnumerateDirectories(definitionOutputPath); - if (jobRuns != null) - { - // Job run directory names are the date/times that the job was started. - foreach (string jobRun in jobRuns) - { - DateTime jobRunDateTime; - int indx = jobRun.LastIndexOf('\\'); - string jobRunName = (indx != -1) ? jobRun.Substring(indx + 1) : jobRun; - if (ConvertJobRunNameToDateTime(jobRunName, out jobRunDateTime)) - { - jobRunInfos.Add(jobRunDateTime); - } - } - } - - return jobRunInfos; - } - - /// - /// Remove the job definition and all job runs from job store. - /// - /// Scheduled Job Definition name. - public static void RemoveJobDefinition( - string definitionName) - { - if (string.IsNullOrEmpty(definitionName)) - { - throw new PSArgumentException("definitionName"); - } - - // Remove job runs, job definition file, and job definition directory. - string jobDefDirectory = GetJobDefinitionPath(definitionName); - Directory.Delete(jobDefDirectory, true); - } - - /// - /// Renames the directory containing the old job definition name - /// to the new name provided. - /// - /// Existing job definition directory. - /// Renamed job definition directory. - public static void RenameScheduledJobDefDir( - string oldDefName, - string newDefName) - { - if (string.IsNullOrEmpty(oldDefName)) - { - throw new PSArgumentException("oldDefName"); - } - - if (string.IsNullOrEmpty(newDefName)) - { - throw new PSArgumentException("newDefName"); - } - - string oldDirPath = GetJobDefinitionPath(oldDefName); - string newDirPath = GetJobDefinitionPath(newDefName); - Directory.Move(oldDirPath, newDirPath); - } - - /// - /// Remove a single job definition job run from the job store. - /// - /// Scheduled Job Definition name. - /// DateTime of job run. - public static void RemoveJobRun( - string definitionName, - DateTime runStart) - { - if (string.IsNullOrEmpty(definitionName)) - { - throw new PSArgumentException("definitionName"); - } - - // Remove the job run files and directory. - string runDirectory = GetRunDirectory(definitionName, runStart); - Directory.Delete(runDirectory, true); - } - - /// - /// Remove a single job definition job run from the job store. - /// - /// Scheduled Job Definition Output path. - /// DateTime of job run. - public static void RemoveJobRunFromOutputPath( - string definitionOutputPath, - DateTime runStart) - { - if (string.IsNullOrEmpty(definitionOutputPath)) - { - throw new PSArgumentException("definitionOutputPath"); - } - - // Remove the job run files and directory. - string runDirectory = GetRunDirectoryFromPath(definitionOutputPath, runStart); - Directory.Delete(runDirectory, true); - } - - /// - /// Remove all job runs for this job definition. - /// - /// Scheduled Job Definition name. - public static void RemoveAllJobRuns( - string definitionName) - { - if (string.IsNullOrEmpty(definitionName)) - { - throw new PSArgumentException("definitionName"); - } - - Collection jobRuns = GetJobRunsForDefinition(definitionName); - foreach (DateTime jobRun in jobRuns) - { - string jobRunPath = GetRunDirectory(definitionName, jobRun); - Directory.Delete(jobRunPath, true); - } - } - - /// - /// Set read access on provided definition file for specified user. - /// - /// Definition name. - /// Account user name. - public static void SetReadAccessOnDefinitionFile( - string definitionName, - string user) - { - string filePath = GetFilePathName(definitionName, DefinitionFileName); - - // Get file security for existing file. - FileSecurity fileSecurity = new FileSecurity( - filePath, - AccessControlSections.Access); - - // Create rule. - FileSystemAccessRule fileAccessRule = new FileSystemAccessRule( - user, - FileSystemRights.Read, - AccessControlType.Allow); - fileSecurity.AddAccessRule(fileAccessRule); - - // Apply rule. - File.SetAccessControl(filePath, fileSecurity); - } - - /// - /// Set write access on Output directory for provided definition for - /// specified user. - /// - /// Definition name. - /// Account user name. - public static void SetWriteAccessOnJobRunOutput( - string definitionName, - string user) - { - string outputDirectoryPath = GetJobRunOutputDirectory(definitionName); - AddFullAccessToDirectory(user, outputDirectoryPath); - } - - /// - /// Returns the directory path for job run output for the specified - /// scheduled job definition. - /// - /// Definition name. - /// Directory Path. - public static string GetJobRunOutputDirectory( - string definitionName) - { - if (string.IsNullOrEmpty(definitionName)) - { - throw new PSArgumentException("definitionName"); - } - - return Path.Combine(GetJobDefinitionPath(definitionName), JobRunOutput); - } - - /// - /// Gets the directory path for a Scheduled Job Definition. - /// - /// Directory Path. - public static string GetJobDefinitionLocation() - { -#if UNIX - return Path.Combine(Platform.SelectProductNameForDirectory(Platform.XDG_Type.CACHE), "ScheduledJobs")); -#else - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ScheduledJobsPath); -#endif - } - - public static void CreateDirectoryIfNotExists() - { - GetDirectoryPath(); - } - - #endregion - - #region Private Methods - - /// - /// Gets the directory path for Scheduled Jobs. Will create the directory if - /// it does not exist. - /// - /// Directory Path. - private static string GetDirectoryPath() - { - string pathName; -#if UNIX - pathName = Path.Combine(Platform.SelectProductNameForDirectory(Platform.XDG_Type.CACHE), "ScheduledJobs")); -#else - pathName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ScheduledJobsPath); -#endif - if (!Directory.Exists(pathName)) - { - Directory.CreateDirectory(pathName); - } - - return pathName; - } - - /// - /// Creates a ScheduledJob definition directory with provided definition name - /// along with a job run Output directory, and returns a file path/name. - /// ...\ScheduledJobs\definitionName\fileName.xml - /// ...\ScheduledJobs\definitionName\Output\ - /// - /// Definition name. - /// File name. - /// File path/name. - private static string CreateFilePathName(string definitionName, string fileName) - { - string filePath = GetJobDefinitionPath(definitionName); - string outputPath = GetJobRunOutputDirectory(definitionName); - if (Directory.Exists(filePath)) - { - ScheduledJobException ex = new ScheduledJobException(StringUtil.Format(ScheduledJobErrorStrings.JobDefFileAlreadyExists, definitionName)); - ex.FQEID = ScheduledJobDefExistsFQEID; - throw ex; - } - - Directory.CreateDirectory(filePath); - Directory.CreateDirectory(outputPath); - return string.Format(CultureInfo.InstalledUICulture, @"{0}\{1}.xml", filePath, fileName); - } - - /// - /// Returns a file path/name for an existing Scheduled job definition directory. - /// - /// Definition name. - /// File name. - /// File path/name. - private static string GetFilePathName(string definitionName, string fileName) - { - string filePath = GetJobDefinitionPath(definitionName); - return string.Format(CultureInfo.InvariantCulture, @"{0}\{1}.xml", filePath, fileName); - } - - /// - /// Gets the directory path for a Scheduled Job Definition. - /// - /// Scheduled job definition name. - /// Directory Path. - private static string GetJobDefinitionPath(string definitionName) - { -#if UNIX - return Path.Combine(Platform.SelectProductNameForDirectory(Platform.XDG_Type.CACHE), "ScheduledJobs", definitionName); -#else - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - ScheduledJobsPath, - definitionName); -#endif - } - - /// - /// Returns a directory path for an existing ScheduledJob run result directory. - /// - /// Definition name. - /// File name. - /// Directory Path. - private static string GetRunDirectory( - string definitionName, - DateTime runStart) - { - string directoryPath = GetJobRunOutputDirectory(definitionName); - return string.Format(CultureInfo.InvariantCulture, @"{0}\{1}", directoryPath, - ConvertDateTimeToJobRunName(runStart)); - } - - /// - /// Returns a directory path for an existing ScheduledJob run based on - /// provided definition Output directory path. - /// - /// Output directory path. - /// File name. - /// Directory Path. - private static string GetRunDirectoryFromPath( - string definitionOutputPath, - DateTime runStart) - { - return string.Format(CultureInfo.InvariantCulture, @"{0}\{1}", - definitionOutputPath, ConvertDateTimeToJobRunName(runStart)); - } - - /// - /// Returns a file path/name for a run result file. Will create the - /// job run directory if it does not exist. - /// - /// Definition name. - /// Result type. - /// Run date. - /// File path/name. - private static string GetRunFilePathName( - string definitionName, - JobRunItem runItem, - DateTime runStart) - { - string directoryPath = GetJobRunOutputDirectory(definitionName); - string jobRunPath = string.Format(CultureInfo.InvariantCulture, @"{0}\{1}", - directoryPath, ConvertDateTimeToJobRunName(runStart)); - - return string.Format(CultureInfo.InvariantCulture, @"{0}\{1}.xml", jobRunPath, - runItem.ToString()); - } - - /// - /// Returns a file path/name for a job run result, based on the passed in - /// job run output path. Will create the job run directory if it does not - /// exist. - /// - /// Definition job run output path. - /// Result type. - /// Run date. - /// - private static string GetRunFilePathNameFromPath( - string outputPath, - JobRunItem runItem, - DateTime runStart) - { - string jobRunPath = string.Format(CultureInfo.InvariantCulture, @"{0}\{1}", - outputPath, ConvertDateTimeToJobRunName(runStart)); - - if (!Directory.Exists(jobRunPath)) - { - // Create directory for this job run date. - Directory.CreateDirectory(jobRunPath); - } - - return string.Format(CultureInfo.InvariantCulture, @"{0}\{1}.xml", jobRunPath, - runItem.ToString()); - } - - private static void AddFullAccessToDirectory( - string user, - string directoryPath) - { - // Create rule. - DirectoryInfo info = new DirectoryInfo(directoryPath); - DirectorySecurity dSecurity = info.GetAccessControl(); - FileSystemAccessRule fileAccessRule = new FileSystemAccessRule( - user, - FileSystemRights.FullControl, - InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, - PropagationFlags.None, - AccessControlType.Allow); - - // Apply rule. - dSecurity.AddAccessRule(fileAccessRule); - info.SetAccessControl(dSecurity); - } - - // - // String format: 'YYYYMMDD-HHMMSS-SSS' - // ,where SSS is milliseconds. - // - - private static string ConvertDateTimeToJobRunName(DateTime dt) - { - return string.Format(CultureInfo.InvariantCulture, - @"{0:d4}{1:d2}{2:d2}-{3:d2}{4:d2}{5:d2}-{6:d3}", - dt.Year, dt.Month, dt.Day, - dt.Hour, dt.Minute, dt.Second, dt.Millisecond); - } - - /// - /// Converts a jobRun name string to an equivalent DateTime. - /// - /// - /// - /// - internal static bool ConvertJobRunNameToDateTime(string jobRunName, out DateTime jobRun) - { - if (jobRunName == null || jobRunName.Length != 19) - { - jobRun = new DateTime(); - return false; - } - - int year = 0; - int month = 0; - int day = 0; - int hour = 0; - int minute = 0; - int second = 0; - int msecs = 0; - bool success = true; - - try - { - year = Convert.ToInt32(jobRunName.Substring(0, 4)); - month = Convert.ToInt32(jobRunName.Substring(4, 2)); - day = Convert.ToInt32(jobRunName.Substring(6, 2)); - hour = Convert.ToInt32(jobRunName.Substring(9, 2)); - minute = Convert.ToInt32(jobRunName.Substring(11, 2)); - second = Convert.ToInt32(jobRunName.Substring(13, 2)); - msecs = Convert.ToInt32(jobRunName.Substring(16, 3)); - } - catch (FormatException) - { - success = false; - } - catch (OverflowException) - { - success = false; - } - - if (success) - { - jobRun = new DateTime(year, month, day, hour, minute, second, msecs); - } - else - { - jobRun = new DateTime(); - } - - return success; - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobTrigger.cs b/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobTrigger.cs deleted file mode 100644 index f32f33ce008..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobTrigger.cs +++ /dev/null @@ -1,897 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Globalization; -using System.Management.Automation; -using System.Runtime.Serialization; -using System.Security.Permissions; -using System.Text; -using System.Threading; - -using Microsoft.Management.Infrastructure; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This class contains parameters used to define how/when a PowerShell job is - /// run via the Windows Task Scheduler (WTS). - /// - [Serializable] - public sealed class ScheduledJobTrigger : ISerializable - { - #region Private Members - - private DateTime? _time; - private List _daysOfWeek; - private TimeSpan _randomDelay; - private Int32 _interval = 1; - private string _user; - private TriggerFrequency _frequency = TriggerFrequency.None; - private TimeSpan? _repInterval; - private TimeSpan? _repDuration; - - private Int32 _id; - private bool _enabled = true; - private ScheduledJobDefinition _jobDefAssociation; - - private static string _allUsers = "*"; - - #endregion - - #region Public Properties - - /// - /// Trigger time. - /// - public DateTime? At - { - get { return _time; } - - set { _time = value; } - } - - /// - /// Trigger days of week. - /// - public List DaysOfWeek - { - get { return _daysOfWeek; } - - set { _daysOfWeek = value; } - } - - /// - /// Trigger days or weeks interval. - /// - public Int32 Interval - { - get { return _interval; } - - set { _interval = value; } - } - - /// - /// Trigger frequency. - /// - public TriggerFrequency Frequency - { - get { return _frequency; } - - set { _frequency = value; } - } - - /// - /// Trigger random delay. - /// - public TimeSpan RandomDelay - { - get { return _randomDelay; } - - set { _randomDelay = value; } - } - - /// - /// Trigger Once frequency repetition interval. - /// - public TimeSpan? RepetitionInterval - { - get { return _repInterval; } - - set - { - // A TimeSpan value of zero is equivalent to a null value. - _repInterval = (value != null && value.Value == TimeSpan.Zero) ? - null : value; - } - } - - /// - /// Trigger Once frequency repetition duration. - /// - public TimeSpan? RepetitionDuration - { - get { return _repDuration; } - - set - { - // A TimeSpan value of zero is equivalent to a null value. - _repDuration = (value != null && value.Value == TimeSpan.Zero) ? - null : value; - } - } - - /// - /// Trigger user name. - /// - public string User - { - get { return _user; } - - set { _user = value; } - } - - /// - /// Returns the trigger local Id. - /// - public Int32 Id - { - get { return _id; } - - internal set { _id = value; } - } - - /// - /// Defines enabled state of trigger. - /// - public bool Enabled - { - get { return _enabled; } - - set { _enabled = value; } - } - - /// - /// ScheduledJobDefinition object this trigger is associated with. - /// - public ScheduledJobDefinition JobDefinition - { - get { return _jobDefAssociation; } - - internal set { _jobDefAssociation = value; } - } - - #endregion - - #region Constructors - - /// - /// Default constructor. - /// - public ScheduledJobTrigger() - { } - - /// - /// Constructor. - /// - /// Enabled. - /// Trigger frequency. - /// Trigger time. - /// Weekly days of week. - /// Daily or Weekly interval. - /// Random delay. - /// Repetition interval. - /// Repetition duration. - /// Logon user. - /// Trigger id. - private ScheduledJobTrigger( - bool enabled, - TriggerFrequency frequency, - DateTime? time, - List daysOfWeek, - Int32 interval, - TimeSpan randomDelay, - TimeSpan? repetitionInterval, - TimeSpan? repetitionDuration, - string user, - Int32 id) - { - _enabled = enabled; - _frequency = frequency; - _time = time; - _daysOfWeek = daysOfWeek; - _interval = interval; - _randomDelay = randomDelay; - RepetitionInterval = repetitionInterval; - RepetitionDuration = repetitionDuration; - _user = user; - _id = id; - } - - /// - /// Copy constructor. - /// - /// ScheduledJobTrigger. - internal ScheduledJobTrigger(ScheduledJobTrigger copyTrigger) - { - if (copyTrigger == null) - { - throw new PSArgumentNullException("copyTrigger"); - } - - _enabled = copyTrigger.Enabled; - _frequency = copyTrigger.Frequency; - _id = copyTrigger.Id; - _time = copyTrigger.At; - _daysOfWeek = copyTrigger.DaysOfWeek; - _interval = copyTrigger.Interval; - _randomDelay = copyTrigger.RandomDelay; - _repInterval = copyTrigger.RepetitionInterval; - _repDuration = copyTrigger.RepetitionDuration; - _user = copyTrigger.User; - - _jobDefAssociation = copyTrigger.JobDefinition; - } - - /// - /// Serialization constructor. - /// - /// SerializationInfo. - /// StreamingContext. - private ScheduledJobTrigger( - SerializationInfo info, - StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException("info"); - } - - DateTime time = info.GetDateTime("Time_Value"); - if (time != DateTime.MinValue) - { - _time = time; - } - else - { - _time = null; - } - - RepetitionInterval = (TimeSpan?)info.GetValue("RepetitionInterval_Value", typeof(TimeSpan)); - RepetitionDuration = (TimeSpan?)info.GetValue("RepetitionDuration_Value", typeof(TimeSpan)); - - _daysOfWeek = (List)info.GetValue("DaysOfWeek_Value", typeof(List)); - _randomDelay = (TimeSpan)info.GetValue("RandomDelay_Value", typeof(TimeSpan)); - _interval = info.GetInt32("Interval_Value"); - _user = info.GetString("User_Value"); - _frequency = (TriggerFrequency)info.GetValue("TriggerFrequency_Value", typeof(TriggerFrequency)); - _id = info.GetInt32("ID_Value"); - _enabled = info.GetBoolean("Enabled_Value"); - - // Runtime reference and not saved to store. - _jobDefAssociation = null; - } - - #endregion - - #region ISerializable Implementation - - /// - /// GetObjectData for ISerializable implementation. - /// - /// - /// - public void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException("info"); - } - - if (_time == null) - { - info.AddValue("Time_Value", DateTime.MinValue); - } - else - { - info.AddValue("Time_Value", _time); - } - - if (_repInterval == null) - { - info.AddValue("RepetitionInterval_Value", TimeSpan.Zero); - } - else - { - info.AddValue("RepetitionInterval_Value", _repInterval); - } - - if (_repDuration == null) - { - info.AddValue("RepetitionDuration_Value", TimeSpan.Zero); - } - else - { - info.AddValue("RepetitionDuration_Value", _repDuration); - } - - info.AddValue("DaysOfWeek_Value", _daysOfWeek); - info.AddValue("RandomDelay_Value", _randomDelay); - info.AddValue("Interval_Value", _interval); - info.AddValue("User_Value", _user); - info.AddValue("TriggerFrequency_Value", _frequency); - info.AddValue("ID_Value", _id); - info.AddValue("Enabled_Value", _enabled); - } - - #endregion - - #region Internal Methods - - internal void ClearProperties() - { - _time = null; - _daysOfWeek = null; - _interval = 1; - _randomDelay = TimeSpan.Zero; - _repInterval = null; - _repDuration = null; - _user = null; - _frequency = TriggerFrequency.None; - _enabled = false; - _id = 0; - } - - internal void Validate() - { - switch (_frequency) - { - case TriggerFrequency.None: - throw new ScheduledJobException(ScheduledJobErrorStrings.MissingJobTriggerType); - - case TriggerFrequency.AtStartup: - // AtStartup has no required parameters. - break; - - case TriggerFrequency.AtLogon: - // AtLogon has no required parameters. - break; - - case TriggerFrequency.Once: - if (_time == null) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.MissingJobTriggerTime, ScheduledJobErrorStrings.TriggerOnceType); - throw new ScheduledJobException(msg); - } - - if (_repInterval != null || _repDuration != null) - { - ValidateOnceRepetitionParams(_repInterval, _repDuration); - } - - break; - - case TriggerFrequency.Daily: - if (_time == null) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.MissingJobTriggerTime, ScheduledJobErrorStrings.TriggerDailyType); - throw new ScheduledJobException(msg); - } - - if (_interval < 1) - { - throw new ScheduledJobException(ScheduledJobErrorStrings.InvalidDaysIntervalParam); - } - - break; - - case TriggerFrequency.Weekly: - if (_time == null) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.MissingJobTriggerTime, ScheduledJobErrorStrings.TriggerWeeklyType); - throw new ScheduledJobException(msg); - } - - if (_interval < 1) - { - throw new ScheduledJobException(ScheduledJobErrorStrings.InvalidWeeksIntervalParam); - } - - if (_daysOfWeek == null || _daysOfWeek.Count == 0) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.MissingJobTriggerDaysOfWeek, ScheduledJobErrorStrings.TriggerWeeklyType); - throw new ScheduledJobException(msg); - } - - break; - } - } - - internal static void ValidateOnceRepetitionParams( - TimeSpan? repInterval, - TimeSpan? repDuration) - { - // Both Interval and Duration parameters must be specified together. - if (repInterval == null || repDuration == null) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidRepetitionParams); - } - - // Interval and Duration parameters must not have negative value. - if (repInterval < TimeSpan.Zero || repDuration < TimeSpan.Zero) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidRepetitionParamValues); - } - - // Zero values are allowed but only if both parameters are set to zero. - // This removes repetition from the Once trigger. - if (repInterval == TimeSpan.Zero && repDuration != TimeSpan.Zero) - { - throw new PSArgumentException(ScheduledJobErrorStrings.MismatchedRepetitionParamValues); - } - - // Parameter values must be GE to one minute unless both are zero to remove repetition. - if (repInterval < TimeSpan.FromMinutes(1) && - !(repInterval == TimeSpan.Zero && repDuration == TimeSpan.Zero)) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidRepetitionIntervalValue); - } - - // Interval parameter must be LE to Duration parameter. - if (repInterval > repDuration) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidRepetitionInterval); - } - } - - internal void CopyTo(ScheduledJobTrigger targetTrigger) - { - if (targetTrigger == null) - { - throw new PSArgumentNullException("targetTrigger"); - } - - targetTrigger.Enabled = _enabled; - targetTrigger.Frequency = _frequency; - targetTrigger.Id = _id; - targetTrigger.At = _time; - targetTrigger.DaysOfWeek = _daysOfWeek; - targetTrigger.Interval = _interval; - targetTrigger.RandomDelay = _randomDelay; - targetTrigger.RepetitionInterval = _repInterval; - targetTrigger.RepetitionDuration = _repDuration; - targetTrigger.User = _user; - targetTrigger.JobDefinition = _jobDefAssociation; - } - - #endregion - - #region Static methods - - /// - /// Creates a one time ScheduledJobTrigger object. - /// - /// DateTime when trigger activates. - /// Random delay. - /// Repetition interval. - /// Repetition duration. - /// Trigger Id. - /// Trigger enabled state. - /// ScheduledJobTrigger. - public static ScheduledJobTrigger CreateOnceTrigger( - DateTime time, - TimeSpan delay, - TimeSpan? repetitionInterval, - TimeSpan? repetitionDuration, - Int32 id, - bool enabled) - { - return new ScheduledJobTrigger( - enabled, - TriggerFrequency.Once, - time, - null, - 1, - delay, - repetitionInterval, - repetitionDuration, - null, - id); - } - - /// - /// Creates a daily ScheduledJobTrigger object. - /// - /// Time of day when trigger activates. - /// Days interval for trigger activation. - /// Random delay. - /// Trigger Id. - /// Trigger enabled state. - /// ScheduledJobTrigger. - public static ScheduledJobTrigger CreateDailyTrigger( - DateTime time, - Int32 interval, - TimeSpan delay, - Int32 id, - bool enabled) - { - return new ScheduledJobTrigger( - enabled, - TriggerFrequency.Daily, - time, - null, - interval, - delay, - null, - null, - null, - id); - } - - /// - /// Creates a weekly ScheduledJobTrigger object. - /// - /// Time of day when trigger activates. - /// Weeks interval for trigger activation. - /// Days of the week for trigger activation. - /// Random delay. - /// Trigger Id. - /// Trigger enabled state. - /// ScheduledJobTrigger. - public static ScheduledJobTrigger CreateWeeklyTrigger( - DateTime time, - Int32 interval, - IEnumerable daysOfWeek, - TimeSpan delay, - Int32 id, - bool enabled) - { - List lDaysOfWeek = (daysOfWeek != null) ? new List(daysOfWeek) : null; - - return new ScheduledJobTrigger( - enabled, - TriggerFrequency.Weekly, - time, - lDaysOfWeek, - interval, - delay, - null, - null, - null, - id); - } - - /// - /// Creates a trigger that activates after user log on. - /// - /// Name of user. - /// Random delay. - /// Trigger Id. - /// Trigger enabled state. - /// ScheduledJobTrigger. - public static ScheduledJobTrigger CreateAtLogOnTrigger( - string user, - TimeSpan delay, - Int32 id, - bool enabled) - { - return new ScheduledJobTrigger( - enabled, - TriggerFrequency.AtLogon, - null, - null, - 1, - delay, - null, - null, - string.IsNullOrEmpty(user) ? AllUsers : user, - id); - } - - /// - /// Creates a trigger that activates after OS boot. - /// - /// Random delay. - /// Trigger Id. - /// Trigger enabled state. - /// ScheduledJobTrigger. - public static ScheduledJobTrigger CreateAtStartupTrigger( - TimeSpan delay, - Int32 id, - bool enabled) - { - return new ScheduledJobTrigger( - enabled, - TriggerFrequency.AtStartup, - null, - null, - 1, - delay, - null, - null, - null, - id); - } - - /// - /// Compares provided user name to All Users string ("*"). - /// - /// Logon user name. - /// Boolean, true if All Users. - internal static bool IsAllUsers(string userName) - { - return (string.Compare(userName, ScheduledJobTrigger.AllUsers, - StringComparison.OrdinalIgnoreCase) == 0); - } - - /// - /// Returns the All Users string. - /// - internal static string AllUsers - { - get { return _allUsers; } - } - - #endregion - - #region Public Methods - - /// - /// Update the associated ScheduledJobDefinition object with the - /// current properties of this object. - /// - public void UpdateJobDefinition() - { - if (_jobDefAssociation == null) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.NoAssociatedJobDefinitionForTrigger, _id); - throw new RuntimeException(msg); - } - - _jobDefAssociation.UpdateTriggers(new ScheduledJobTrigger[1] { this }, true); - } - - #endregion - } - - #region Public Enums - - /// - /// Specifies trigger types in terms of the frequency that - /// the trigger is activated. - /// - public enum TriggerFrequency - { - /// - /// None. - /// - None = 0, - /// - /// Trigger activates once at a specified time. - /// - Once = 1, - /// - /// Trigger activates daily. - /// - Daily = 2, - /// - /// Trigger activates on a weekly basis and multiple days - /// during the week. - /// - Weekly = 3, - /// - /// Trigger activates at user logon to the operating system. - /// - AtLogon = 4, - /// - /// Trigger activates after machine boot up. - /// - AtStartup = 5 - } - - #endregion - - #region JobTriggerToCimInstanceConverter - /// - /// Class providing implementation of PowerShell conversions for types in Microsoft.Management.Infrastructure namespace. - /// - public sealed class JobTriggerToCimInstanceConverter : PSTypeConverter - { - private static readonly string CIM_TRIGGER_NAMESPACE = @"Root\Microsoft\Windows\TaskScheduler"; - - /// - /// Determines if the converter can convert the parameter to the parameter. - /// - /// The value to convert from. - /// The type to convert to. - /// True if the converter can convert the parameter to the parameter, otherwise false. - public override bool CanConvertFrom(object sourceValue, Type destinationType) - { - if (destinationType == null) - { - throw new ArgumentNullException("destinationType"); - } - - return (sourceValue is ScheduledJobTrigger) && (destinationType.Equals(typeof(CimInstance))); - } - - /// - /// Converts the parameter to the parameter using formatProvider and ignoreCase. - /// - /// The value to convert from. - /// The type to convert to. - /// The format provider to use like in IFormattable's ToString. - /// True if case should be ignored. - /// The parameter converted to the parameter using formatProvider and ignoreCase. - /// If no conversion was possible. - public override object ConvertFrom(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase) - { - if (destinationType == null) - { - throw new ArgumentNullException("destinationType"); - } - - if (sourceValue == null) - { - throw new ArgumentNullException("sourceValue"); - } - - ScheduledJobTrigger originalTrigger = (ScheduledJobTrigger) sourceValue; - using (CimSession cimSession = CimSession.Create(null)) - { - switch (originalTrigger.Frequency) - { - case TriggerFrequency.Weekly: - return ConvertToWeekly(originalTrigger, cimSession); - case TriggerFrequency.Once: - return ConvertToOnce(originalTrigger, cimSession); - case TriggerFrequency.Daily: - return ConvertToDaily(originalTrigger, cimSession); - case TriggerFrequency.AtStartup: - return ConvertToAtStartup(originalTrigger, cimSession); - case TriggerFrequency.AtLogon: - return ConvertToAtLogon(originalTrigger, cimSession); - case TriggerFrequency.None: - return ConvertToDefault(originalTrigger, cimSession); - default: - string errorMsg = StringUtil.Format(ScheduledJobErrorStrings.UnknownTriggerFrequency, - originalTrigger.Frequency.ToString()); - throw new PSInvalidOperationException(errorMsg); - } - } - } - - /// - /// Returns true if the converter can convert the parameter to the parameter. - /// - /// The value to convert from. - /// The type to convert to. - /// True if the converter can convert the parameter to the parameter, otherwise false. - public override bool CanConvertTo(object sourceValue, Type destinationType) - { - return false; - } - - /// - /// Converts the parameter to the parameter using formatProvider and ignoreCase. - /// - /// The value to convert from. - /// The type to convert to. - /// The format provider to use like in IFormattable's ToString. - /// True if case should be ignored. - /// SourceValue converted to the parameter using formatProvider and ignoreCase. - /// If no conversion was possible. - public override object ConvertTo(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase) - { - throw new NotImplementedException(); - } - - #region Helper Methods - - private CimInstance ConvertToWeekly(ScheduledJobTrigger trigger, CimSession cimSession) - { - CimClass cimClass = cimSession.GetClass(CIM_TRIGGER_NAMESPACE, "MSFT_TaskWeeklyTrigger"); - CimInstance cimInstance = new CimInstance(cimClass); - - cimInstance.CimInstanceProperties["DaysOfWeek"].Value = ScheduledJobWTS.ConvertDaysOfWeekToMask(trigger.DaysOfWeek); - cimInstance.CimInstanceProperties["RandomDelay"].Value = ScheduledJobWTS.ConvertTimeSpanToWTSString(trigger.RandomDelay); - cimInstance.CimInstanceProperties["WeeksInterval"].Value = trigger.Interval; - - AddCommonProperties(trigger, cimInstance); - return cimInstance; - } - - private CimInstance ConvertToOnce(ScheduledJobTrigger trigger, CimSession cimSession) - { - CimClass cimClass = cimSession.GetClass(CIM_TRIGGER_NAMESPACE, "MSFT_TaskTimeTrigger"); - CimInstance cimInstance = new CimInstance(cimClass); - - cimInstance.CimInstanceProperties["RandomDelay"].Value = ScheduledJobWTS.ConvertTimeSpanToWTSString(trigger.RandomDelay); - - if (trigger.RepetitionInterval != null && trigger.RepetitionDuration != null) - { - CimClass cimRepClass = cimSession.GetClass(CIM_TRIGGER_NAMESPACE, "MSFT_TaskRepetitionPattern"); - CimInstance cimRepInstance = new CimInstance(cimRepClass); - - cimRepInstance.CimInstanceProperties["Interval"].Value = ScheduledJobWTS.ConvertTimeSpanToWTSString(trigger.RepetitionInterval.Value); - - if (trigger.RepetitionDuration == TimeSpan.MaxValue) - { - cimRepInstance.CimInstanceProperties["StopAtDurationEnd"].Value = false; - } - else - { - cimRepInstance.CimInstanceProperties["StopAtDurationEnd"].Value = true; - cimRepInstance.CimInstanceProperties["Duration"].Value = ScheduledJobWTS.ConvertTimeSpanToWTSString(trigger.RepetitionDuration.Value); - } - - cimInstance.CimInstanceProperties["Repetition"].Value = cimRepInstance; - } - - AddCommonProperties(trigger, cimInstance); - return cimInstance; - } - - private CimInstance ConvertToDaily(ScheduledJobTrigger trigger, CimSession cimSession) - { - CimClass cimClass = cimSession.GetClass(CIM_TRIGGER_NAMESPACE, "MSFT_TaskDailyTrigger"); - CimInstance cimInstance = new CimInstance(cimClass); - - cimInstance.CimInstanceProperties["RandomDelay"].Value = ScheduledJobWTS.ConvertTimeSpanToWTSString(trigger.RandomDelay); - cimInstance.CimInstanceProperties["DaysInterval"].Value = trigger.Interval; - - AddCommonProperties(trigger, cimInstance); - return cimInstance; - } - - private CimInstance ConvertToAtLogon(ScheduledJobTrigger trigger, CimSession cimSession) - { - CimClass cimClass = cimSession.GetClass(CIM_TRIGGER_NAMESPACE, "MSFT_TaskLogonTrigger"); - CimInstance cimInstance = new CimInstance(cimClass); - - cimInstance.CimInstanceProperties["Delay"].Value = ScheduledJobWTS.ConvertTimeSpanToWTSString(trigger.RandomDelay); - - // Convert the "AllUsers" name ("*" character) to null for Task Scheduler. - string userId = (ScheduledJobTrigger.IsAllUsers(trigger.User)) ? null : trigger.User; - cimInstance.CimInstanceProperties["UserId"].Value = userId; - - AddCommonProperties(trigger, cimInstance); - return cimInstance; - } - - private CimInstance ConvertToAtStartup(ScheduledJobTrigger trigger, CimSession cimSession) - { - CimClass cimClass = cimSession.GetClass(CIM_TRIGGER_NAMESPACE, "MSFT_TaskBootTrigger"); - CimInstance cimInstance = new CimInstance(cimClass); - - cimInstance.CimInstanceProperties["Delay"].Value = ScheduledJobWTS.ConvertTimeSpanToWTSString(trigger.RandomDelay); - - AddCommonProperties(trigger, cimInstance); - return cimInstance; - } - - private CimInstance ConvertToDefault(ScheduledJobTrigger trigger, CimSession cimSession) - { - CimClass cimClass = cimSession.GetClass(CIM_TRIGGER_NAMESPACE, "MSFT_TaskTrigger"); - CimInstance result = new CimInstance(cimClass); - AddCommonProperties(trigger, result); - return result; - } - - private static void AddCommonProperties(ScheduledJobTrigger trigger, CimInstance cimInstance) - { - cimInstance.CimInstanceProperties["Enabled"].Value = trigger.Enabled; - - if (trigger.At != null) - { - cimInstance.CimInstanceProperties["StartBoundary"].Value = ScheduledJobWTS.ConvertDateTimeToString(trigger.At); - } - } - - #endregion - } - - #endregion -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobWTS.cs b/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobWTS.cs deleted file mode 100644 index a8d76ef7da8..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobWTS.cs +++ /dev/null @@ -1,961 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Globalization; -using System.Management.Automation; -using System.Runtime.InteropServices; -using System.Security.AccessControl; -using System.Text; - -using TaskScheduler; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// Managed code class to provide Windows Task Scheduler functionality for - /// scheduled jobs. - /// - internal sealed class ScheduledJobWTS : IDisposable - { - #region Private Members - - private ITaskService _taskScheduler; - private ITaskFolder _iRootFolder; - - private const short WTSSunday = 0x01; - private const short WTSMonday = 0x02; - private const short WTSTuesday = 0x04; - private const short WTSWednesday = 0x08; - private const short WTSThursday = 0x10; - private const short WTSFriday = 0x20; - private const short WTSSaturday = 0x40; - - // Task Scheduler folders for PowerShell scheduled job tasks. - private const string TaskSchedulerWindowsFolder = @"\Microsoft\Windows"; - private const string ScheduledJobSubFolder = @"PowerShell\ScheduledJobs"; - private const string ScheduledJobTasksRootFolder = @"\Microsoft\Windows\PowerShell\ScheduledJobs"; - - // Define a single Action Id since PowerShell Scheduled Job tasks will have only one action. - private const string ScheduledJobTaskActionId = "StartPowerShellJob"; - - #endregion - - #region Constructors - - public ScheduledJobWTS() - { - // Create the Windows Task Scheduler object. - _taskScheduler = (ITaskService)new TaskScheduler.TaskScheduler(); - - // Connect the task scheduler object to the local machine - // using the current user security token. - _taskScheduler.Connect(null, null, null, null); - - // Get or create the root folder in Task Scheduler for PowerShell scheduled jobs. - _iRootFolder = GetRootFolder(); - } - - #endregion - - #region Public Methods - - /// - /// Retrieves job triggers from WTS with provided task Id. - /// - /// Task Id. - /// Task not found. - /// ScheduledJobTriggers. - public Collection GetJobTriggers( - string taskId) - { - if (string.IsNullOrEmpty(taskId)) - { - throw new PSArgumentException("taskId"); - } - - ITaskDefinition iTaskDefinition = FindTask(taskId); - - Collection jobTriggers = new Collection(); - ITriggerCollection iTriggerCollection = iTaskDefinition.Triggers; - if (iTriggerCollection != null) - { - foreach (ITrigger iTrigger in iTriggerCollection) - { - ScheduledJobTrigger jobTrigger = CreateJobTrigger(iTrigger); - if (jobTrigger == null) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.UnknownTriggerType, taskId, iTrigger.Id); - throw new ScheduledJobException(msg); - } - - jobTriggers.Add(jobTrigger); - } - } - - return jobTriggers; - } - - /// - /// Retrieves options for the provided task Id. - /// - /// Task Id. - /// Task not found. - /// ScheduledJobOptions. - public ScheduledJobOptions GetJobOptions( - string taskId) - { - if (string.IsNullOrEmpty(taskId)) - { - throw new PSArgumentException("taskId"); - } - - ITaskDefinition iTaskDefinition = FindTask(taskId); - - return CreateJobOptions(iTaskDefinition); - } - - /// - /// Returns a boolean indicating whether the job/task is enabled - /// in the Task Scheduler. - /// - /// - /// - public bool GetTaskEnabled( - string taskId) - { - if (string.IsNullOrEmpty(taskId)) - { - throw new PSArgumentException("taskId"); - } - - ITaskDefinition iTaskDefinition = FindTask(taskId); - - return iTaskDefinition.Settings.Enabled; - } - - /// - /// Creates a new task in WTS with information from ScheduledJobDefinition. - /// - /// ScheduledJobDefinition. - public void CreateTask( - ScheduledJobDefinition definition) - { - if (definition == null) - { - throw new PSArgumentNullException("definition"); - } - - // Create task definition - ITaskDefinition iTaskDefinition = _taskScheduler.NewTask(0); - - // Add task options. - AddTaskOptions(iTaskDefinition, definition.Options); - - // Add task triggers. - foreach (ScheduledJobTrigger jobTrigger in definition.JobTriggers) - { - AddTaskTrigger(iTaskDefinition, jobTrigger); - } - - // Add task action. - AddTaskAction(iTaskDefinition, definition); - - // Create a security descriptor for the current user so that only the user - // (and Local System account) can see/access the registered task. - string startSddl = "D:P(A;;GA;;;SY)(A;;GA;;;BA)"; // DACL Allow Generic Access to System and BUILTIN\Administrators. - System.Security.Principal.SecurityIdentifier userSid = - System.Security.Principal.WindowsIdentity.GetCurrent().User; - CommonSecurityDescriptor SDesc = new CommonSecurityDescriptor(false, false, startSddl); - SDesc.DiscretionaryAcl.AddAccess(AccessControlType.Allow, userSid, 0x10000000, InheritanceFlags.None, PropagationFlags.None); - string sddl = SDesc.GetSddlForm(AccessControlSections.All); - - // Register this new task with the Task Scheduler. - if (definition.Credential == null) - { - // Register task to run as currently logged on user. - _iRootFolder.RegisterTaskDefinition( - definition.Name, - iTaskDefinition, - (int)_TASK_CREATION.TASK_CREATE, - null, // User name - null, // Password - _TASK_LOGON_TYPE.TASK_LOGON_S4U, - sddl); - } - else - { - // Register task to run under provided user account/credentials. - _iRootFolder.RegisterTaskDefinition( - definition.Name, - iTaskDefinition, - (int)_TASK_CREATION.TASK_CREATE, - definition.Credential.UserName, - GetCredentialPassword(definition.Credential), - _TASK_LOGON_TYPE.TASK_LOGON_PASSWORD, - sddl); - } - } - - /// - /// Removes the WTS task for this ScheduledJobDefinition. - /// Throws error if one or more instances of this task are running. - /// Force parameter will stop all running instances and remove task. - /// - /// ScheduledJobDefinition. - /// Force running instances to stop and remove task. - public void RemoveTask( - ScheduledJobDefinition definition, - bool force = false) - { - if (definition == null) - { - throw new PSArgumentNullException("definition"); - } - - RemoveTaskByName(definition.Name, force, false); - } - - /// - /// Removes a Task Scheduler task from the PowerShell/ScheduledJobs folder - /// based on a task name. - /// - /// Task Scheduler task name. - /// Force running instances to stop and remove task. - /// First check for existence of task. - public void RemoveTaskByName( - string taskName, - bool force, - bool firstCheckForTask) - { - // Get registered task. - IRegisteredTask iRegisteredTask = null; - try - { - iRegisteredTask = _iRootFolder.GetTask(taskName); - } - catch (System.IO.DirectoryNotFoundException) - { - if (!firstCheckForTask) - { - throw; - } - } - catch (System.IO.FileNotFoundException) - { - if (!firstCheckForTask) - { - throw; - } - } - - if (iRegisteredTask == null) - { - return; - } - - // Check to see if any instances of this job/task is running. - IRunningTaskCollection iRunningTasks = iRegisteredTask.GetInstances(0); - if (iRunningTasks.Count > 0) - { - if (!force) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.CannotRemoveTaskRunningInstance, taskName); - throw new ScheduledJobException(msg); - } - - // Stop all running tasks. - iRegisteredTask.Stop(0); - } - - // Remove task. - _iRootFolder.DeleteTask(taskName, 0); - } - - /// - /// Starts task running from Task Scheduler. - /// - /// ScheduledJobDefinition. - /// - /// - public void RunTask( - ScheduledJobDefinition definition) - { - // Get registered task. - IRegisteredTask iRegisteredTask = _iRootFolder.GetTask(definition.Name); - - // Run task. - iRegisteredTask.Run(null); - } - - /// - /// Updates an existing task in WTS with information from - /// ScheduledJobDefinition. - /// - /// ScheduledJobDefinition. - public void UpdateTask( - ScheduledJobDefinition definition) - { - if (definition == null) - { - throw new PSArgumentNullException("definition"); - } - - // Get task to update. - ITaskDefinition iTaskDefinition = FindTask(definition.Name); - - // Replace options. - AddTaskOptions(iTaskDefinition, definition.Options); - - // Set enabled state. - iTaskDefinition.Settings.Enabled = definition.Enabled; - - // Replace triggers. - iTaskDefinition.Triggers.Clear(); - foreach (ScheduledJobTrigger jobTrigger in definition.JobTriggers) - { - AddTaskTrigger(iTaskDefinition, jobTrigger); - } - - // Replace action. - iTaskDefinition.Actions.Clear(); - AddTaskAction(iTaskDefinition, definition); - - // Register updated task. - if (definition.Credential == null) - { - // Register task to run as currently logged on user. - _iRootFolder.RegisterTaskDefinition( - definition.Name, - iTaskDefinition, - (int)_TASK_CREATION.TASK_UPDATE, - null, // User name - null, // Password - _TASK_LOGON_TYPE.TASK_LOGON_S4U, - null); - } - else - { - // Register task to run under provided user account/credentials. - _iRootFolder.RegisterTaskDefinition( - definition.Name, - iTaskDefinition, - (int)_TASK_CREATION.TASK_UPDATE, - definition.Credential.UserName, - GetCredentialPassword(definition.Credential), - _TASK_LOGON_TYPE.TASK_LOGON_PASSWORD, - null); - } - } - - #endregion - - #region Private Methods - - /// - /// Creates a new WTS trigger based on the provided ScheduledJobTrigger object - /// and adds it to the provided ITaskDefinition object. - /// - /// ITaskDefinition. - /// ScheduledJobTrigger. - private void AddTaskTrigger( - ITaskDefinition iTaskDefinition, - ScheduledJobTrigger jobTrigger) - { - ITrigger iTrigger = null; - - switch (jobTrigger.Frequency) - { - case TriggerFrequency.AtStartup: - { - iTrigger = iTaskDefinition.Triggers.Create(_TASK_TRIGGER_TYPE2.TASK_TRIGGER_BOOT); - IBootTrigger iBootTrigger = iTrigger as IBootTrigger; - Debug.Assert(iBootTrigger != null); - - iBootTrigger.Delay = ConvertTimeSpanToWTSString(jobTrigger.RandomDelay); - - iTrigger.Id = jobTrigger.Id.ToString(CultureInfo.InvariantCulture); - iTrigger.Enabled = jobTrigger.Enabled; - } - - break; - - case TriggerFrequency.AtLogon: - { - iTrigger = iTaskDefinition.Triggers.Create(_TASK_TRIGGER_TYPE2.TASK_TRIGGER_LOGON); - ILogonTrigger iLogonTrigger = iTrigger as ILogonTrigger; - Debug.Assert(iLogonTrigger != null); - - iLogonTrigger.UserId = ScheduledJobTrigger.IsAllUsers(jobTrigger.User) ? null : jobTrigger.User; - iLogonTrigger.Delay = ConvertTimeSpanToWTSString(jobTrigger.RandomDelay); - - iTrigger.Id = jobTrigger.Id.ToString(CultureInfo.InvariantCulture); - iTrigger.Enabled = jobTrigger.Enabled; - } - - break; - - case TriggerFrequency.Once: - { - iTrigger = iTaskDefinition.Triggers.Create(_TASK_TRIGGER_TYPE2.TASK_TRIGGER_TIME); - ITimeTrigger iTimeTrigger = iTrigger as ITimeTrigger; - Debug.Assert(iTimeTrigger != null); - - iTimeTrigger.RandomDelay = ConvertTimeSpanToWTSString(jobTrigger.RandomDelay); - - // Time trigger repetition. - if (jobTrigger.RepetitionInterval != null && - jobTrigger.RepetitionDuration != null) - { - iTimeTrigger.Repetition.Interval = ConvertTimeSpanToWTSString(jobTrigger.RepetitionInterval.Value); - if (jobTrigger.RepetitionDuration.Value == TimeSpan.MaxValue) - { - iTimeTrigger.Repetition.StopAtDurationEnd = false; - } - else - { - iTimeTrigger.Repetition.StopAtDurationEnd = true; - iTimeTrigger.Repetition.Duration = ConvertTimeSpanToWTSString(jobTrigger.RepetitionDuration.Value); - } - } - - iTrigger.StartBoundary = ConvertDateTimeToString(jobTrigger.At); - iTrigger.Id = jobTrigger.Id.ToString(CultureInfo.InvariantCulture); - iTrigger.Enabled = jobTrigger.Enabled; - } - - break; - - case TriggerFrequency.Daily: - { - iTrigger = iTaskDefinition.Triggers.Create(_TASK_TRIGGER_TYPE2.TASK_TRIGGER_DAILY); - IDailyTrigger iDailyTrigger = iTrigger as IDailyTrigger; - Debug.Assert(iDailyTrigger != null); - - iDailyTrigger.RandomDelay = ConvertTimeSpanToWTSString(jobTrigger.RandomDelay); - iDailyTrigger.DaysInterval = (short)jobTrigger.Interval; - - iTrigger.StartBoundary = ConvertDateTimeToString(jobTrigger.At); - iTrigger.Id = jobTrigger.Id.ToString(CultureInfo.InvariantCulture); - iTrigger.Enabled = jobTrigger.Enabled; - } - - break; - - case TriggerFrequency.Weekly: - { - iTrigger = iTaskDefinition.Triggers.Create(_TASK_TRIGGER_TYPE2.TASK_TRIGGER_WEEKLY); - IWeeklyTrigger iWeeklyTrigger = iTrigger as IWeeklyTrigger; - Debug.Assert(iWeeklyTrigger != null); - - iWeeklyTrigger.RandomDelay = ConvertTimeSpanToWTSString(jobTrigger.RandomDelay); - iWeeklyTrigger.WeeksInterval = (short)jobTrigger.Interval; - iWeeklyTrigger.DaysOfWeek = ConvertDaysOfWeekToMask(jobTrigger.DaysOfWeek); - - iTrigger.StartBoundary = ConvertDateTimeToString(jobTrigger.At); - iTrigger.Id = jobTrigger.Id.ToString(CultureInfo.InvariantCulture); - iTrigger.Enabled = jobTrigger.Enabled; - } - - break; - } - } - - /// - /// Creates a ScheduledJobTrigger object based on a provided WTS ITrigger. - /// - /// ITrigger. - /// ScheduledJobTrigger. - private ScheduledJobTrigger CreateJobTrigger( - ITrigger iTrigger) - { - ScheduledJobTrigger rtnJobTrigger = null; - - if (iTrigger is IBootTrigger) - { - IBootTrigger iBootTrigger = (IBootTrigger)iTrigger; - rtnJobTrigger = ScheduledJobTrigger.CreateAtStartupTrigger( - ParseWTSTime(iBootTrigger.Delay), - ConvertStringId(iBootTrigger.Id), - iBootTrigger.Enabled); - } - else if (iTrigger is ILogonTrigger) - { - ILogonTrigger iLogonTrigger = (ILogonTrigger)iTrigger; - rtnJobTrigger = ScheduledJobTrigger.CreateAtLogOnTrigger( - iLogonTrigger.UserId, - ParseWTSTime(iLogonTrigger.Delay), - ConvertStringId(iLogonTrigger.Id), - iLogonTrigger.Enabled); - } - else if (iTrigger is ITimeTrigger) - { - ITimeTrigger iTimeTrigger = (ITimeTrigger)iTrigger; - TimeSpan repInterval = ParseWTSTime(iTimeTrigger.Repetition.Interval); - TimeSpan repDuration = (repInterval != TimeSpan.Zero && iTimeTrigger.Repetition.StopAtDurationEnd == false) ? - TimeSpan.MaxValue : ParseWTSTime(iTimeTrigger.Repetition.Duration); - rtnJobTrigger = ScheduledJobTrigger.CreateOnceTrigger( - DateTime.Parse(iTimeTrigger.StartBoundary, CultureInfo.InvariantCulture), - ParseWTSTime(iTimeTrigger.RandomDelay), - repInterval, - repDuration, - ConvertStringId(iTimeTrigger.Id), - iTimeTrigger.Enabled); - } - else if (iTrigger is IDailyTrigger) - { - IDailyTrigger iDailyTrigger = (IDailyTrigger)iTrigger; - rtnJobTrigger = ScheduledJobTrigger.CreateDailyTrigger( - DateTime.Parse(iDailyTrigger.StartBoundary, CultureInfo.InvariantCulture), - (Int32)iDailyTrigger.DaysInterval, - ParseWTSTime(iDailyTrigger.RandomDelay), - ConvertStringId(iDailyTrigger.Id), - iDailyTrigger.Enabled); - } - else if (iTrigger is IWeeklyTrigger) - { - IWeeklyTrigger iWeeklyTrigger = (IWeeklyTrigger)iTrigger; - rtnJobTrigger = ScheduledJobTrigger.CreateWeeklyTrigger( - DateTime.Parse(iWeeklyTrigger.StartBoundary, CultureInfo.InvariantCulture), - (Int32)iWeeklyTrigger.WeeksInterval, - ConvertMaskToDaysOfWeekArray(iWeeklyTrigger.DaysOfWeek), - ParseWTSTime(iWeeklyTrigger.RandomDelay), - ConvertStringId(iWeeklyTrigger.Id), - iWeeklyTrigger.Enabled); - } - - return rtnJobTrigger; - } - - private void AddTaskOptions( - ITaskDefinition iTaskDefinition, - ScheduledJobOptions jobOptions) - { - iTaskDefinition.Settings.DisallowStartIfOnBatteries = !jobOptions.StartIfOnBatteries; - iTaskDefinition.Settings.StopIfGoingOnBatteries = jobOptions.StopIfGoingOnBatteries; - iTaskDefinition.Settings.WakeToRun = jobOptions.WakeToRun; - iTaskDefinition.Settings.RunOnlyIfIdle = !jobOptions.StartIfNotIdle; - iTaskDefinition.Settings.IdleSettings.StopOnIdleEnd = jobOptions.StopIfGoingOffIdle; - iTaskDefinition.Settings.IdleSettings.RestartOnIdle = jobOptions.RestartOnIdleResume; - iTaskDefinition.Settings.IdleSettings.IdleDuration = ConvertTimeSpanToWTSString(jobOptions.IdleDuration); - iTaskDefinition.Settings.IdleSettings.WaitTimeout = ConvertTimeSpanToWTSString(jobOptions.IdleTimeout); - iTaskDefinition.Settings.Hidden = !jobOptions.ShowInTaskScheduler; - iTaskDefinition.Settings.RunOnlyIfNetworkAvailable = !jobOptions.RunWithoutNetwork; - iTaskDefinition.Settings.AllowDemandStart = !jobOptions.DoNotAllowDemandStart; - iTaskDefinition.Settings.MultipleInstances = ConvertFromMultiInstances(jobOptions.MultipleInstancePolicy); - iTaskDefinition.Principal.RunLevel = (jobOptions.RunElevated) ? - _TASK_RUNLEVEL.TASK_RUNLEVEL_HIGHEST : _TASK_RUNLEVEL.TASK_RUNLEVEL_LUA; - } - - private ScheduledJobOptions CreateJobOptions( - ITaskDefinition iTaskDefinition) - { - ITaskSettings iTaskSettings = iTaskDefinition.Settings; - IPrincipal iPrincipal = iTaskDefinition.Principal; - - return new ScheduledJobOptions( - !iTaskSettings.DisallowStartIfOnBatteries, - iTaskSettings.StopIfGoingOnBatteries, - iTaskSettings.WakeToRun, - !iTaskSettings.RunOnlyIfIdle, - iTaskSettings.IdleSettings.StopOnIdleEnd, - iTaskSettings.IdleSettings.RestartOnIdle, - ParseWTSTime(iTaskSettings.IdleSettings.IdleDuration), - ParseWTSTime(iTaskSettings.IdleSettings.WaitTimeout), - !iTaskSettings.Hidden, - iPrincipal.RunLevel == _TASK_RUNLEVEL.TASK_RUNLEVEL_HIGHEST, - !iTaskSettings.RunOnlyIfNetworkAvailable, - !iTaskSettings.AllowDemandStart, - ConvertToMultiInstances(iTaskSettings)); - } - - private void AddTaskAction( - ITaskDefinition iTaskDefinition, - ScheduledJobDefinition definition) - { - IExecAction iExecAction = iTaskDefinition.Actions.Create(_TASK_ACTION_TYPE.TASK_ACTION_EXEC) as IExecAction; - Debug.Assert(iExecAction != null); - - iExecAction.Id = ScheduledJobTaskActionId; - iExecAction.Path = definition.PSExecutionPath; - iExecAction.Arguments = definition.PSExecutionArgs; - } - - /// - /// Gets and returns the unsecured password for the provided - /// PSCredential object. - /// - /// PSCredential. - /// Unsecured password string. - private string GetCredentialPassword(PSCredential credential) - { - if (credential == null) - { - return null; - } - - IntPtr unmanagedString = IntPtr.Zero; - try - { - unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(credential.Password); - return Marshal.PtrToStringUni(unmanagedString); - } - finally - { - Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString); - } - } - - #endregion - - #region Private Utility Methods - - /// - /// Gets the Task Scheduler root folder for Scheduled Jobs or - /// creates it if it does not exist. - /// - /// Scheduled Jobs root folder. - private ITaskFolder GetRootFolder() - { - ITaskFolder iTaskRootFolder = null; - - try - { - iTaskRootFolder = _taskScheduler.GetFolder(ScheduledJobTasksRootFolder); - } - catch (System.IO.DirectoryNotFoundException) - { - } - catch (System.IO.FileNotFoundException) - { - // This can be thrown if COM interop tries to load the Microsoft.PowerShell.ScheduledJob - // assembly again. - } - - if (iTaskRootFolder == null) - { - // Create the PowerShell Scheduled Job root folder. - ITaskFolder iTSWindowsFolder = _taskScheduler.GetFolder(TaskSchedulerWindowsFolder); - iTaskRootFolder = iTSWindowsFolder.CreateFolder(ScheduledJobSubFolder); - } - - return iTaskRootFolder; - } - - /// - /// Finds a task with the provided Task Id and returns it as - /// a ITaskDefinition object. - /// - /// Task Id. - /// ITaskDefinition. - private ITaskDefinition FindTask(string taskId) - { - try - { - ITaskFolder iTaskFolder = _taskScheduler.GetFolder(ScheduledJobTasksRootFolder); - IRegisteredTask iRegisteredTask = iTaskFolder.GetTask(taskId); - return iRegisteredTask.Definition; - } - catch (System.IO.DirectoryNotFoundException e) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.CannotFindTaskId, taskId); - throw new ScheduledJobException(msg, e); - } - } - - private Int32 ConvertStringId(string triggerId) - { - Int32 triggerIdVal = 0; - - try - { - triggerIdVal = Convert.ToInt32(triggerId); - } - catch (FormatException) - { } - catch (OverflowException) - { } - - return triggerIdVal; - } - - /// - /// Helper method to parse a WTS time string and return - /// a corresponding TimeSpan object. Note that the - /// year and month values are ignored. - /// Format: - /// "PnYnMnDTnHnMnS" - /// "P" - Date separator - /// "nY" - year value. - /// "nM" - month value. - /// "nD" - day value. - /// "T" - Time separator - /// "nH" - hour value. - /// "nM" - minute value. - /// "nS" - second value. - /// - /// Formatted time string. - /// TimeSpan. - private TimeSpan ParseWTSTime(string wtsTime) - { - if (string.IsNullOrEmpty(wtsTime)) - { - return new TimeSpan(0); - } - - int days = 0; - int hours = 0; - int minutes = 0; - int seconds = 0; - int indx = 0; - int length = wtsTime.Length; - StringBuilder str = new StringBuilder(); - - try - { - while (indx != length) - { - char c = wtsTime[indx++]; - - switch (c) - { - case 'P': - str.Clear(); - while (indx != length && - wtsTime[indx] != 'T') - { - char c2 = wtsTime[indx++]; - if (c2 == 'Y') - { - // Ignore year value. - str.Clear(); - } - else if (c2 == 'M') - { - // Ignore month value. - str.Clear(); - } - else if (c2 == 'D') - { - days = Convert.ToInt32(str.ToString(), CultureInfo.InvariantCulture); - str.Clear(); - } - else if (c2 >= '0' && c2 <= '9') - { - str.Append(c2); - } - } - - break; - - case 'T': - str.Clear(); - while (indx != length && - wtsTime[indx] != 'P') - { - char c2 = wtsTime[indx++]; - if (c2 == 'H') - { - hours = Convert.ToInt32(str.ToString(), CultureInfo.InvariantCulture); - str.Clear(); - } - else if (c2 == 'M') - { - minutes = Convert.ToInt32(str.ToString(), CultureInfo.InvariantCulture); - str.Clear(); - } - else if (c2 == 'S') - { - seconds = Convert.ToInt32(str.ToString(), CultureInfo.InvariantCulture); - str.Clear(); - } - else if (c2 >= '0' && c2 <= '9') - { - str.Append(c2); - } - } - - break; - } - } - } - catch (FormatException) - { } - catch (OverflowException) - { } - - return new TimeSpan(days, hours, minutes, seconds); - } - - /// - /// Creates WTS formatted time string based on TimeSpan parameter. - /// - /// TimeSpan. - /// WTS time string. - internal static string ConvertTimeSpanToWTSString(TimeSpan time) - { - return string.Format( - CultureInfo.InvariantCulture, - "P{0}DT{1}H{2}M{3}S", - time.Days, - time.Hours, - time.Minutes, - time.Seconds); - } - - /// - /// Converts DateTime to string for WTS. - /// - /// DateTime. - /// DateTime string. - internal static string ConvertDateTimeToString(DateTime? dt) - { - if (dt == null) - { - return string.Empty; - } - else - { - return dt.Value.ToString("s", CultureInfo.InvariantCulture); - } - } - - /// - /// Returns a bitmask representing days of week as - /// required by Windows Task Scheduler API. - /// - /// Array of DayOfWeek. - /// WTS days of week mask. - internal static short ConvertDaysOfWeekToMask(IEnumerable daysOfWeek) - { - short rtnValue = 0; - foreach (DayOfWeek day in daysOfWeek) - { - switch (day) - { - case DayOfWeek.Sunday: - rtnValue |= WTSSunday; - break; - - case DayOfWeek.Monday: - rtnValue |= WTSMonday; - break; - - case DayOfWeek.Tuesday: - rtnValue |= WTSTuesday; - break; - - case DayOfWeek.Wednesday: - rtnValue |= WTSWednesday; - break; - - case DayOfWeek.Thursday: - rtnValue |= WTSThursday; - break; - - case DayOfWeek.Friday: - rtnValue |= WTSFriday; - break; - - case DayOfWeek.Saturday: - rtnValue |= WTSSaturday; - break; - } - } - - return rtnValue; - } - - /// - /// Converts WTS days of week mask to an array of DayOfWeek type. - /// - /// WTS days of week mask. - /// Days of week as List. - private List ConvertMaskToDaysOfWeekArray(short mask) - { - List daysOfWeek = new List(); - - if ((mask & WTSSunday) != 0) { daysOfWeek.Add(DayOfWeek.Sunday); } - - if ((mask & WTSMonday) != 0) { daysOfWeek.Add(DayOfWeek.Monday); } - - if ((mask & WTSTuesday) != 0) { daysOfWeek.Add(DayOfWeek.Tuesday); } - - if ((mask & WTSWednesday) != 0) { daysOfWeek.Add(DayOfWeek.Wednesday); } - - if ((mask & WTSThursday) != 0) { daysOfWeek.Add(DayOfWeek.Thursday); } - - if ((mask & WTSFriday) != 0) { daysOfWeek.Add(DayOfWeek.Friday); } - - if ((mask & WTSSaturday) != 0) { daysOfWeek.Add(DayOfWeek.Saturday); } - - return daysOfWeek; - } - - private TaskMultipleInstancePolicy ConvertToMultiInstances( - ITaskSettings iTaskSettings) - { - switch (iTaskSettings.MultipleInstances) - { - case _TASK_INSTANCES_POLICY.TASK_INSTANCES_IGNORE_NEW: - return TaskMultipleInstancePolicy.IgnoreNew; - - case _TASK_INSTANCES_POLICY.TASK_INSTANCES_PARALLEL: - return TaskMultipleInstancePolicy.Parallel; - - case _TASK_INSTANCES_POLICY.TASK_INSTANCES_QUEUE: - return TaskMultipleInstancePolicy.Queue; - - case _TASK_INSTANCES_POLICY.TASK_INSTANCES_STOP_EXISTING: - return TaskMultipleInstancePolicy.StopExisting; - } - - Debug.Assert(false); - return TaskMultipleInstancePolicy.None; - } - - private _TASK_INSTANCES_POLICY ConvertFromMultiInstances( - TaskMultipleInstancePolicy jobPolicies) - { - switch (jobPolicies) - { - case TaskMultipleInstancePolicy.IgnoreNew: - return _TASK_INSTANCES_POLICY.TASK_INSTANCES_IGNORE_NEW; - - case TaskMultipleInstancePolicy.Parallel: - return _TASK_INSTANCES_POLICY.TASK_INSTANCES_PARALLEL; - - case TaskMultipleInstancePolicy.Queue: - return _TASK_INSTANCES_POLICY.TASK_INSTANCES_QUEUE; - - case TaskMultipleInstancePolicy.StopExisting: - return _TASK_INSTANCES_POLICY.TASK_INSTANCES_STOP_EXISTING; - - default: - return _TASK_INSTANCES_POLICY.TASK_INSTANCES_IGNORE_NEW; - } - } - - #endregion - - #region IDisposable - - /// - /// Dispose. - /// - public void Dispose() - { - // Release reference to Task Scheduler object so that the COM - // object can be released. - _iRootFolder = null; - _taskScheduler = null; - - GC.SuppressFinalize(this); - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/AddJobTrigger.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/AddJobTrigger.cs deleted file mode 100644 index 2936fcf78c6..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/AddJobTrigger.cs +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Management.Automation.Internal; -using System.Threading; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This cmdlet adds ScheduledJobTriggers to ScheduledJobDefinition objects. - /// - [Cmdlet(VerbsCommon.Add, "JobTrigger", DefaultParameterSetName = AddJobTriggerCommand.JobDefinitionParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=223913")] - public sealed class AddJobTriggerCommand : ScheduleJobCmdletBase - { - #region Parameters - - private const string JobDefinitionParameterSet = "JobDefinition"; - private const string JobDefinitionIdParameterSet = "JobDefinitionId"; - private const string JobDefinitionNameParameterSet = "JobDefinitionName"; - - /// - /// ScheduledJobTrigger. - /// - [Parameter(Position = 1, Mandatory = true, ValueFromPipeline = true, - ParameterSetName = AddJobTriggerCommand.JobDefinitionParameterSet)] - [Parameter(Position = 1, Mandatory = true, ValueFromPipeline = true, - ParameterSetName = AddJobTriggerCommand.JobDefinitionIdParameterSet)] - [Parameter(Position = 1, Mandatory = true, ValueFromPipeline = true, - ParameterSetName = AddJobTriggerCommand.JobDefinitionNameParameterSet)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public ScheduledJobTrigger[] Trigger - { - get { return _triggers; } - - set { _triggers = value; } - } - - private ScheduledJobTrigger[] _triggers; - - /// - /// ScheduledJobDefinition Id. - /// - [Parameter(Position = 0, Mandatory = true, - ParameterSetName = AddJobTriggerCommand.JobDefinitionIdParameterSet)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public Int32[] Id - { - get { return _ids; } - - set { _ids = value; } - } - - private Int32[] _ids; - - /// - /// ScheduledJobDefinition Name. - /// - [Parameter(Position = 0, Mandatory = true, - ParameterSetName = AddJobTriggerCommand.JobDefinitionNameParameterSet)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public string[] Name - { - get { return _names; } - - set { _names = value; } - } - - private string[] _names; - - /// - /// ScheduledJobDefinition. - /// - [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, - ParameterSetName = AddJobTriggerCommand.JobDefinitionParameterSet)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public ScheduledJobDefinition[] InputObject - { - get { return _definitions; } - - set { _definitions = value; } - } - - private ScheduledJobDefinition[] _definitions; - - #endregion - - #region Cmdlet Overrides - - /// - /// Process input. - /// - protected override void ProcessRecord() - { - switch (ParameterSetName) - { - case JobDefinitionParameterSet: - AddToJobDefinition(_definitions); - break; - - case JobDefinitionIdParameterSet: - AddToJobDefinition(GetJobDefinitionsById(_ids)); - break; - - case JobDefinitionNameParameterSet: - AddToJobDefinition(GetJobDefinitionsByName(_names)); - break; - } - } - - #endregion - - #region Private Methods - - private void AddToJobDefinition(IEnumerable jobDefinitions) - { - foreach (ScheduledJobDefinition definition in jobDefinitions) - { - try - { - definition.AddTriggers(_triggers, true); - } - catch (ScheduledJobException e) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.CantAddJobTriggersToDefinition, definition.Name); - Exception reason = new RuntimeException(msg, e); - ErrorRecord errorRecord = new ErrorRecord(reason, "CantAddJobTriggersToScheduledJobDefinition", ErrorCategory.InvalidOperation, definition); - WriteError(errorRecord); - } - } - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/DisableJobDefinition.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/DisableJobDefinition.cs deleted file mode 100644 index d76b1829d42..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/DisableJobDefinition.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Management.Automation; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This cmdlet disables the specified ScheduledJobDefinition. - /// - [Cmdlet(VerbsLifecycle.Disable, "ScheduledJob", SupportsShouldProcess = true, DefaultParameterSetName = DisableScheduledJobDefinitionBase.DefinitionParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=223927")] - [OutputType(typeof(ScheduledJobDefinition))] - public sealed class DisableScheduledJobCommand : DisableScheduledJobDefinitionBase - { - #region Properties - - /// - /// Returns true if scheduled job definition should be enabled, - /// false otherwise. - /// - protected override bool Enabled - { - get { return false; } - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/DisableJobDefinitionBase.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/DisableJobDefinitionBase.cs deleted file mode 100644 index d06020ed3d6..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/DisableJobDefinitionBase.cs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Management.Automation; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// Base class for the DisableScheduledJobCommand, EnableScheduledJobCommand cmdlets. - /// - public abstract class DisableScheduledJobDefinitionBase : ScheduleJobCmdletBase - { - #region Parameters - - /// - /// DefinitionIdParameterSet. - /// - protected const string DefinitionIdParameterSet = "DefinitionId"; - - /// - /// DefinitionNameParameterSet. - /// - protected const string DefinitionNameParameterSet = "DefinitionName"; - - /// - /// DefinitionParameterSet. - /// - protected const string DefinitionParameterSet = "Definition"; - - /// - /// ScheduledJobDefinition. - /// - [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, - ParameterSetName = DisableScheduledJobDefinitionBase.DefinitionParameterSet)] - [ValidateNotNull] - public ScheduledJobDefinition InputObject - { - get { return _definition; } - - set { _definition = value; } - } - - private ScheduledJobDefinition _definition; - - /// - /// ScheduledJobDefinition Id. - /// - [Parameter(Position = 0, Mandatory = true, - ParameterSetName = DisableScheduledJobDefinitionBase.DefinitionIdParameterSet)] - public Int32 Id - { - get { return _definitionId; } - - set { _definitionId = value; } - } - - private Int32 _definitionId; - - /// - /// ScheduledJobDefinition Name. - /// - [Parameter(Position = 0, Mandatory = true, - ParameterSetName = DisableScheduledJobDefinitionBase.DefinitionNameParameterSet)] - [ValidateNotNullOrEmpty] - public string Name - { - get { return _definitionName; } - - set { _definitionName = value; } - } - - private string _definitionName; - - /// - /// Pass through ScheduledJobDefinition object. - /// - [Parameter(ParameterSetName = DisableScheduledJobDefinitionBase.DefinitionParameterSet)] - [Parameter(ParameterSetName = DisableScheduledJobDefinitionBase.DefinitionIdParameterSet)] - [Parameter(ParameterSetName = DisableScheduledJobDefinitionBase.DefinitionNameParameterSet)] - public SwitchParameter PassThru - { - get { return _passThru; } - - set { _passThru = value; } - } - - private SwitchParameter _passThru; - - #endregion - - #region Cmdlet Overrides - - /// - /// Process input. - /// - protected override void ProcessRecord() - { - ScheduledJobDefinition definition = null; - - switch (ParameterSetName) - { - case DefinitionParameterSet: - definition = _definition; - break; - - case DefinitionIdParameterSet: - definition = GetJobDefinitionById(_definitionId); - break; - - case DefinitionNameParameterSet: - definition = GetJobDefinitionByName(_definitionName); - break; - } - - string verbName = Enabled ? VerbsLifecycle.Enable : VerbsLifecycle.Disable; - - if (definition != null && - ShouldProcess(definition.Name, verbName)) - { - try - { - definition.SetEnabled(Enabled, true); - } - catch (ScheduledJobException e) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.CantSetEnableOnJobDefinition, definition.Name); - Exception reason = new RuntimeException(msg, e); - ErrorRecord errorRecord = new ErrorRecord(reason, "CantSetEnableOnScheduledJobDefinition", ErrorCategory.InvalidOperation, definition); - WriteError(errorRecord); - } - - if (_passThru) - { - WriteObject(definition); - } - } - } - - #endregion - - #region Properties - - /// - /// Returns true if scheduled job definition should be enabled, - /// false otherwise. - /// - protected abstract bool Enabled - { - get; - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/DisableJobTrigger.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/DisableJobTrigger.cs deleted file mode 100644 index 02e37c0acfb..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/DisableJobTrigger.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Management.Automation.Internal; -using System.Threading; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This cmdlet enables triggers on a ScheduledJobDefinition object. - /// - [Cmdlet(VerbsLifecycle.Disable, "JobTrigger", SupportsShouldProcess = true, DefaultParameterSetName = DisableJobTriggerCommand.EnabledParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=223918")] - public sealed class DisableJobTriggerCommand : EnableDisableScheduledJobCmdletBase - { - #region Enabled Implementation - - /// - /// Property to determine if trigger should be enabled or disabled. - /// - internal override bool Enabled - { - get { return false; } - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/EnableDisableCmdletBase.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/EnableDisableCmdletBase.cs deleted file mode 100644 index c00626e6d64..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/EnableDisableCmdletBase.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Management.Automation.Internal; -using System.Threading; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// Base class for DisableJobTrigger, EnableJobTrigger cmdlets. - /// - public abstract class EnableDisableScheduledJobCmdletBase : ScheduleJobCmdletBase - { - #region Parameters - - /// - /// JobDefinition parameter set. - /// - protected const string EnabledParameterSet = "JobEnabled"; - - /// - /// ScheduledJobTrigger objects to set properties on. - /// - [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, - ParameterSetName = EnableDisableScheduledJobCmdletBase.EnabledParameterSet)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public ScheduledJobTrigger[] InputObject - { - get { return _triggers; } - - set { _triggers = value; } - } - - /// - /// Pass through for scheduledjobtrigger object. - /// - [Parameter(ParameterSetName = EnableDisableScheduledJobCmdletBase.EnabledParameterSet)] - public SwitchParameter PassThru - { - get { return _passThru; } - - set { _passThru = value; } - } - - private SwitchParameter _passThru; - - private ScheduledJobTrigger[] _triggers; - - #endregion - - #region Cmdlet Overrides - - /// - /// Process input. - /// - protected override void ProcessRecord() - { - // Update each trigger with the current enabled state. - foreach (ScheduledJobTrigger trigger in _triggers) - { - trigger.Enabled = Enabled; - if (trigger.JobDefinition != null) - { - trigger.UpdateJobDefinition(); - } - - if (_passThru) - { - WriteObject(trigger); - } - } - } - - #endregion - - #region Internal Properties - - /// - /// Property to determine if trigger should be enabled or disabled. - /// - internal abstract bool Enabled - { - get; - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/EnableJobDefinition.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/EnableJobDefinition.cs deleted file mode 100644 index 6f236ea57af..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/EnableJobDefinition.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Management.Automation; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This cmdlet enables the specified ScheduledJobDefinition. - /// - [Cmdlet(VerbsLifecycle.Enable, "ScheduledJob", SupportsShouldProcess = true, DefaultParameterSetName = DisableScheduledJobDefinitionBase.DefinitionParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=223926")] - [OutputType(typeof(ScheduledJobDefinition))] - public sealed class EnableScheduledJobCommand : DisableScheduledJobDefinitionBase - { - #region Properties - - /// - /// Returns true if scheduled job definition should be enabled, - /// false otherwise. - /// - protected override bool Enabled - { - get { return true; } - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/EnableJobTrigger.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/EnableJobTrigger.cs deleted file mode 100644 index 955dde31dfe..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/EnableJobTrigger.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Management.Automation.Internal; -using System.Threading; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This cmdlet disables triggers on a ScheduledJobDefinition object. - /// - [Cmdlet(VerbsLifecycle.Enable, "JobTrigger", SupportsShouldProcess = true, DefaultParameterSetName = EnableJobTriggerCommand.EnabledParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=223917")] - public sealed class EnableJobTriggerCommand : EnableDisableScheduledJobCmdletBase - { - #region Enabled Implementation - - /// - /// Property to determine if trigger should be enabled or disabled. - /// - internal override bool Enabled - { - get { return true; } - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/GetJobDefinition.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/GetJobDefinition.cs deleted file mode 100644 index 772027f4fa5..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/GetJobDefinition.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This cmdlet gets scheduled job definition objects from the local repository. - /// - [Cmdlet(VerbsCommon.Get, "ScheduledJob", DefaultParameterSetName = GetScheduledJobCommand.DefinitionIdParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=223923")] - [OutputType(typeof(ScheduledJobDefinition))] - public sealed class GetScheduledJobCommand : ScheduleJobCmdletBase - { - #region Parameters - - private const string DefinitionIdParameterSet = "DefinitionId"; - private const string DefinitionNameParameterSet = "DefinitionName"; - - /// - /// ScheduledJobDefinition Id. - /// - [Parameter(Position = 0, - ParameterSetName = GetScheduledJobCommand.DefinitionIdParameterSet)] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public Int32[] Id - { - get { return _definitionIds; } - - set { _definitionIds = value; } - } - - private Int32[] _definitionIds; - - /// - /// ScheduledJobDefinition Name. - /// - [Parameter(Position = 0, Mandatory = true, - ParameterSetName = GetScheduledJobCommand.DefinitionNameParameterSet)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public string[] Name - { - get { return _definitionNames; } - - set { _definitionNames = value; } - } - - private string[] _definitionNames; - - #endregion - - #region Cmdlet Overrides - - /// - /// Process input. - /// - protected override void ProcessRecord() - { - switch (ParameterSetName) - { - case DefinitionIdParameterSet: - if (_definitionIds == null) - { - FindAllJobDefinitions( - (definition) => - { - WriteObject(definition); - }); - } - else - { - FindJobDefinitionsById( - _definitionIds, - (definition) => - { - WriteObject(definition); - }); - } - - break; - - case DefinitionNameParameterSet: - FindJobDefinitionsByName( - _definitionNames, - (definition) => - { - WriteObject(definition); - }); - break; - } - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/GetJobTrigger.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/GetJobTrigger.cs deleted file mode 100644 index 0218395f3a1..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/GetJobTrigger.cs +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Management.Automation.Internal; -using System.Threading; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This cmdlet gets ScheduledJobTriggers for the specified ScheduledJobDefinition object. - /// - [Cmdlet(VerbsCommon.Get, "JobTrigger", DefaultParameterSetName = GetJobTriggerCommand.JobDefinitionParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=223915")] - [OutputType(typeof(ScheduledJobTrigger))] - public sealed class GetJobTriggerCommand : ScheduleJobCmdletBase - { - #region Parameters - - private const string JobDefinitionParameterSet = "JobDefinition"; - private const string JobDefinitionIdParameterSet = "JobDefinitionId"; - private const string JobDefinitionNameParameterSet = "JobDefinitionName"; - - /// - /// Trigger number to get. - /// - [Parameter(Position = 1, - ParameterSetName = GetJobTriggerCommand.JobDefinitionParameterSet)] - [Parameter(Position = 1, - ParameterSetName = GetJobTriggerCommand.JobDefinitionIdParameterSet)] - [Parameter(Position = 1, - ParameterSetName = GetJobTriggerCommand.JobDefinitionNameParameterSet)] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public Int32[] TriggerId - { - get { return _triggerIds; } - - set { _triggerIds = value; } - } - - private Int32[] _triggerIds; - - /// - /// ScheduledJobDefinition. - /// - [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, - ParameterSetName = GetJobTriggerCommand.JobDefinitionParameterSet)] - [ValidateNotNull] - public ScheduledJobDefinition InputObject - { - get { return _definition; } - - set { _definition = value; } - } - - private ScheduledJobDefinition _definition; - - /// - /// ScheduledJobDefinition Id. - /// - [Parameter(Position = 0, Mandatory = true, - ParameterSetName = GetJobTriggerCommand.JobDefinitionIdParameterSet)] - public Int32 Id - { - get { return _definitionId; } - - set { _definitionId = value; } - } - - private Int32 _definitionId; - - /// - /// ScheduledJobDefinition Name. - /// - [Parameter(Position = 0, Mandatory = true, - ParameterSetName = GetJobTriggerCommand.JobDefinitionNameParameterSet)] - [ValidateNotNullOrEmpty] - public string Name - { - get { return _name; } - - set { _name = value; } - } - - private string _name; - - #endregion - - #region Cmdlet Overrides - - /// - /// Process input. - /// - protected override void ProcessRecord() - { - switch (ParameterSetName) - { - case JobDefinitionParameterSet: - WriteTriggers(_definition); - break; - - case JobDefinitionIdParameterSet: - WriteTriggers(GetJobDefinitionById(_definitionId)); - break; - - case JobDefinitionNameParameterSet: - WriteTriggers(GetJobDefinitionByName(_name)); - break; - } - } - - #endregion - - #region Private Methods - - private void WriteTriggers(ScheduledJobDefinition definition) - { - if (definition == null) - { - return; - } - - List notFoundIds; - List triggers = definition.GetTriggers(_triggerIds, out notFoundIds); - - // Write found trigger objects. - foreach (ScheduledJobTrigger trigger in triggers) - { - WriteObject(trigger); - } - - // Report any triggers that were not found. - foreach (Int32 notFoundId in notFoundIds) - { - WriteTriggerNotFoundError(notFoundId, definition.Name, definition); - } - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/GetScheduledJobOption.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/GetScheduledJobOption.cs deleted file mode 100644 index 4de3131b223..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/GetScheduledJobOption.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Management.Automation; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This cmdlet gets scheduled job option object from a provided ScheduledJobDefinition object. - /// - [Cmdlet(VerbsCommon.Get, "ScheduledJobOption", DefaultParameterSetName = GetScheduledJobOptionCommand.JobDefinitionParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=223920")] - [OutputType(typeof(ScheduledJobOptions))] - public sealed class GetScheduledJobOptionCommand : ScheduleJobCmdletBase - { - #region Parameters - - private const string JobDefinitionParameterSet = "JobDefinition"; - private const string JobDefinitionIdParameterSet = "JobDefinitionId"; - private const string JobDefinitionNameParameterSet = "JobDefinitionName"; - - /// - /// ScheduledJobDefinition Id. - /// - [Parameter(Position = 0, Mandatory = true, - ParameterSetName = GetScheduledJobOptionCommand.JobDefinitionIdParameterSet)] - public Int32 Id - { - get { return _id; } - - set { _id = value; } - } - - private Int32 _id; - - /// - /// ScheduledJobDefinition Name. - /// - [Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, - ParameterSetName = GetScheduledJobOptionCommand.JobDefinitionNameParameterSet)] - [ValidateNotNullOrEmpty] - public string Name - { - get { return _name; } - - set { _name = value; } - } - - private string _name; - - /// - /// ScheduledJobDefinition. - /// - [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, - ParameterSetName = GetScheduledJobOptionCommand.JobDefinitionParameterSet)] - [ValidateNotNull] - public ScheduledJobDefinition InputObject - { - get { return _definition; } - - set { _definition = value; } - } - - private ScheduledJobDefinition _definition; - - #endregion - - #region Cmdlet Overrides - - /// - /// Process input. - /// - protected override void ProcessRecord() - { - // Get ScheduledJobDefinition object. - ScheduledJobDefinition definition = null; - switch (ParameterSetName) - { - case JobDefinitionParameterSet: - definition = _definition; - break; - - case JobDefinitionIdParameterSet: - definition = GetJobDefinitionById(_id); - break; - - case JobDefinitionNameParameterSet: - definition = GetJobDefinitionByName(_name); - break; - } - - // Return options from the definition object. - if (definition != null) - { - WriteObject(definition.Options); - } - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/NewJobTrigger.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/NewJobTrigger.cs deleted file mode 100644 index 99ce575bec3..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/NewJobTrigger.cs +++ /dev/null @@ -1,360 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Management.Automation.Internal; -using System.Threading; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This cmdlet creates a new scheduled job trigger based on the provided - /// parameter values. - /// - [Cmdlet(VerbsCommon.New, "JobTrigger", DefaultParameterSetName = NewJobTriggerCommand.OnceParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=223912")] - [OutputType(typeof(ScheduledJobTrigger))] - public sealed class NewJobTriggerCommand : ScheduleJobCmdletBase - { - #region Parameters - - private const string AtLogonParameterSet = "AtLogon"; - private const string AtStartupParameterSet = "AtStartup"; - private const string OnceParameterSet = "Once"; - private const string DailyParameterSet = "Daily"; - private const string WeeklyParameterSet = "Weekly"; - - /// - /// Daily interval for trigger. - /// - [Parameter(ParameterSetName = NewJobTriggerCommand.DailyParameterSet)] - public Int32 DaysInterval - { - get { return _daysInterval; } - - set { _daysInterval = value; } - } - - private Int32 _daysInterval = 1; - - /// - /// Weekly interval for trigger. - /// - [Parameter(ParameterSetName = NewJobTriggerCommand.WeeklyParameterSet)] - public Int32 WeeksInterval - { - get { return _weeksInterval; } - - set { _weeksInterval = value; } - } - - private Int32 _weeksInterval = 1; - - /// - /// Random delay for trigger. - /// - [Parameter(ParameterSetName = NewJobTriggerCommand.AtLogonParameterSet)] - [Parameter(ParameterSetName = NewJobTriggerCommand.AtStartupParameterSet)] - [Parameter(ParameterSetName = NewJobTriggerCommand.OnceParameterSet)] - [Parameter(ParameterSetName = NewJobTriggerCommand.DailyParameterSet)] - [Parameter(ParameterSetName = NewJobTriggerCommand.WeeklyParameterSet)] - public TimeSpan RandomDelay - { - get { return _randomDelay; } - - set { _randomDelay = value; } - } - - private TimeSpan _randomDelay; - - /// - /// Job start date/time for trigger. - /// - [Parameter(Mandatory = true, - ParameterSetName = NewJobTriggerCommand.OnceParameterSet)] - [Parameter(Mandatory = true, - ParameterSetName = NewJobTriggerCommand.DailyParameterSet)] - [Parameter(Mandatory = true, - ParameterSetName = NewJobTriggerCommand.WeeklyParameterSet)] - public DateTime At - { - get { return _atTime; } - - set { _atTime = value; } - } - - private DateTime _atTime; - - /// - /// User name for AtLogon trigger. User name is used to determine which user - /// log on causes the trigger to activate. - /// - [Parameter(ParameterSetName = NewJobTriggerCommand.AtLogonParameterSet)] - [ValidateNotNullOrEmpty] - public string User - { - get { return _user; } - - set { _user = value; } - } - - private string _user; - - /// - /// Days of week for trigger applies only to the Weekly parameter set. - /// Specifies which day(s) of the week the weekly trigger is activated. - /// - [Parameter(Mandatory = true, ParameterSetName = NewJobTriggerCommand.WeeklyParameterSet)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public DayOfWeek[] DaysOfWeek - { - get { return _daysOfWeek; } - - set { _daysOfWeek = value; } - } - - private DayOfWeek[] _daysOfWeek; - - /// - /// Switch to specify an AtStartup trigger. - /// - [Parameter(Mandatory = true, Position = 0, - ParameterSetName = NewJobTriggerCommand.AtStartupParameterSet)] - public SwitchParameter AtStartup - { - get { return _atStartup; } - - set { _atStartup = value; } - } - - private SwitchParameter _atStartup; - - /// - /// Switch to specify an AtLogon trigger. - /// - [Parameter(Mandatory = true, Position = 0, - ParameterSetName = NewJobTriggerCommand.AtLogonParameterSet)] - public SwitchParameter AtLogOn - { - get { return _atLogon; } - - set { _atLogon = value; } - } - - private SwitchParameter _atLogon; - - /// - /// Switch to specify a Once (one time) trigger. - /// - [Parameter(Mandatory = true, Position = 0, - ParameterSetName = NewJobTriggerCommand.OnceParameterSet)] - public SwitchParameter Once - { - get { return _once; } - - set { _once = value; } - } - - private SwitchParameter _once; - - /// - /// Repetition interval of a one time trigger. - /// - [Parameter(ParameterSetName = NewJobTriggerCommand.OnceParameterSet)] - public TimeSpan RepetitionInterval - { - get { return _repInterval; } - - set { _repInterval = value; } - } - - private TimeSpan _repInterval; - - /// - /// Repetition duration of a one time trigger. - /// - [Parameter(ParameterSetName = NewJobTriggerCommand.OnceParameterSet)] - public TimeSpan RepetitionDuration - { - get { return _repDuration; } - - set { _repDuration = value; } - } - - private TimeSpan _repDuration; - - /// - /// Repetition interval repeats indefinitely. - /// - [Parameter(ParameterSetName = NewJobTriggerCommand.OnceParameterSet)] - public SwitchParameter RepeatIndefinitely - { - get { return _repRepeatIndefinitely; } - - set { _repRepeatIndefinitely = value; } - } - - private SwitchParameter _repRepeatIndefinitely; - - /// - /// Switch to specify a Daily trigger. - /// - [Parameter(Mandatory = true, Position = 0, - ParameterSetName = NewJobTriggerCommand.DailyParameterSet)] - public SwitchParameter Daily - { - get { return _daily; } - - set { _daily = value; } - } - - private SwitchParameter _daily; - - /// - /// Switch to specify a Weekly trigger. - /// - [Parameter(Mandatory = true, Position = 0, - ParameterSetName = NewJobTriggerCommand.WeeklyParameterSet)] - public SwitchParameter Weekly - { - get { return _weekly; } - - set { _weekly = value; } - } - - private SwitchParameter _weekly; - - #endregion - - #region Cmdlet Overrides - - /// - /// Do begin processing. - /// - protected override void BeginProcessing() - { - base.BeginProcessing(); - - // Validate parameters. - if (_daysInterval < 1) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidDaysIntervalParam); - } - - if (_weeksInterval < 1) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidWeeksIntervalParam); - } - } - - /// - /// Process input. - /// - protected override void ProcessRecord() - { - switch (ParameterSetName) - { - case AtLogonParameterSet: - CreateAtLogonTrigger(); - break; - - case AtStartupParameterSet: - CreateAtStartupTrigger(); - break; - - case OnceParameterSet: - CreateOnceTrigger(); - break; - - case DailyParameterSet: - CreateDailyTrigger(); - break; - - case WeeklyParameterSet: - CreateWeeklyTrigger(); - break; - } - } - - #endregion - - #region Private Methods - - private void CreateAtLogonTrigger() - { - WriteObject(ScheduledJobTrigger.CreateAtLogOnTrigger(_user, _randomDelay, 0, true)); - } - - private void CreateAtStartupTrigger() - { - WriteObject(ScheduledJobTrigger.CreateAtStartupTrigger(_randomDelay, 0, true)); - } - - private void CreateOnceTrigger() - { - TimeSpan? repInterval = null; - TimeSpan? repDuration = null; - if (MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionInterval)) || MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionDuration)) || - MyInvocation.BoundParameters.ContainsKey(nameof(RepeatIndefinitely))) - { - if (MyInvocation.BoundParameters.ContainsKey(nameof(RepeatIndefinitely))) - { - if (MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionDuration))) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidRepeatIndefinitelyParams); - } - - if (!MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionInterval))) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidRepetitionRepeatParams); - } - - _repDuration = TimeSpan.MaxValue; - } - else if (!MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionInterval)) || !MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionDuration))) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidRepetitionParams); - } - - if (_repInterval < TimeSpan.Zero || _repDuration < TimeSpan.Zero) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidRepetitionParamValues); - } - - if (_repInterval < TimeSpan.FromMinutes(1)) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidRepetitionIntervalValue); - } - - if (_repInterval > _repDuration) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidRepetitionInterval); - } - - repInterval = _repInterval; - repDuration = _repDuration; - } - - WriteObject(ScheduledJobTrigger.CreateOnceTrigger(_atTime, _randomDelay, repInterval, repDuration, 0, true)); - } - - private void CreateDailyTrigger() - { - WriteObject(ScheduledJobTrigger.CreateDailyTrigger(_atTime, _daysInterval, _randomDelay, 0, true)); - } - - private void CreateWeeklyTrigger() - { - WriteObject(ScheduledJobTrigger.CreateWeeklyTrigger(_atTime, _weeksInterval, _daysOfWeek, _randomDelay, 0, true)); - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/NewScheduledJobOption.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/NewScheduledJobOption.cs deleted file mode 100644 index 09eab549426..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/NewScheduledJobOption.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Management.Automation; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This cmdlet creates a new scheduled job option object based on the provided - /// parameter values. - /// - [Cmdlet(VerbsCommon.New, "ScheduledJobOption", DefaultParameterSetName = ScheduledJobOptionCmdletBase.OptionsParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=223919")] - [OutputType(typeof(ScheduledJobOptions))] - public sealed class NewScheduledJobOptionCommand : ScheduledJobOptionCmdletBase - { - #region Cmdlet Overrides - - /// - /// Process input. - /// - protected override void ProcessRecord() - { - WriteObject(new ScheduledJobOptions( - StartIfOnBattery, - !ContinueIfGoingOnBattery, - WakeToRun, - !StartIfIdle, - StopIfGoingOffIdle, - RestartOnIdleResume, - IdleDuration, - IdleTimeout, - !HideInTaskScheduler, - RunElevated, - !RequireNetwork, - DoNotAllowDemandStart, - MultipleInstancePolicy)); - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/RegisterJobDefinition.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/RegisterJobDefinition.cs deleted file mode 100644 index e473f91dfd4..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/RegisterJobDefinition.cs +++ /dev/null @@ -1,408 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; -using System.Management.Automation.Runspaces; - -using Microsoft.PowerShell.Commands; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This cmdlet creates a new scheduled job definition object based on the provided - /// parameter values and registers it with the Task Scheduler. - /// - [SuppressMessage("Microsoft.PowerShell", "PS1012:CallShouldProcessOnlyIfDeclaringSupport")] - [Cmdlet(VerbsLifecycle.Register, "ScheduledJob", SupportsShouldProcess = true, DefaultParameterSetName = RegisterScheduledJobCommand.ScriptBlockParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=223922")] - [OutputType(typeof(ScheduledJobDefinition))] - public sealed class RegisterScheduledJobCommand : ScheduleJobCmdletBase - { - #region Parameters - - private const string FilePathParameterSet = "FilePath"; - private const string ScriptBlockParameterSet = "ScriptBlock"; - - /// - /// File path for script to be run in job. - /// - [Parameter(Position = 1, Mandatory = true, - ParameterSetName = RegisterScheduledJobCommand.FilePathParameterSet)] - [Alias("Path")] - [ValidateNotNullOrEmpty] - public string FilePath - { - get { return _filePath; } - - set { _filePath = value; } - } - - private string _filePath; - - /// - /// ScriptBlock containing script to run in job. - /// - [Parameter(Position = 1, Mandatory = true, - ParameterSetName = RegisterScheduledJobCommand.ScriptBlockParameterSet)] - [ValidateNotNull] - public ScriptBlock ScriptBlock - { - get { return _scriptBlock; } - - set { _scriptBlock = value; } - } - - private ScriptBlock _scriptBlock; - - /// - /// Name of scheduled job definition. - /// - [Parameter(Position = 0, Mandatory = true, - ParameterSetName = RegisterScheduledJobCommand.FilePathParameterSet)] - [Parameter(Position = 0, Mandatory = true, - ParameterSetName = RegisterScheduledJobCommand.ScriptBlockParameterSet)] - [ValidateNotNullOrEmpty] - public string Name - { - get { return _name; } - - set { _name = value; } - } - - private string _name; - - /// - /// Triggers to define when job will run. - /// - [Parameter(ParameterSetName = RegisterScheduledJobCommand.FilePathParameterSet)] - [Parameter(ParameterSetName = RegisterScheduledJobCommand.ScriptBlockParameterSet)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public ScheduledJobTrigger[] Trigger - { - get { return _triggers; } - - set { _triggers = value; } - } - - private ScheduledJobTrigger[] _triggers; - - /// - /// Initialization script to run before the job starts. - /// - [Parameter(ParameterSetName = RegisterScheduledJobCommand.FilePathParameterSet)] - [Parameter(ParameterSetName = RegisterScheduledJobCommand.ScriptBlockParameterSet)] - [ValidateNotNull] - public ScriptBlock InitializationScript - { - get { return _initializationScript; } - - set { _initializationScript = value; } - } - - private ScriptBlock _initializationScript; - - /// - /// Runs the job in a 32-bit PowerShell process. - /// - [Parameter(ParameterSetName = RegisterScheduledJobCommand.FilePathParameterSet)] - [Parameter(ParameterSetName = RegisterScheduledJobCommand.ScriptBlockParameterSet)] - public SwitchParameter RunAs32 - { - get { return _runAs32; } - - set { _runAs32 = value; } - } - - private SwitchParameter _runAs32; - - /// - /// Credentials for job. - /// - [Parameter(ParameterSetName = RegisterScheduledJobCommand.FilePathParameterSet)] - [Parameter(ParameterSetName = RegisterScheduledJobCommand.ScriptBlockParameterSet)] - [Credential()] - public PSCredential Credential - { - get { return _credential; } - - set { _credential = value; } - } - - private PSCredential _credential; - - /// - /// Authentication mechanism to use for job. - /// - [Parameter(ParameterSetName = RegisterScheduledJobCommand.FilePathParameterSet)] - [Parameter(ParameterSetName = RegisterScheduledJobCommand.ScriptBlockParameterSet)] - public AuthenticationMechanism Authentication - { - get { return _authenticationMechanism; } - - set { _authenticationMechanism = value; } - } - - private AuthenticationMechanism _authenticationMechanism; - - /// - /// Scheduling options for job. - /// - [Parameter(ParameterSetName = RegisterScheduledJobCommand.FilePathParameterSet)] - [Parameter(ParameterSetName = RegisterScheduledJobCommand.ScriptBlockParameterSet)] - [ValidateNotNull] - public ScheduledJobOptions ScheduledJobOption - { - get { return _options; } - - set { _options = value; } - } - - private ScheduledJobOptions _options; - - /// - /// Argument list for FilePath parameter. - /// - [Parameter(ParameterSetName = RegisterScheduledJobCommand.FilePathParameterSet)] - [Parameter(ParameterSetName = RegisterScheduledJobCommand.ScriptBlockParameterSet)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public object[] ArgumentList - { - get { return _arguments; } - - set { _arguments = value; } - } - - private object[] _arguments; - - /// - /// Maximum number of job results allowed in job store. - /// - [Parameter(ParameterSetName = RegisterScheduledJobCommand.FilePathParameterSet)] - [Parameter(ParameterSetName = RegisterScheduledJobCommand.ScriptBlockParameterSet)] - public int MaxResultCount - { - get { return _executionHistoryLength; } - - set { _executionHistoryLength = value; } - } - - private int _executionHistoryLength; - - /// - /// Runs scheduled job immediately after successful registration. - /// - [Parameter(ParameterSetName = RegisterScheduledJobCommand.FilePathParameterSet)] - [Parameter(ParameterSetName = RegisterScheduledJobCommand.ScriptBlockParameterSet)] - public SwitchParameter RunNow - { - get { return _runNow; } - - set { _runNow = value; } - } - - private SwitchParameter _runNow; - - /// - /// Runs scheduled job at the repetition interval indicated by the - /// TimeSpan value for an unending duration. - /// - [Parameter(ParameterSetName = RegisterScheduledJobCommand.FilePathParameterSet)] - [Parameter(ParameterSetName = RegisterScheduledJobCommand.ScriptBlockParameterSet)] - public TimeSpan RunEvery - { - get { return _runEvery; } - - set { _runEvery = value; } - } - - private TimeSpan _runEvery; - - #endregion - - #region Cmdlet Overrides - - /// - /// Process input. - /// - protected override void ProcessRecord() - { - string targetString = StringUtil.Format(ScheduledJobErrorStrings.DefinitionWhatIf, Name); - if (!ShouldProcess(targetString, VerbsLifecycle.Register)) - { - return; - } - - ScheduledJobDefinition definition = null; - - switch (ParameterSetName) - { - case ScriptBlockParameterSet: - definition = CreateScriptBlockDefinition(); - break; - - case FilePathParameterSet: - definition = CreateFilePathDefinition(); - break; - } - - if (definition != null) - { - // Set the MaxCount value if available. - if (MyInvocation.BoundParameters.ContainsKey(nameof(MaxResultCount))) - { - if (MaxResultCount < 1) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidMaxResultCount); - Exception reason = new RuntimeException(msg); - ErrorRecord errorRecord = new ErrorRecord(reason, "InvalidMaxResultCountParameterForRegisterScheduledJobDefinition", ErrorCategory.InvalidArgument, null); - WriteError(errorRecord); - - return; - } - - definition.SetExecutionHistoryLength(MaxResultCount, false); - } - - try - { - // If RunEvery parameter is specified then create a job trigger for the definition that - // runs the job at the requested interval. - if (MyInvocation.BoundParameters.ContainsKey(nameof(RunEvery))) - { - AddRepetitionJobTriggerToDefinition( - definition, - RunEvery, - false); - } - - definition.Register(); - WriteObject(definition); - - if (_runNow) - { - definition.RunAsTask(); - } - } - catch (ScheduledJobException e) - { - // Check for access denied error. - if (e.InnerException != null && e.InnerException is System.UnauthorizedAccessException) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.UnauthorizedAccessError, definition.Name); - Exception reason = new RuntimeException(msg, e); - ErrorRecord errorRecord = new ErrorRecord(reason, "UnauthorizedAccessToRegisterScheduledJobDefinition", ErrorCategory.PermissionDenied, definition); - WriteError(errorRecord); - } - else if (e.InnerException != null && e.InnerException is System.IO.DirectoryNotFoundException) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.DirectoryNotFoundError, definition.Name); - Exception reason = new RuntimeException(msg, e); - ErrorRecord errorRecord = new ErrorRecord(reason, "DirectoryNotFoundWhenRegisteringScheduledJobDefinition", ErrorCategory.ObjectNotFound, definition); - WriteError(errorRecord); - } - else if (e.InnerException != null && e.InnerException is System.Runtime.Serialization.InvalidDataContractException) - { - string innerMsg = (!string.IsNullOrEmpty(e.InnerException.Message)) ? e.InnerException.Message : string.Empty; - string msg = StringUtil.Format(ScheduledJobErrorStrings.CannotSerializeData, definition.Name, innerMsg); - Exception reason = new RuntimeException(msg, e); - ErrorRecord errorRecord = new ErrorRecord(reason, "CannotSerializeDataWhenRegisteringScheduledJobDefinition", ErrorCategory.InvalidData, definition); - WriteError(errorRecord); - } - else - { - // Create record around known exception type. - ErrorRecord errorRecord = new ErrorRecord(e, "CantRegisterScheduledJobDefinition", ErrorCategory.InvalidOperation, definition); - WriteError(errorRecord); - } - } - } - } - - #endregion - - #region Private Methods - - private ScheduledJobDefinition CreateScriptBlockDefinition() - { - JobDefinition jobDefinition = new JobDefinition(typeof(ScheduledJobSourceAdapter), ScriptBlock.ToString(), _name); - jobDefinition.ModuleName = ModuleName; - Dictionary parameterCollection = CreateCommonParameters(); - - // ScriptBlock, mandatory - parameterCollection.Add(ScheduledJobInvocationInfo.ScriptBlockParameter, ScriptBlock); - - JobInvocationInfo jobInvocationInfo = new ScheduledJobInvocationInfo(jobDefinition, parameterCollection); - - ScheduledJobDefinition definition = new ScheduledJobDefinition(jobInvocationInfo, Trigger, - ScheduledJobOption, _credential); - - return definition; - } - - private ScheduledJobDefinition CreateFilePathDefinition() - { - JobDefinition jobDefinition = new JobDefinition(typeof(ScheduledJobSourceAdapter), FilePath, _name); - jobDefinition.ModuleName = ModuleName; - Dictionary parameterCollection = CreateCommonParameters(); - - // FilePath, mandatory - if (!FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidFilePathFile); - Exception reason = new RuntimeException(msg); - ErrorRecord errorRecord = new ErrorRecord(reason, "InvalidFilePathParameterForRegisterScheduledJobDefinition", ErrorCategory.InvalidArgument, this); - WriteError(errorRecord); - - return null; - } - - Collection pathInfos = SessionState.Path.GetResolvedPSPathFromPSPath(FilePath); - if (pathInfos.Count != 1) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidFilePath); - Exception reason = new RuntimeException(msg); - ErrorRecord errorRecord = new ErrorRecord(reason, "InvalidFilePathParameterForRegisterScheduledJobDefinition", ErrorCategory.InvalidArgument, this); - WriteError(errorRecord); - - return null; - } - - parameterCollection.Add(ScheduledJobInvocationInfo.FilePathParameter, pathInfos[0].Path); - - JobInvocationInfo jobInvocationInfo = new ScheduledJobInvocationInfo(jobDefinition, parameterCollection); - - ScheduledJobDefinition definition = new ScheduledJobDefinition(jobInvocationInfo, Trigger, - ScheduledJobOption, _credential); - - return definition; - } - - private Dictionary CreateCommonParameters() - { - Dictionary parameterCollection = new Dictionary(); - - parameterCollection.Add(ScheduledJobInvocationInfo.RunAs32Parameter, RunAs32.ToBool()); - parameterCollection.Add(ScheduledJobInvocationInfo.AuthenticationParameter, Authentication); - - if (InitializationScript != null) - { - parameterCollection.Add(ScheduledJobInvocationInfo.InitializationScriptParameter, InitializationScript); - } - - if (ArgumentList != null) - { - parameterCollection.Add(ScheduledJobInvocationInfo.ArgumentListParameter, ArgumentList); - } - - return parameterCollection; - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/RemoveJobTrigger.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/RemoveJobTrigger.cs deleted file mode 100644 index 81cdb8ecc35..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/RemoveJobTrigger.cs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Management.Automation; -using System.Management.Automation.Host; -using System.Management.Automation.Internal; -using System.Threading; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This cmdlet removes ScheduledJobTriggers from ScheduledJobDefinition objects. - /// - [Cmdlet(VerbsCommon.Remove, "JobTrigger", DefaultParameterSetName = RemoveJobTriggerCommand.JobDefinitionParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=223914")] - public sealed class RemoveJobTriggerCommand : ScheduleJobCmdletBase - { - #region Parameters - - private const string JobDefinitionParameterSet = "JobDefinition"; - private const string JobDefinitionIdParameterSet = "JobDefinitionId"; - private const string JobDefinitionNameParameterSet = "JobDefinitionName"; - - /// - /// Trigger number to remove. - /// - [Parameter(ParameterSetName = RemoveJobTriggerCommand.JobDefinitionParameterSet)] - [Parameter(ParameterSetName = RemoveJobTriggerCommand.JobDefinitionIdParameterSet)] - [Parameter(ParameterSetName = RemoveJobTriggerCommand.JobDefinitionNameParameterSet)] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public Int32[] TriggerId - { - get { return _triggerIds; } - - set { _triggerIds = value; } - } - - private Int32[] _triggerIds; - - /// - /// ScheduledJobDefinition Id. - /// - [Parameter(Position = 0, Mandatory = true, - ParameterSetName = RemoveJobTriggerCommand.JobDefinitionIdParameterSet)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public Int32[] Id - { - get { return _definitionIds; } - - set { _definitionIds = value; } - } - - private Int32[] _definitionIds; - - /// - /// ScheduledJobDefinition Name. - /// - [Parameter(Position = 0, Mandatory = true, - ParameterSetName = RemoveJobTriggerCommand.JobDefinitionNameParameterSet)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public string[] Name - { - get { return _names; } - - set { _names = value; } - } - - private string[] _names; - - /// - /// ScheduledJobDefinition. - /// - [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, - ParameterSetName = RemoveJobTriggerCommand.JobDefinitionParameterSet)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public ScheduledJobDefinition[] InputObject - { - get { return _definitions; } - - set { _definitions = value; } - } - - private ScheduledJobDefinition[] _definitions; - - #endregion - - #region Cmdlet Overrides - - /// - /// Process input. - /// - protected override void ProcessRecord() - { - switch (ParameterSetName) - { - case JobDefinitionParameterSet: - RemoveFromJobDefinition(_definitions); - break; - - case JobDefinitionIdParameterSet: - RemoveFromJobDefinition(GetJobDefinitionsById(_definitionIds)); - break; - - case JobDefinitionNameParameterSet: - RemoveFromJobDefinition(GetJobDefinitionsByName(_names)); - break; - } - } - - #endregion - - #region Private Methods - - private void RemoveFromJobDefinition(IEnumerable definitions) - { - foreach (ScheduledJobDefinition definition in definitions) - { - List notFoundIds = new List(); - try - { - notFoundIds = definition.RemoveTriggers(_triggerIds, true); - } - catch (ScheduledJobException e) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.CantRemoveTriggersFromDefinition, definition.Name); - Exception reason = new RuntimeException(msg, e); - ErrorRecord errorRecord = new ErrorRecord(reason, "CantRemoveTriggersFromScheduledJobDefinition", ErrorCategory.InvalidOperation, definition); - WriteError(errorRecord); - } - - // Report not found errors. - foreach (Int32 idNotFound in notFoundIds) - { - WriteTriggerNotFoundError(idNotFound, definition.Name, definition); - } - } - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/SchedJobCmdletBase.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/SchedJobCmdletBase.cs deleted file mode 100644 index e8112877019..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/SchedJobCmdletBase.cs +++ /dev/null @@ -1,468 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Management.Automation; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// Base class for ScheduledJob cmdlets. - /// - public abstract class ScheduleJobCmdletBase : PSCmdlet - { - #region Cmdlet Strings - - /// - /// Scheduled job module name. - /// - protected const string ModuleName = "PSScheduledJob"; - - #endregion - - #region Utility Methods - - /// - /// Makes delegate callback call for each scheduledjob definition object found. - /// - /// Callback delegate for each discovered item. - internal void FindAllJobDefinitions( - Action itemFound) - { - Dictionary errors = ScheduledJobDefinition.RefreshRepositoryFromStore((definition) => - { - if (ValidateJobDefinition(definition)) - { - itemFound(definition); - } - }); - HandleAllLoadErrors(errors); - } - - /// - /// Returns a single ScheduledJobDefinition object from the local - /// scheduled job definition repository corresponding to the provided id. - /// - /// Local repository scheduled job definition id. - /// Errors/warnings are written to host. - /// ScheduledJobDefinition object. - internal ScheduledJobDefinition GetJobDefinitionById( - Int32 id, - bool writeErrorsAndWarnings = true) - { - Dictionary errors = ScheduledJobDefinition.RefreshRepositoryFromStore(null); - HandleAllLoadErrors(errors); - - foreach (var definition in ScheduledJobDefinition.Repository.Definitions) - { - if (definition.Id == id && - ValidateJobDefinition(definition)) - { - return definition; - } - } - - if (writeErrorsAndWarnings) - { - WriteDefinitionNotFoundByIdError(id); - } - - return null; - } - - /// - /// Returns an array of ScheduledJobDefinition objects from the local - /// scheduled job definition repository corresponding to the provided Ids. - /// - /// Local repository scheduled job definition ids. - /// Errors/warnings are written to host. - /// List of ScheduledJobDefinition objects. - internal List GetJobDefinitionsById( - Int32[] ids, - bool writeErrorsAndWarnings = true) - { - Dictionary errors = ScheduledJobDefinition.RefreshRepositoryFromStore(null); - HandleAllLoadErrors(errors); - - List definitions = new List(); - HashSet findIds = new HashSet(ids); - foreach (var definition in ScheduledJobDefinition.Repository.Definitions) - { - if (findIds.Contains(definition.Id) && - ValidateJobDefinition(definition)) - { - definitions.Add(definition); - findIds.Remove(definition.Id); - } - } - - if (writeErrorsAndWarnings) - { - foreach (int id in findIds) - { - WriteDefinitionNotFoundByIdError(id); - } - } - - return definitions; - } - - /// - /// Makes delegate callback call for each scheduledjob definition object found. - /// - /// Local repository scheduled job definition ids. - /// Callback delegate for each discovered item. - /// Errors/warnings are written to host. - internal void FindJobDefinitionsById( - Int32[] ids, - Action itemFound, - bool writeErrorsAndWarnings = true) - { - HashSet findIds = new HashSet(ids); - Dictionary errors = ScheduledJobDefinition.RefreshRepositoryFromStore((definition) => - { - if (findIds.Contains(definition.Id) && - ValidateJobDefinition(definition)) - { - itemFound(definition); - findIds.Remove(definition.Id); - } - }); - - HandleAllLoadErrors(errors); - - if (writeErrorsAndWarnings) - { - foreach (Int32 id in findIds) - { - WriteDefinitionNotFoundByIdError(id); - } - } - } - - /// - /// Returns an array of ScheduledJobDefinition objects from the local - /// scheduled job definition repository corresponding to the given name. - /// - /// Scheduled job definition name. - /// Errors/warnings are written to host. - /// ScheduledJobDefinition object. - internal ScheduledJobDefinition GetJobDefinitionByName( - string name, - bool writeErrorsAndWarnings = true) - { - Dictionary errors = ScheduledJobDefinition.RefreshRepositoryFromStore(null); - - // Look for match. - WildcardPattern namePattern = new WildcardPattern(name, WildcardOptions.IgnoreCase); - foreach (var definition in ScheduledJobDefinition.Repository.Definitions) - { - if (namePattern.IsMatch(definition.Name) && - ValidateJobDefinition(definition)) - { - return definition; - } - } - - // Look for load error. - foreach (var error in errors) - { - if (namePattern.IsMatch(error.Key)) - { - HandleLoadError(error.Key, error.Value); - } - } - - if (writeErrorsAndWarnings) - { - WriteDefinitionNotFoundByNameError(name); - } - - return null; - } - - /// - /// Returns an array of ScheduledJobDefinition objects from the local - /// scheduled job definition repository corresponding to the given names. - /// - /// Scheduled job definition names. - /// Errors/warnings are written to host. - /// List of ScheduledJobDefinition objects. - internal List GetJobDefinitionsByName( - string[] names, - bool writeErrorsAndWarnings = true) - { - Dictionary errors = ScheduledJobDefinition.RefreshRepositoryFromStore(null); - - List definitions = new List(); - foreach (string name in names) - { - WildcardPattern namePattern = new WildcardPattern(name, WildcardOptions.IgnoreCase); - - // Look for match. - bool nameFound = false; - foreach (var definition in ScheduledJobDefinition.Repository.Definitions) - { - if (namePattern.IsMatch(definition.Name) && - ValidateJobDefinition(definition)) - { - nameFound = true; - definitions.Add(definition); - } - } - - // Look for load error. - foreach (var error in errors) - { - if (namePattern.IsMatch(error.Key)) - { - HandleLoadError(error.Key, error.Value); - } - } - - if (!nameFound && writeErrorsAndWarnings) - { - WriteDefinitionNotFoundByNameError(name); - } - } - - return definitions; - } - - /// - /// Makes delegate callback call for each scheduledjob definition object found. - /// - /// Scheduled job definition names. - /// Callback delegate for each discovered item. - /// Errors/warnings are written to host. - internal void FindJobDefinitionsByName( - string[] names, - Action itemFound, - bool writeErrorsAndWarnings = true) - { - HashSet notFoundNames = new HashSet(names); - Dictionary patterns = new Dictionary(); - foreach (string name in names) - { - if (!patterns.ContainsKey(name)) - { - patterns.Add(name, new WildcardPattern(name, WildcardOptions.IgnoreCase)); - } - } - - Dictionary errors = ScheduledJobDefinition.RefreshRepositoryFromStore((definition) => - { - foreach (var item in patterns) - { - if (item.Value.IsMatch(definition.Name) && - ValidateJobDefinition(definition)) - { - itemFound(definition); - if (notFoundNames.Contains(item.Key)) - { - notFoundNames.Remove(item.Key); - } - } - } - }); - - // Look for load error. - foreach (var error in errors) - { - foreach (var item in patterns) - { - if (item.Value.IsMatch(error.Key)) - { - HandleLoadError(error.Key, error.Value); - } - } - } - - if (writeErrorsAndWarnings) - { - foreach (var name in notFoundNames) - { - WriteDefinitionNotFoundByNameError(name); - } - } - } - - /// - /// Writes a "Trigger not found" error to host. - /// - /// Trigger Id not found. - /// ScheduledJobDefinition name. - /// Error object. - internal void WriteTriggerNotFoundError( - Int32 notFoundId, - string definitionName, - object errorObject) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.TriggerNotFound, notFoundId, definitionName); - Exception reason = new RuntimeException(msg); - ErrorRecord errorRecord = new ErrorRecord(reason, "ScheduledJobTriggerNotFound", ErrorCategory.ObjectNotFound, errorObject); - WriteError(errorRecord); - } - - /// - /// Writes a "Definition not found for Id" error to host. - /// - /// Definition Id. - internal void WriteDefinitionNotFoundByIdError( - Int32 defId) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.DefinitionNotFoundById, defId); - Exception reason = new RuntimeException(msg); - ErrorRecord errorRecord = new ErrorRecord(reason, "ScheduledJobDefinitionNotFoundById", ErrorCategory.ObjectNotFound, null); - WriteError(errorRecord); - } - - /// - /// Writes a "Definition not found for Name" error to host. - /// - /// Definition Name. - internal void WriteDefinitionNotFoundByNameError( - string name) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.DefinitionNotFoundByName, name); - Exception reason = new RuntimeException(msg); - ErrorRecord errorRecord = new ErrorRecord(reason, "ScheduledJobDefinitionNotFoundByName", ErrorCategory.ObjectNotFound, null); - WriteError(errorRecord); - } - - /// - /// Writes a "Load from job store" error to host. - /// - /// Scheduled job definition name. - /// Exception thrown during loading. - internal void WriteErrorLoadingDefinition(string name, Exception error) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.CantLoadDefinitionFromStore, name); - Exception reason = new RuntimeException(msg, error); - ErrorRecord errorRecord = new ErrorRecord(reason, "CantLoadScheduledJobDefinitionFromStore", ErrorCategory.InvalidOperation, null); - WriteError(errorRecord); - } - - /// - /// Creates a Once job trigger with provided repetition interval and an - /// infinite duration, and adds the trigger to the provided scheduled job - /// definition object. - /// - /// ScheduledJobDefinition. - /// Rep interval. - /// Save definition change. - internal static void AddRepetitionJobTriggerToDefinition( - ScheduledJobDefinition definition, - TimeSpan repInterval, - bool save) - { - if (definition == null) - { - throw new PSArgumentNullException("definition"); - } - - TimeSpan repDuration = TimeSpan.MaxValue; - - // Validate every interval value. - if (repInterval < TimeSpan.Zero || repDuration < TimeSpan.Zero) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidRepetitionParamValues); - } - - if (repInterval < TimeSpan.FromMinutes(1)) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidRepetitionIntervalValue); - } - - if (repInterval > repDuration) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidRepetitionInterval); - } - - // Create job trigger. - var trigger = ScheduledJobTrigger.CreateOnceTrigger( - DateTime.Now, - TimeSpan.Zero, - repInterval, - repDuration, - 0, - true); - - definition.AddTriggers(new ScheduledJobTrigger[] { trigger }, save); - } - - #endregion - - #region Private Methods - - private void HandleAllLoadErrors(Dictionary errors) - { - foreach (var error in errors) - { - HandleLoadError(error.Key, error.Value); - } - } - - private void HandleLoadError(string name, Exception e) - { - if (e is System.IO.IOException || - e is System.Xml.XmlException || - e is System.TypeInitializationException || - e is System.Runtime.Serialization.SerializationException || - e is System.ArgumentNullException) - { - // Remove the corrupted scheduled job definition and - // notify user with error message. - ScheduledJobDefinition.RemoveDefinition(name); - WriteErrorLoadingDefinition(name, e); - } - } - - private void ValidateJobDefinitions() - { - foreach (var definition in ScheduledJobDefinition.Repository.Definitions) - { - ValidateJobDefinition(definition); - } - } - - /// - /// Validates the job definition object retrieved from store by syncing - /// its data with the corresponding Task Scheduler task. If no task - /// is found then validation fails. - /// - /// - /// - private bool ValidateJobDefinition(ScheduledJobDefinition definition) - { - Exception ex = null; - try - { - definition.SyncWithWTS(); - } - catch (System.IO.DirectoryNotFoundException e) - { - ex = e; - } - catch (System.IO.FileNotFoundException e) - { - ex = e; - } - catch (System.ArgumentNullException e) - { - ex = e; - } - - if (ex != null) - { - WriteErrorLoadingDefinition(definition.Name, ex); - } - - return (ex == null); - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/ScheduledJobOptionCmdletBase.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/ScheduledJobOptionCmdletBase.cs deleted file mode 100644 index cd70dc0a8f1..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/ScheduledJobOptionCmdletBase.cs +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Management.Automation; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// Base class for NewScheduledJobOption, SetScheduledJobOption cmdlets. - /// - public abstract class ScheduledJobOptionCmdletBase : ScheduleJobCmdletBase - { - #region Parameters - - /// - /// Options parameter set name. - /// - protected const string OptionsParameterSet = "Options"; - - /// - /// Scheduled job task is run with elevated privileges when this switch is selected. - /// - [Parameter(ParameterSetName = ScheduledJobOptionCmdletBase.OptionsParameterSet)] - public SwitchParameter RunElevated - { - get { return _runElevated; } - - set { _runElevated = value; } - } - - private SwitchParameter _runElevated = false; - - /// - /// Scheduled job task is hidden in Windows Task Scheduler when true. - /// - [Parameter(ParameterSetName = ScheduledJobOptionCmdletBase.OptionsParameterSet)] - public SwitchParameter HideInTaskScheduler - { - get { return _hideInTaskScheduler; } - - set { _hideInTaskScheduler = value; } - } - - private SwitchParameter _hideInTaskScheduler = false; - - /// - /// Scheduled job task will be restarted when machine becomes idle. This is applicable - /// only if the job was configured to stop when no longer idle. - /// - [Parameter(ParameterSetName = ScheduledJobOptionCmdletBase.OptionsParameterSet)] - public SwitchParameter RestartOnIdleResume - { - get { return _restartOnIdleResume; } - - set { _restartOnIdleResume = value; } - } - - private SwitchParameter _restartOnIdleResume = false; - - /// - /// Provides task scheduler options for multiple running instances of the job. - /// - [Parameter(ParameterSetName = ScheduledJobOptionCmdletBase.OptionsParameterSet)] - public TaskMultipleInstancePolicy MultipleInstancePolicy - { - get { return _multipleInstancePolicy; } - - set { _multipleInstancePolicy = value; } - } - - private TaskMultipleInstancePolicy _multipleInstancePolicy = TaskMultipleInstancePolicy.IgnoreNew; - - /// - /// Prevents the job task from being started manually via Task Scheduler UI. - /// - [Parameter(ParameterSetName = ScheduledJobOptionCmdletBase.OptionsParameterSet)] - public SwitchParameter DoNotAllowDemandStart - { - get { return _doNotAllowDemandStart; } - - set { _doNotAllowDemandStart = value; } - } - - private SwitchParameter _doNotAllowDemandStart = false; - - /// - /// Allows the job task to be run only when network connection available. - /// - [Parameter(ParameterSetName = ScheduledJobOptionCmdletBase.OptionsParameterSet)] - public SwitchParameter RequireNetwork - { - get { return _requireNetwork; } - - set { _requireNetwork = value; } - } - - private SwitchParameter _requireNetwork = false; - - /// - /// Stops running job started by Task Scheduler if computer is no longer idle. - /// - [Parameter(ParameterSetName = ScheduledJobOptionCmdletBase.OptionsParameterSet)] - public SwitchParameter StopIfGoingOffIdle - { - get { return _stopIfGoingOffIdle; } - - set { _stopIfGoingOffIdle = value; } - } - - private SwitchParameter _stopIfGoingOffIdle = false; - - /// - /// Will wake the computer to run the job if computer is in sleep mode when - /// trigger activates. - /// - [Parameter(ParameterSetName = ScheduledJobOptionCmdletBase.OptionsParameterSet)] - public SwitchParameter WakeToRun - { - get { return _wakeToRun; } - - set { _wakeToRun = value; } - } - - private SwitchParameter _wakeToRun = false; - - /// - /// Continue running task job if computer going on battery. - /// - [Parameter(ParameterSetName = ScheduledJobOptionCmdletBase.OptionsParameterSet)] - public SwitchParameter ContinueIfGoingOnBattery - { - get { return _continueIfGoingOnBattery; } - - set { _continueIfGoingOnBattery = value; } - } - - private SwitchParameter _continueIfGoingOnBattery = false; - - /// - /// Will start job task even if computer is running on battery power. - /// - [Parameter(ParameterSetName = ScheduledJobOptionCmdletBase.OptionsParameterSet)] - public SwitchParameter StartIfOnBattery - { - get { return _startIfOnBattery; } - - set { _startIfOnBattery = value; } - } - - private SwitchParameter _startIfOnBattery = false; - - /// - /// Specifies how long Task Scheduler will wait for idle time after a trigger has - /// activated before giving up trying to run job during computer idle. - /// - [Parameter(ParameterSetName = ScheduledJobOptionCmdletBase.OptionsParameterSet)] - public TimeSpan IdleTimeout - { - get { return _idleTimeout; } - - set { _idleTimeout = value; } - } - - private TimeSpan _idleTimeout = new TimeSpan(1, 0, 0); - - /// - /// How long the computer needs to be idle before a triggered job task is started. - /// - [Parameter(ParameterSetName = ScheduledJobOptionCmdletBase.OptionsParameterSet)] - public TimeSpan IdleDuration - { - get { return _idleDuration; } - - set { _idleDuration = value; } - } - - private TimeSpan _idleDuration = new TimeSpan(0, 10, 0); - - /// - /// Will start job task if machine is idle. - /// - [Parameter(ParameterSetName = ScheduledJobOptionCmdletBase.OptionsParameterSet)] - public SwitchParameter StartIfIdle - { - get { return _startIfIdle; } - - set { _startIfIdle = value; } - } - - private SwitchParameter _startIfIdle = false; - - #endregion - - #region Cmdlet Overrides - - /// - /// Begin processing. - /// - protected override void BeginProcessing() - { - // Validate parameters. - if (MyInvocation.BoundParameters.ContainsKey(nameof(IdleTimeout)) && - _idleTimeout < TimeSpan.Zero) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidIdleTimeout); - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(IdleDuration)) && - _idleDuration < TimeSpan.Zero) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidIdleDuration); - } - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/SetJobDefinition.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/SetJobDefinition.cs deleted file mode 100644 index 0d56f5640e4..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/SetJobDefinition.cs +++ /dev/null @@ -1,552 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; -using System.Management.Automation.Runspaces; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This cmdlet updates a scheduled job definition object based on the provided - /// parameter values and saves changes to job store and Task Scheduler. - /// - [Cmdlet(VerbsCommon.Set, "ScheduledJob", DefaultParameterSetName = SetScheduledJobCommand.ScriptBlockParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=223924")] - [OutputType(typeof(ScheduledJobDefinition))] - public sealed class SetScheduledJobCommand : ScheduleJobCmdletBase - { - #region Parameters - - private const string ExecutionParameterSet = "Execution"; - private const string ScriptBlockParameterSet = "ScriptBlock"; - private const string FilePathParameterSet = "FilePath"; - - /// - /// Name of scheduled job definition. - /// - [Parameter(ParameterSetName = SetScheduledJobCommand.FilePathParameterSet)] - [Parameter(ParameterSetName = SetScheduledJobCommand.ScriptBlockParameterSet)] - [ValidateNotNullOrEmpty] - public string Name - { - get { return _name; } - - set { _name = value; } - } - - private string _name; - - /// - /// File path for script to be run in job. - /// - [Parameter(ParameterSetName = SetScheduledJobCommand.FilePathParameterSet)] - [Alias("Path")] - [ValidateNotNullOrEmpty] - public string FilePath - { - get { return _filePath; } - - set { _filePath = value; } - } - - private string _filePath; - - /// - /// ScriptBlock containing script to run in job. - /// - [Parameter(ParameterSetName = SetScheduledJobCommand.ScriptBlockParameterSet)] - [ValidateNotNull] - public ScriptBlock ScriptBlock - { - get { return _scriptBlock; } - - set { _scriptBlock = value; } - } - - private ScriptBlock _scriptBlock; - - /// - /// Triggers to define when job will run. - /// - [Parameter(ParameterSetName = SetScheduledJobCommand.ScriptBlockParameterSet)] - [Parameter(ParameterSetName = SetScheduledJobCommand.FilePathParameterSet)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public ScheduledJobTrigger[] Trigger - { - get { return _triggers; } - - set { _triggers = value; } - } - - private ScheduledJobTrigger[] _triggers; - - /// - /// Initialization script to run before the job starts. - /// - [Parameter(ParameterSetName = SetScheduledJobCommand.ScriptBlockParameterSet)] - [Parameter(ParameterSetName = SetScheduledJobCommand.FilePathParameterSet)] - [ValidateNotNull] - public ScriptBlock InitializationScript - { - get { return _initializationScript; } - - set { _initializationScript = value; } - } - - private ScriptBlock _initializationScript; - - /// - /// Runs the job in a 32-bit PowerShell process. - /// - [Parameter(ParameterSetName = SetScheduledJobCommand.ScriptBlockParameterSet)] - [Parameter(ParameterSetName = SetScheduledJobCommand.FilePathParameterSet)] - public SwitchParameter RunAs32 - { - get { return _runAs32; } - - set { _runAs32 = value; } - } - - private SwitchParameter _runAs32; - - /// - /// Credentials for job. - /// - [Parameter(ParameterSetName = SetScheduledJobCommand.ScriptBlockParameterSet)] - [Parameter(ParameterSetName = SetScheduledJobCommand.FilePathParameterSet)] - [Credential()] - public PSCredential Credential - { - get { return _credential; } - - set { _credential = value; } - } - - private PSCredential _credential; - - /// - /// Authentication mechanism to use for job. - /// - [Parameter(ParameterSetName = SetScheduledJobCommand.ScriptBlockParameterSet)] - [Parameter(ParameterSetName = SetScheduledJobCommand.FilePathParameterSet)] - public AuthenticationMechanism Authentication - { - get { return _authenticationMechanism; } - - set { _authenticationMechanism = value; } - } - - private AuthenticationMechanism _authenticationMechanism; - - /// - /// Scheduling options for job. - /// - [Parameter(ParameterSetName = SetScheduledJobCommand.ScriptBlockParameterSet)] - [Parameter(ParameterSetName = SetScheduledJobCommand.FilePathParameterSet)] - [ValidateNotNull] - public ScheduledJobOptions ScheduledJobOption - { - get { return _options; } - - set { _options = value; } - } - - private ScheduledJobOptions _options; - - /// - /// Input for the job. - /// - [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, - ParameterSetName = SetScheduledJobCommand.ScriptBlockParameterSet)] - [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, - ParameterSetName = SetScheduledJobCommand.FilePathParameterSet)] - [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, - ParameterSetName = SetScheduledJobCommand.ExecutionParameterSet)] - [ValidateNotNull] - public ScheduledJobDefinition InputObject - { - get { return _definition; } - - set { _definition = value; } - } - - private ScheduledJobDefinition _definition; - - /// - /// ClearExecutionHistory. - /// - [Parameter(ParameterSetName = SetScheduledJobCommand.ExecutionParameterSet)] - public SwitchParameter ClearExecutionHistory - { - get { return _clearExecutionHistory; } - - set { _clearExecutionHistory = value; } - } - - private SwitchParameter _clearExecutionHistory; - - /// - /// Maximum number of job results allowed in job store. - /// - [Parameter(ParameterSetName = SetScheduledJobCommand.ScriptBlockParameterSet)] - [Parameter(ParameterSetName = SetScheduledJobCommand.FilePathParameterSet)] - public int MaxResultCount - { - get { return _executionHistoryLength; } - - set { _executionHistoryLength = value; } - } - - private int _executionHistoryLength; - - /// - /// Pass the ScheduledJobDefinition object through to output. - /// - [Parameter(ParameterSetName = SetScheduledJobCommand.ScriptBlockParameterSet)] - [Parameter(ParameterSetName = SetScheduledJobCommand.FilePathParameterSet)] - [Parameter(ParameterSetName = SetScheduledJobCommand.ExecutionParameterSet)] - public SwitchParameter PassThru - { - get { return _passThru; } - - set { _passThru = value; } - } - - private SwitchParameter _passThru; - - /// - /// Argument list. - /// - [Parameter(ParameterSetName = SetScheduledJobCommand.ScriptBlockParameterSet)] - [Parameter(ParameterSetName = SetScheduledJobCommand.FilePathParameterSet)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public object[] ArgumentList - { - get { return _arguments; } - - set { _arguments = value; } - } - - private object[] _arguments; - - /// - /// Runs scheduled job immediately after successfully setting job definition. - /// - [Parameter(ParameterSetName = SetScheduledJobCommand.ScriptBlockParameterSet)] - [Parameter(ParameterSetName = SetScheduledJobCommand.FilePathParameterSet)] - public SwitchParameter RunNow - { - get { return _runNow; } - - set { _runNow = value; } - } - - private SwitchParameter _runNow; - - /// - /// Runs scheduled job at the repetition interval indicated by the - /// TimeSpan value for an unending duration. - /// - [Parameter(ParameterSetName = SetScheduledJobCommand.ScriptBlockParameterSet)] - [Parameter(ParameterSetName = SetScheduledJobCommand.FilePathParameterSet)] - public TimeSpan RunEvery - { - get { return _runEvery; } - - set { _runEvery = value; } - } - - private TimeSpan _runEvery; - - #endregion - - #region Cmdlet Overrides - - /// - /// Process input. - /// - protected override void ProcessRecord() - { - switch (ParameterSetName) - { - case ExecutionParameterSet: - UpdateExecutionDefinition(); - break; - - case ScriptBlockParameterSet: - case FilePathParameterSet: - UpdateDefinition(); - break; - } - - try - { - // If RunEvery parameter is specified then create a job trigger for the definition that - // runs the job at the requested interval. - bool addedTrigger = false; - if (MyInvocation.BoundParameters.ContainsKey(nameof(RunEvery))) - { - AddRepetitionJobTriggerToDefinition( - _definition, - RunEvery, - false); - - addedTrigger = true; - } - - if (Trigger != null || ScheduledJobOption != null || Credential != null || addedTrigger) - { - // Save definition to file and update WTS. - _definition.Save(); - } - else - { - // No WTS changes. Save definition to store only. - _definition.SaveToStore(); - } - - if (_runNow) - { - _definition.RunAsTask(); - } - } - catch (ScheduledJobException e) - { - ErrorRecord errorRecord; - - if (e.InnerException != null && - e.InnerException is System.UnauthorizedAccessException) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.NoAccessOnSetJobDefinition, _definition.Name); - errorRecord = new ErrorRecord(new RuntimeException(msg, e), - "NoAccessFailureOnSetJobDefinition", ErrorCategory.InvalidOperation, _definition); - } - else if (e.InnerException != null && - e.InnerException is System.IO.IOException) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.IOFailureOnSetJobDefinition, _definition.Name); - errorRecord = new ErrorRecord(new RuntimeException(msg, e), - "IOFailureOnSetJobDefinition", ErrorCategory.InvalidOperation, _definition); - } - else - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.CantSetJobDefinition, _definition.Name); - errorRecord = new ErrorRecord(new RuntimeException(msg, e), - "CantSetPropertiesToScheduledJobDefinition", ErrorCategory.InvalidOperation, _definition); - } - - WriteError(errorRecord); - } - - if (_passThru) - { - WriteObject(_definition); - } - } - - #endregion - - #region Private Methods - - private void UpdateExecutionDefinition() - { - if (_clearExecutionHistory) - { - _definition.ClearExecutionHistory(); - } - } - - private void UpdateDefinition() - { - if (_name != null && - string.Compare(_name, _definition.Name, StringComparison.OrdinalIgnoreCase) != 0) - { - _definition.RenameAndSave(_name); - } - - UpdateJobInvocationInfo(); - - if (MyInvocation.BoundParameters.ContainsKey(nameof(MaxResultCount))) - { - _definition.SetExecutionHistoryLength(MaxResultCount, false); - } - - if (Credential != null) - { - _definition.Credential = Credential; - } - - if (Trigger != null) - { - _definition.SetTriggers(Trigger, false); - } - - if (ScheduledJobOption != null) - { - _definition.UpdateOptions(ScheduledJobOption, false); - } - } - - /// - /// Create new ScheduledJobInvocationInfo object with update information and - /// update the job definition object. - /// - private void UpdateJobInvocationInfo() - { - Dictionary parameters = UpdateParameters(); - string name = _definition.Name; - string command; - - if (ScriptBlock != null) - { - command = ScriptBlock.ToString(); - } - else if (FilePath != null) - { - command = FilePath; - } - else - { - command = _definition.InvocationInfo.Command; - } - - JobDefinition jobDefinition = new JobDefinition(typeof(ScheduledJobSourceAdapter), command, name); - jobDefinition.ModuleName = ModuleName; - JobInvocationInfo jobInvocationInfo = new ScheduledJobInvocationInfo(jobDefinition, parameters); - - _definition.UpdateJobInvocationInfo(jobInvocationInfo, false); - } - - /// - /// Creates a new parameter dictionary with update parameters. - /// - /// Updated parameters. - private Dictionary UpdateParameters() - { - Debug.Assert(_definition.InvocationInfo.Parameters.Count != 0, - "ScheduledJobDefinition must always have some job invocation parameters"); - Dictionary newParameters = new Dictionary(); - foreach (CommandParameter parameter in _definition.InvocationInfo.Parameters[0]) - { - newParameters.Add(parameter.Name, parameter.Value); - } - - // RunAs32 - if (MyInvocation.BoundParameters.ContainsKey(nameof(RunAs32))) - { - if (newParameters.ContainsKey(ScheduledJobInvocationInfo.RunAs32Parameter)) - { - newParameters[ScheduledJobInvocationInfo.RunAs32Parameter] = RunAs32.ToBool(); - } - else - { - newParameters.Add(ScheduledJobInvocationInfo.RunAs32Parameter, RunAs32.ToBool()); - } - } - - // Authentication - if (MyInvocation.BoundParameters.ContainsKey(nameof(Authentication))) - { - if (newParameters.ContainsKey(ScheduledJobInvocationInfo.AuthenticationParameter)) - { - newParameters[ScheduledJobInvocationInfo.AuthenticationParameter] = Authentication; - } - else - { - newParameters.Add(ScheduledJobInvocationInfo.AuthenticationParameter, Authentication); - } - } - - // InitializationScript - if (InitializationScript == null) - { - if (newParameters.ContainsKey(ScheduledJobInvocationInfo.InitializationScriptParameter)) - { - newParameters.Remove(ScheduledJobInvocationInfo.InitializationScriptParameter); - } - } - else - { - if (newParameters.ContainsKey(ScheduledJobInvocationInfo.InitializationScriptParameter)) - { - newParameters[ScheduledJobInvocationInfo.InitializationScriptParameter] = InitializationScript; - } - else - { - newParameters.Add(ScheduledJobInvocationInfo.InitializationScriptParameter, InitializationScript); - } - } - - // ScriptBlock - if (ScriptBlock != null) - { - // FilePath cannot also be specified. - if (newParameters.ContainsKey(ScheduledJobInvocationInfo.FilePathParameter)) - { - newParameters.Remove(ScheduledJobInvocationInfo.FilePathParameter); - } - - if (newParameters.ContainsKey(ScheduledJobInvocationInfo.ScriptBlockParameter)) - { - newParameters[ScheduledJobInvocationInfo.ScriptBlockParameter] = ScriptBlock; - } - else - { - newParameters.Add(ScheduledJobInvocationInfo.ScriptBlockParameter, ScriptBlock); - } - } - - // FilePath - if (FilePath != null) - { - // ScriptBlock cannot also be specified. - if (newParameters.ContainsKey(ScheduledJobInvocationInfo.ScriptBlockParameter)) - { - newParameters.Remove(ScheduledJobInvocationInfo.ScriptBlockParameter); - } - - if (newParameters.ContainsKey(ScheduledJobInvocationInfo.FilePathParameter)) - { - newParameters[ScheduledJobInvocationInfo.FilePathParameter] = FilePath; - } - else - { - newParameters.Add(ScheduledJobInvocationInfo.FilePathParameter, FilePath); - } - } - - // ArgumentList - if (ArgumentList == null) - { - // Clear existing argument list only if new scriptblock or script file path was specified - // (in this case old argument list is invalid). - if (newParameters.ContainsKey(ScheduledJobInvocationInfo.ArgumentListParameter) && - (ScriptBlock != null || FilePath != null)) - { - newParameters.Remove(ScheduledJobInvocationInfo.ArgumentListParameter); - } - } - else - { - if (newParameters.ContainsKey(ScheduledJobInvocationInfo.ArgumentListParameter)) - { - newParameters[ScheduledJobInvocationInfo.ArgumentListParameter] = ArgumentList; - } - else - { - newParameters.Add(ScheduledJobInvocationInfo.ArgumentListParameter, ArgumentList); - } - } - - return newParameters; - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/SetJobTrigger.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/SetJobTrigger.cs deleted file mode 100644 index 4eeab7fcc72..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/SetJobTrigger.cs +++ /dev/null @@ -1,945 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This cmdlet sets properties on a trigger for a ScheduledJobDefinition. - /// - [Cmdlet(VerbsCommon.Set, "JobTrigger", DefaultParameterSetName = SetJobTriggerCommand.DefaultParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=223916")] - [OutputType(typeof(ScheduledJobTrigger))] - public sealed class SetJobTriggerCommand : ScheduleJobCmdletBase - { - #region Parameters - - private const string DefaultParameterSet = "DefaultParams"; - - /// - /// ScheduledJobTrigger objects to set properties on. - /// - [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, - ParameterSetName = SetJobTriggerCommand.DefaultParameterSet)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public ScheduledJobTrigger[] InputObject - { - get { return _triggers; } - - set { _triggers = value; } - } - - private ScheduledJobTrigger[] _triggers; - - /// - /// Daily interval for trigger. - /// - [Parameter(ParameterSetName = SetJobTriggerCommand.DefaultParameterSet)] - public Int32 DaysInterval - { - get { return _daysInterval; } - - set { _daysInterval = value; } - } - - private Int32 _daysInterval = 1; - - /// - /// Weekly interval for trigger. - /// - [Parameter(ParameterSetName = SetJobTriggerCommand.DefaultParameterSet)] - public Int32 WeeksInterval - { - get { return _weeksInterval; } - - set { _weeksInterval = value; } - } - - private Int32 _weeksInterval = 1; - - /// - /// Random delay for trigger. - /// - [Parameter(ParameterSetName = SetJobTriggerCommand.DefaultParameterSet)] - public TimeSpan RandomDelay - { - get { return _randomDelay; } - - set { _randomDelay = value; } - } - - private TimeSpan _randomDelay; - - /// - /// Job start date/time for trigger. - /// - [Parameter(ParameterSetName = SetJobTriggerCommand.DefaultParameterSet)] - public DateTime At - { - get { return _atTime; } - - set { _atTime = value; } - } - - private DateTime _atTime; - - /// - /// User name for AtLogon trigger. The AtLogon parameter set will create a trigger - /// that activates after log on for the provided user name. - /// - [Parameter(ParameterSetName = SetJobTriggerCommand.DefaultParameterSet)] - [ValidateNotNullOrEmpty] - public string User - { - get { return _user; } - - set { _user = value; } - } - - private string _user; - - /// - /// Days of week for trigger. - /// - [Parameter(ParameterSetName = SetJobTriggerCommand.DefaultParameterSet)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public DayOfWeek[] DaysOfWeek - { - get { return _daysOfWeek; } - - set { _daysOfWeek = value; } - } - - private DayOfWeek[] _daysOfWeek; - - /// - /// Switch to specify an AtStartup trigger. - /// - [Parameter(ParameterSetName = SetJobTriggerCommand.DefaultParameterSet)] - public SwitchParameter AtStartup - { - get { return _atStartup; } - - set { _atStartup = value; } - } - - private SwitchParameter _atStartup; - - /// - /// Switch to specify an AtLogon trigger. - /// - [Parameter(ParameterSetName = SetJobTriggerCommand.DefaultParameterSet)] - public SwitchParameter AtLogOn - { - get { return _atLogon; } - - set { _atLogon = value; } - } - - private SwitchParameter _atLogon; - - /// - /// Switch to specify an Once trigger. - /// - [Parameter(ParameterSetName = SetJobTriggerCommand.DefaultParameterSet)] - public SwitchParameter Once - { - get { return _once; } - - set { _once = value; } - } - - private SwitchParameter _once; - - /// - /// Repetition interval of a one time trigger. - /// - [Parameter(ParameterSetName = SetJobTriggerCommand.DefaultParameterSet)] - public TimeSpan RepetitionInterval - { - get { return _repInterval; } - - set { _repInterval = value; } - } - - private TimeSpan _repInterval; - - /// - /// Repetition duration of a one time trigger. - /// - [Parameter(ParameterSetName = SetJobTriggerCommand.DefaultParameterSet)] - public TimeSpan RepetitionDuration - { - get { return _repDuration; } - - set { _repDuration = value; } - } - - private TimeSpan _repDuration; - - /// - /// Repetition interval repeats indefinitely. - /// - [Parameter(ParameterSetName = SetJobTriggerCommand.DefaultParameterSet)] - public SwitchParameter RepeatIndefinitely - { - get { return _repRepeatIndefinitely; } - - set { _repRepeatIndefinitely = value; } - } - - private SwitchParameter _repRepeatIndefinitely; - - /// - /// Switch to specify an Daily trigger. - /// - [Parameter(ParameterSetName = SetJobTriggerCommand.DefaultParameterSet)] - public SwitchParameter Daily - { - get { return _daily; } - - set { _daily = value; } - } - - private SwitchParameter _daily; - - /// - /// Switch to specify an Weekly trigger. - /// - [Parameter(ParameterSetName = SetJobTriggerCommand.DefaultParameterSet)] - public SwitchParameter Weekly - { - get { return _weekly; } - - set { _weekly = value; } - } - - private SwitchParameter _weekly; - - /// - /// Pass through job trigger object. - /// - [Parameter(ParameterSetName = SetJobTriggerCommand.DefaultParameterSet)] - public SwitchParameter PassThru - { - get { return _passThru; } - - set { _passThru = value; } - } - - private SwitchParameter _passThru; - - #endregion - - #region Cmdlet Overrides - - /// - /// Process input. - /// - protected override void ProcessRecord() - { - // Validate the parameter set and write any errors. - TriggerFrequency newTriggerFrequency = TriggerFrequency.None; - if (!ValidateParameterSet(ref newTriggerFrequency)) - { - return; - } - - // Update each trigger object with the current parameter set. - // The associated scheduled job definition will also be updated. - foreach (ScheduledJobTrigger trigger in _triggers) - { - ScheduledJobTrigger originalTrigger = new ScheduledJobTrigger(trigger); - if (!UpdateTrigger(trigger, newTriggerFrequency)) - { - continue; - } - - ScheduledJobDefinition definition = trigger.JobDefinition; - if (definition != null) - { - bool jobUpdateFailed = false; - - try - { - trigger.UpdateJobDefinition(); - } - catch (ScheduledJobException e) - { - jobUpdateFailed = true; - - string msg = StringUtil.Format(ScheduledJobErrorStrings.CantUpdateTriggerOnJobDef, definition.Name, trigger.Id); - Exception reason = new RuntimeException(msg, e); - ErrorRecord errorRecord = new ErrorRecord(reason, "CantSetPropertiesOnJobTrigger", ErrorCategory.InvalidOperation, trigger); - WriteError(errorRecord); - } - - if (jobUpdateFailed) - { - // Restore trigger to original configuration. - originalTrigger.CopyTo(trigger); - } - } - - if (_passThru) - { - WriteObject(trigger); - } - } - } - - #endregion - - #region Private Methods - - private bool ValidateParameterSet(ref TriggerFrequency newTriggerFrequency) - { - // First see if a switch parameter was set. - List switchParamList = new List(); - if (MyInvocation.BoundParameters.ContainsKey(nameof(AtStartup))) - { - switchParamList.Add(TriggerFrequency.AtStartup); - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(AtLogon))) - { - switchParamList.Add(TriggerFrequency.AtLogon); - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(Once))) - { - switchParamList.Add(TriggerFrequency.Once); - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(Daily))) - { - switchParamList.Add(TriggerFrequency.Daily); - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(Weekly))) - { - switchParamList.Add(TriggerFrequency.Weekly); - } - - if (switchParamList.Count > 1) - { - WriteValidationError(ScheduledJobErrorStrings.ConflictingTypeParams); - return false; - } - - newTriggerFrequency = (switchParamList.Count == 1) ? switchParamList[0] : TriggerFrequency.None; - - // Validate parameters against the new trigger frequency value. - bool rtnValue = false; - switch (newTriggerFrequency) - { - case TriggerFrequency.None: - rtnValue = true; - break; - - case TriggerFrequency.AtStartup: - rtnValue = ValidateStartupParams(); - break; - - case TriggerFrequency.AtLogon: - rtnValue = ValidateLogonParams(); - break; - - case TriggerFrequency.Once: - rtnValue = ValidateOnceParams(); - break; - - case TriggerFrequency.Daily: - rtnValue = ValidateDailyParams(); - break; - - case TriggerFrequency.Weekly: - rtnValue = ValidateWeeklyParams(); - break; - - default: - Debug.Assert(false, "Invalid trigger frequency value."); - rtnValue = false; - break; - } - - return rtnValue; - } - - private bool ValidateStartupParams() - { - if (MyInvocation.BoundParameters.ContainsKey(nameof(DaysInterval))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidDaysInterval, ScheduledJobErrorStrings.TriggerStartUpType); - WriteValidationError(msg); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(WeeksInterval))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidWeeksInterval, ScheduledJobErrorStrings.TriggerStartUpType); - WriteValidationError(msg); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(At))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidAtTime, ScheduledJobErrorStrings.TriggerStartUpType); - WriteValidationError(msg); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(User))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidUser, ScheduledJobErrorStrings.TriggerStartUpType); - WriteValidationError(msg); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(DaysOfWeek))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidDaysOfWeek, ScheduledJobErrorStrings.TriggerStartUpType); - WriteValidationError(msg); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionInterval)) || MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionDuration)) || - MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionInfiniteDuration))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidSetTriggerRepetition, ScheduledJobErrorStrings.TriggerStartUpType); - WriteValidationError(msg); - return false; - } - - return true; - } - - private bool ValidateLogonParams() - { - if (MyInvocation.BoundParameters.ContainsKey(nameof(DaysInterval))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidDaysInterval, ScheduledJobErrorStrings.TriggerLogonType); - WriteValidationError(msg); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(WeeksInterval))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidWeeksInterval, ScheduledJobErrorStrings.TriggerLogonType); - WriteValidationError(msg); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(At))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidAtTime, ScheduledJobErrorStrings.TriggerLogonType); - WriteValidationError(msg); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(DaysOfWeek))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidDaysOfWeek, ScheduledJobErrorStrings.TriggerLogonType); - WriteValidationError(msg); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionInterval)) || MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionDuration)) || - MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionInfiniteDuration))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidSetTriggerRepetition, ScheduledJobErrorStrings.TriggerLogonType); - WriteValidationError(msg); - return false; - } - - return true; - } - - private bool ValidateOnceParams(ScheduledJobTrigger trigger = null) - { - if (MyInvocation.BoundParameters.ContainsKey(nameof(DaysInterval))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidDaysInterval, ScheduledJobErrorStrings.TriggerOnceType); - WriteValidationError(msg); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(WeeksInterval))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidWeeksInterval, ScheduledJobErrorStrings.TriggerOnceType); - WriteValidationError(msg); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(User))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidUser, ScheduledJobErrorStrings.TriggerOnceType); - WriteValidationError(msg); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(DaysOfWeek))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidDaysOfWeek, ScheduledJobErrorStrings.TriggerOnceType); - WriteValidationError(msg); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionInfiniteDuration))) - { - _repDuration = TimeSpan.MaxValue; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionInterval)) || MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionDuration)) || - MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionInfiniteDuration))) - { - // Validate Once trigger repetition parameters. - try - { - ScheduledJobTrigger.ValidateOnceRepetitionParams(_repInterval, _repDuration); - } - catch (PSArgumentException e) - { - WriteValidationError(e.Message); - return false; - } - } - - if (trigger != null) - { - if (trigger.At == null && !MyInvocation.BoundParameters.ContainsKey(nameof(At))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.MissingAtTime, ScheduledJobErrorStrings.TriggerOnceType); - WriteValidationError(msg); - return false; - } - } - - return true; - } - - private bool ValidateDailyParams(ScheduledJobTrigger trigger = null) - { - if (MyInvocation.BoundParameters.ContainsKey(nameof(DaysInterval)) && - _daysInterval < 1) - { - WriteValidationError(ScheduledJobErrorStrings.InvalidDaysIntervalParam); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(WeeksInterval))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidWeeksInterval, ScheduledJobErrorStrings.TriggerDailyType); - WriteValidationError(msg); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(User))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidUser, ScheduledJobErrorStrings.TriggerDailyType); - WriteValidationError(msg); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(DaysOfWeek))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidDaysOfWeek, ScheduledJobErrorStrings.TriggerDailyType); - WriteValidationError(msg); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionInterval)) || MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionDuration)) || - MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionInfiniteDuration))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidSetTriggerRepetition, ScheduledJobErrorStrings.TriggerDailyType); - WriteValidationError(msg); - return false; - } - - if (trigger != null) - { - if (trigger.At == null && !MyInvocation.BoundParameters.ContainsKey(nameof(At))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.MissingAtTime, ScheduledJobErrorStrings.TriggerDailyType); - WriteValidationError(msg); - return false; - } - } - - return true; - } - - private bool ValidateWeeklyParams(ScheduledJobTrigger trigger = null) - { - if (MyInvocation.BoundParameters.ContainsKey(nameof(DaysInterval))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidDaysInterval, ScheduledJobErrorStrings.TriggerWeeklyType); - WriteValidationError(msg); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(WeeksInterval)) && - _weeksInterval < 1) - { - WriteValidationError(ScheduledJobErrorStrings.InvalidWeeksIntervalParam); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(User))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidUser, ScheduledJobErrorStrings.TriggerWeeklyType); - WriteValidationError(msg); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionInterval)) || MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionDuration)) || - MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionInfiniteDuration))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidSetTriggerRepetition, ScheduledJobErrorStrings.TriggerWeeklyType); - WriteValidationError(msg); - return false; - } - - if (trigger != null) - { - if (trigger.At == null && !MyInvocation.BoundParameters.ContainsKey(nameof(At))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.MissingAtTime, ScheduledJobErrorStrings.TriggerDailyType); - WriteValidationError(msg); - return false; - } - - if ((trigger.DaysOfWeek == null || trigger.DaysOfWeek.Count == 0) && - !MyInvocation.BoundParameters.ContainsKey(nameof(DaysOfWeek))) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.MissingDaysOfWeek, ScheduledJobErrorStrings.TriggerDailyType); - WriteValidationError(msg); - return false; - } - } - - return true; - } - - private bool UpdateTrigger(ScheduledJobTrigger trigger, TriggerFrequency triggerFrequency) - { - if (triggerFrequency != TriggerFrequency.None) - { - // - // User has specified a specific trigger type. - // Parameters have been validated for this trigger type. - // - if (triggerFrequency != trigger.Frequency) - { - // Changing to a new trigger type. - return CreateTrigger(trigger, triggerFrequency); - } - else - { - // Modifying existing trigger type. - return ModifyTrigger(trigger, triggerFrequency); - } - } - else - { - // We are updating an existing trigger. Need to validate params - // against each trigger type we are updating. - return ModifyTrigger(trigger, trigger.Frequency, true); - } - } - - private bool CreateTrigger(ScheduledJobTrigger trigger, TriggerFrequency triggerFrequency) - { - switch (triggerFrequency) - { - case TriggerFrequency.AtStartup: - CreateAtStartupTrigger(trigger); - break; - - case TriggerFrequency.AtLogon: - CreateAtLogonTrigger(trigger); - break; - - case TriggerFrequency.Once: - if (trigger.Frequency != triggerFrequency && - !ValidateOnceParams(trigger)) - { - return false; - } - - CreateOnceTrigger(trigger); - break; - - case TriggerFrequency.Daily: - if (trigger.Frequency != triggerFrequency && - !ValidateDailyParams(trigger)) - { - return false; - } - - CreateDailyTrigger(trigger); - break; - - case TriggerFrequency.Weekly: - if (trigger.Frequency != triggerFrequency && - !ValidateWeeklyParams(trigger)) - { - return false; - } - - CreateWeeklyTrigger(trigger); - break; - } - - return true; - } - - private bool ModifyTrigger(ScheduledJobTrigger trigger, TriggerFrequency triggerFrequency, bool validate = false) - { - switch (triggerFrequency) - { - case TriggerFrequency.AtStartup: - if (validate && - !ValidateStartupParams()) - { - return false; - } - - ModifyStartupTrigger(trigger); - break; - - case TriggerFrequency.AtLogon: - if (validate && - !ValidateLogonParams()) - { - return false; - } - - ModifyLogonTrigger(trigger); - break; - - case TriggerFrequency.Once: - if (validate && - !ValidateOnceParams()) - { - return false; - } - - ModifyOnceTrigger(trigger); - break; - - case TriggerFrequency.Daily: - if (validate && - !ValidateDailyParams()) - { - return false; - } - - ModifyDailyTrigger(trigger); - break; - - case TriggerFrequency.Weekly: - if (validate && - !ValidateWeeklyParams()) - { - return false; - } - - ModifyWeeklyTrigger(trigger); - break; - } - - return true; - } - - private void ModifyStartupTrigger(ScheduledJobTrigger trigger) - { - if (MyInvocation.BoundParameters.ContainsKey(nameof(RandomDelay))) - { - trigger.RandomDelay = _randomDelay; - } - } - - private void ModifyLogonTrigger(ScheduledJobTrigger trigger) - { - if (MyInvocation.BoundParameters.ContainsKey(nameof(RandomDelay))) - { - trigger.RandomDelay = _randomDelay; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(User))) - { - trigger.User = string.IsNullOrEmpty(_user) ? ScheduledJobTrigger.AllUsers : _user; - } - } - - private void ModifyOnceTrigger(ScheduledJobTrigger trigger) - { - if (MyInvocation.BoundParameters.ContainsKey(nameof(RandomDelay))) - { - trigger.RandomDelay = _randomDelay; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionInterval))) - { - trigger.RepetitionInterval = _repInterval; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionDuration))) - { - trigger.RepetitionDuration = _repDuration; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(At))) - { - trigger.At = _atTime; - } - } - - private void ModifyDailyTrigger(ScheduledJobTrigger trigger) - { - if (MyInvocation.BoundParameters.ContainsKey(nameof(RandomDelay))) - { - trigger.RandomDelay = _randomDelay; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(At))) - { - trigger.At = _atTime; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(DaysInterval))) - { - trigger.Interval = _daysInterval; - } - } - - private void ModifyWeeklyTrigger(ScheduledJobTrigger trigger) - { - if (MyInvocation.BoundParameters.ContainsKey(nameof(RandomDelay))) - { - trigger.RandomDelay = _randomDelay; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(At))) - { - trigger.At = _atTime; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(WeeksInterval))) - { - trigger.Interval = _weeksInterval; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(DaysOfWeek))) - { - trigger.DaysOfWeek = new List(_daysOfWeek); - } - } - - private void CreateAtLogonTrigger(ScheduledJobTrigger trigger) - { - bool enabled = trigger.Enabled; - int id = trigger.Id; - TimeSpan randomDelay = trigger.RandomDelay; - string user = string.IsNullOrEmpty(trigger.User) ? ScheduledJobTrigger.AllUsers : trigger.User; - - trigger.ClearProperties(); - trigger.Frequency = TriggerFrequency.AtLogon; - trigger.Enabled = enabled; - trigger.Id = id; - - trigger.RandomDelay = MyInvocation.BoundParameters.ContainsKey(nameof(RandomDelay)) ? _randomDelay : randomDelay; - trigger.User = MyInvocation.BoundParameters.ContainsKey(nameof(User)) ? _user : user; - } - - private void CreateAtStartupTrigger(ScheduledJobTrigger trigger) - { - bool enabled = trigger.Enabled; - int id = trigger.Id; - TimeSpan randomDelay = trigger.RandomDelay; - - trigger.ClearProperties(); - trigger.Frequency = TriggerFrequency.AtStartup; - trigger.Enabled = enabled; - trigger.Id = id; - - trigger.RandomDelay = MyInvocation.BoundParameters.ContainsKey(nameof(RandomDelay)) ? _randomDelay : randomDelay; - } - - private void CreateOnceTrigger(ScheduledJobTrigger trigger) - { - bool enabled = trigger.Enabled; - int id = trigger.Id; - TimeSpan randomDelay = trigger.RandomDelay; - DateTime? atTime = trigger.At; - TimeSpan? repInterval = trigger.RepetitionInterval; - TimeSpan? repDuration = trigger.RepetitionDuration; - - trigger.ClearProperties(); - trigger.Frequency = TriggerFrequency.Once; - trigger.Enabled = enabled; - trigger.Id = id; - - trigger.RandomDelay = MyInvocation.BoundParameters.ContainsKey(nameof(RandomDelay)) ? _randomDelay : randomDelay; - trigger.At = MyInvocation.BoundParameters.ContainsKey(nameof(At)) ? _atTime : atTime; - trigger.RepetitionInterval = MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionInterval)) ? _repInterval : repInterval; - trigger.RepetitionDuration = MyInvocation.BoundParameters.ContainsKey(nameof(RepetitionDuration)) ? _repDuration : repDuration; - } - - private void CreateDailyTrigger(ScheduledJobTrigger trigger) - { - bool enabled = trigger.Enabled; - int id = trigger.Id; - TimeSpan randomDelay = trigger.RandomDelay; - DateTime? atTime = trigger.At; - int interval = trigger.Interval; - - trigger.ClearProperties(); - trigger.Frequency = TriggerFrequency.Daily; - trigger.Enabled = enabled; - trigger.Id = id; - - trigger.RandomDelay = MyInvocation.BoundParameters.ContainsKey(nameof(RandomDelay)) ? _randomDelay : randomDelay; - trigger.At = MyInvocation.BoundParameters.ContainsKey(nameof(At)) ? _atTime : atTime; - trigger.Interval = MyInvocation.BoundParameters.ContainsKey(nameof(DaysInterval)) ? _daysInterval : interval; - } - - private void CreateWeeklyTrigger(ScheduledJobTrigger trigger) - { - bool enabled = trigger.Enabled; - int id = trigger.Id; - TimeSpan randomDelay = trigger.RandomDelay; - DateTime? atTime = trigger.At; - int interval = trigger.Interval; - List daysOfWeek = trigger.DaysOfWeek; - - trigger.ClearProperties(); - trigger.Frequency = TriggerFrequency.Weekly; - trigger.Enabled = enabled; - trigger.Id = id; - - trigger.RandomDelay = MyInvocation.BoundParameters.ContainsKey(nameof(RandomDelay)) ? _randomDelay : randomDelay; - trigger.At = MyInvocation.BoundParameters.ContainsKey(nameof(At)) ? _atTime : atTime; - trigger.Interval = MyInvocation.BoundParameters.ContainsKey(nameof(WeeksInterval)) ? _weeksInterval : interval; - trigger.DaysOfWeek = MyInvocation.BoundParameters.ContainsKey(nameof(DaysOfWeek)) ? new List(_daysOfWeek) : daysOfWeek; - } - - private void WriteValidationError(string msg) - { - Exception reason = new RuntimeException(msg); - ErrorRecord errorRecord = new ErrorRecord(reason, "SetJobTriggerParameterValidationError", ErrorCategory.InvalidArgument, null); - WriteError(errorRecord); - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/SetScheduledJobOption.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/SetScheduledJobOption.cs deleted file mode 100644 index bc1e07b1473..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/SetScheduledJobOption.cs +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Management.Automation; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This cmdlet sets the provided scheduled job options to the provided ScheduledJobOptions objects. - /// - [Cmdlet(VerbsCommon.Set, "ScheduledJobOption", DefaultParameterSetName = ScheduledJobOptionCmdletBase.OptionsParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=223921")] - [OutputType(typeof(ScheduledJobOptions))] - public class SetScheduledJobOptionCommand : ScheduledJobOptionCmdletBase - { - #region Parameters - - /// - /// ScheduledJobOptions object. - /// - [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, - ParameterSetName = ScheduledJobOptionCmdletBase.OptionsParameterSet)] - [ValidateNotNull] - public ScheduledJobOptions InputObject - { - get { return _jobOptions; } - - set { _jobOptions = value; } - } - - private ScheduledJobOptions _jobOptions; - - /// - /// Pas the ScheduledJobOptions object through to output. - /// - [Parameter(ParameterSetName = ScheduledJobOptionCmdletBase.OptionsParameterSet)] - public SwitchParameter PassThru - { - get { return _passThru; } - - set { _passThru = value; } - } - - private SwitchParameter _passThru; - - #endregion - - #region Cmdlet Overrides - - /// - /// Process input. - /// - protected override void ProcessRecord() - { - // Update ScheduledJobOptions object with current parameters. - // Update switch parameters only if they were selected. - // Also update the ScheduledJobDefinition object associated with this options object. - if (MyInvocation.BoundParameters.ContainsKey(nameof(StartIfOnBattery))) - { - _jobOptions.StartIfOnBatteries = StartIfOnBattery; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(ContinueIfGoingOnBattery))) - { - _jobOptions.StopIfGoingOnBatteries = !ContinueIfGoingOnBattery; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(WakeToRun))) - { - _jobOptions.WakeToRun = WakeToRun; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(StartIfIdle))) - { - _jobOptions.StartIfNotIdle = !StartIfIdle; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(StopIfGoingOffIdle))) - { - _jobOptions.StopIfGoingOffIdle = StopIfGoingOffIdle; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(RestartOnIdleResume))) - { - _jobOptions.RestartOnIdleResume = RestartOnIdleResume; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(HideInTaskScheduler))) - { - _jobOptions.ShowInTaskScheduler = !HideInTaskScheduler; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(RunElevated))) - { - _jobOptions.RunElevated = RunElevated; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(RequireNetwork))) - { - _jobOptions.RunWithoutNetwork = !RequireNetwork; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(DoNotAllowDemandStart))) - { - _jobOptions.DoNotAllowDemandStart = DoNotAllowDemandStart; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(IdleDuration))) - { - _jobOptions.IdleDuration = IdleDuration; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(IdleTimeout))) - { - _jobOptions.IdleTimeout = IdleTimeout; - } - - if (MyInvocation.BoundParameters.ContainsKey(nameof(MultipleInstancePolicy))) - { - _jobOptions.MultipleInstancePolicy = MultipleInstancePolicy; - } - - // Update ScheduledJobDefinition with changes. - if (_jobOptions.JobDefinition != null) - { - _jobOptions.UpdateJobDefinition(); - } - - if (_passThru) - { - WriteObject(_jobOptions); - } - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/UnregisterJobDefinition.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/UnregisterJobDefinition.cs deleted file mode 100644 index c6c3885fb90..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/UnregisterJobDefinition.cs +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; - -namespace Microsoft.PowerShell.ScheduledJob -{ - /// - /// This cmdlet removes the specified ScheduledJobDefinition objects from the - /// Task Scheduler, job store, and local repository. - /// - [Cmdlet(VerbsLifecycle.Unregister, "ScheduledJob", SupportsShouldProcess = true, DefaultParameterSetName = UnregisterScheduledJobCommand.DefinitionParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=223925")] - public sealed class UnregisterScheduledJobCommand : ScheduleJobCmdletBase - { - #region Parameters - - private const string DefinitionIdParameterSet = "DefinitionId"; - private const string DefinitionNameParameterSet = "DefinitionName"; - private const string DefinitionParameterSet = "Definition"; - - /// - /// ScheduledJobDefinition Id. - /// - [Parameter(Position = 0, Mandatory = true, - ParameterSetName = UnregisterScheduledJobCommand.DefinitionIdParameterSet)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public Int32[] Id - { - get { return _definitionIds; } - - set { _definitionIds = value; } - } - - private Int32[] _definitionIds; - - /// - /// ScheduledJobDefinition Name. - /// - [Parameter(Position = 0, Mandatory = true, - ParameterSetName = UnregisterScheduledJobCommand.DefinitionNameParameterSet)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public string[] Name - { - get { return _names; } - - set { _names = value; } - } - - private string[] _names; - - /// - /// ScheduledJobDefinition. - /// - [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, - ParameterSetName = UnregisterScheduledJobCommand.DefinitionParameterSet)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public ScheduledJobDefinition[] InputObject - { - get { return _definitions; } - - set { _definitions = value; } - } - - private ScheduledJobDefinition[] _definitions; - - /// - /// When true this will stop any running instances of this job definition before - /// removing the definition. - /// - [Parameter(ParameterSetName = UnregisterScheduledJobCommand.DefinitionIdParameterSet)] - [Parameter(ParameterSetName = UnregisterScheduledJobCommand.DefinitionNameParameterSet)] - [Parameter(ParameterSetName = UnregisterScheduledJobCommand.DefinitionParameterSet)] - public SwitchParameter Force - { - get { return _force; } - - set { _force = value; } - } - - private SwitchParameter _force; - - #endregion - - #region Cmdlet Overrides - - /// - /// Process input. - /// - protected override void ProcessRecord() - { - List definitions = null; - switch (ParameterSetName) - { - case DefinitionParameterSet: - definitions = new List(_definitions); - break; - - case DefinitionNameParameterSet: - definitions = GetJobDefinitionsByName(_names); - break; - - case DefinitionIdParameterSet: - definitions = GetJobDefinitionsById(_definitionIds); - break; - } - - if (definitions != null) - { - foreach (ScheduledJobDefinition definition in definitions) - { - string targetString = StringUtil.Format(ScheduledJobErrorStrings.DefinitionWhatIf, definition.Name); - if (ShouldProcess(targetString, VerbsLifecycle.Unregister)) - { - // Removes the ScheduledJobDefinition from the job store, - // Task Scheduler, and disposes the object. - try - { - definition.Remove(_force); - } - catch (ScheduledJobException e) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.CantUnregisterDefinition, definition.Name); - Exception reason = new RuntimeException(msg, e); - ErrorRecord errorRecord = new ErrorRecord(reason, "CantUnregisterScheduledJobDefinition", ErrorCategory.InvalidOperation, definition); - WriteError(errorRecord); - } - } - } - } - - // Check for unknown definition names. - if ((_names != null && _names.Length > 0) && - (_definitions == null || _definitions.Length < _names.Length)) - { - // Make sure there is no PowerShell task in Task Scheduler with removed names. - // This covers the case where the scheduled job definition was manually removed from - // the job store but remains as a PowerShell task in Task Scheduler. - using (ScheduledJobWTS taskScheduler = new ScheduledJobWTS()) - { - foreach (string name in _names) - { - taskScheduler.RemoveTaskByName(name, true, true); - } - } - } - } - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/resources/ScheduledJobErrorStrings.resx b/src/Microsoft.PowerShell.ScheduledJob/resources/ScheduledJobErrorStrings.resx deleted file mode 100644 index 047abfee829..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/resources/ScheduledJobErrorStrings.resx +++ /dev/null @@ -1,387 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Cannot find scheduled job {0}. - {0} is the scheduled job definition name that cannot be found. - - - Cannot find scheduled job definition {0} in the Task Scheduler. - - - The scheduled job definition {0} cannot be removed because one or more instances are currently running. You can remove and stop all running instances by using the Force parameter. - - - An error occurred while adding triggers to the scheduled job {0}. - - - There is no entry in Task Scheduler for scheduled job definition {0}. A new Task Scheduler entry has been created for this scheduled job definition. - - - Cannot get the {0} scheduled job because it is corrupted or in an irresolvable state. Because it cannot run, PowerShell has deleted {0} and its results from the computer. To recreate the scheduled job, use the Register-ScheduledJob cmdlet. For more information about corrupted scheduled jobs, see about_Scheduled_Jobs_Troubleshooting. - {0} is the name of the scheduled job definition. - - - An error occurred while loading job run results for scheduled job {0} with job run date {1}. - - - An error occurred while registering the scheduled job {0}. - - - An error occurred while removing job triggers from scheduled job {0}. - - - One or more scheduled job runs could not be retrieved {0}. - - - Job {0} cannot be saved because no file path was specified. - - - Job {0} has not been run and cannot be saved. Run the job first, and then save results. - - - An error occurred while enabling or disabling the scheduled job {0}. - - - An error occurred while setting properties on the scheduled job {0}. - - - Cannot start a job from the {0} scheduled job definition. - - - An error occurred while unregistering the scheduled job {0}. - - - An error occurred while updating the scheduled job definition {0} with this trigger {1}. See exception details for more information. - - - Only one JobTrigger type can be specified: AtStartup, AtLogon, Once, Daily, or Weekly. - - - A scheduled job definition object {0} already exists in the local scheduled job repository having this Global ID {1}. - - - A scheduled job definition object with Global ID {0} could not be found. - - - A scheduled job definition with ID {0} could not be found. - - - A scheduled job definition with Name {0} could not be found. - - - This scheduled job definition object {0} has been disposed. - - - Scheduled job definition {0}. - - - A directory not found error occurred while registering scheduled job definition {0}. Make sure you are running PowerShell with elevated privileges. - - - An error occurred while registering scheduled job definition {0}. Cannot add this definition object to the job store. - - - An error occurred while registering scheduled job definition {0} to the Windows Task Scheduler. The Task Scheduler error is: {1}. - - - An error occurred while unregistering scheduled job definition {0}. - - - An error occurred while setting file access permissions for job definition {0} and user {1}. - - - An error occurred while updating scheduled job definition {0}. Cannot update this definition in the job store. - - - An error occurred while updating scheduled job definition {0}. Cannot update this definition with the Windows Task Scheduler. - - - An error has occurred within the Task Scheduler. - - - The At parameter is not valid for the {0} job trigger type. - - - The DaysInterval parameter is not valid for the {0} job trigger type. - - - The DaysInterval parameter value must be greater than zero. - - - The DaysOfWeek parameter is not valid for the {0} job trigger type. - - - The FilePath parameter is not valid. - - - Only PowerShell script files are allowed for FilePath parameter. Specify a file with .ps1 extension. - - - The IdleDuration parameter cannot have a negative value. - - - The IdleTimeout parameter cannot have a negative value. - - - The scheduled job definition name {0} contains characters that are not valid. - - - The MaxResultCount parameter cannot have a negative or zero value. - - - The User parameter is not valid for the {0} job trigger type. - - - The WeeksInterval parameter is not valid for the {0} job trigger type. - - - The WeeksInterval parameter value must be greater than zero. - - - An I/O failure occurred while updating the scheduled job definition {0}. This could mean a file is missing or corrupted, either in Task Scheduler or in the PowerShell scheduled job store. You might need to create the scheduled job definition again. - {0} is the scheduled job definition name - - - Job {0} is currently running. - - - The scheduled job definition {0} already exists in the job definition store. - - - The scheduled job results {0} already exist in the job results store. - - - The At parameter is required for the {0} job trigger type. - - - The DaysOfWeek parameter is required for the {0} job trigger type. - - - The job trigger {0} requires the DaysOfWeek parameter to be defined. - - - The Job trigger {0} requires the At parameter to be defined. - - - No Frequency type has been specified for this job trigger. One of the following job trigger frequencies must be specified: AtStartup, AtLogon, Once, Daily, Weekly. - - - An access denied error occurred while updating the scheduled job definition {0}. Try running PowerShell with elevated user rights; that is, Run as Administrator. - {0} is the scheduled job definition name - - - There is no scheduled job definition object associated with this options object. - - - There is no scheduled job definition object associated with this trigger {0}. - - - The scheduled job {0} already exists in the local repository. - - - The scheduled job {0} is not in the local job repository. - - - The scheduled job definition {0} already exists in Task Scheduler. - - - Daily - - - AtLogon - - - A scheduled job trigger with ID {0} was not found for the scheduled job definition {1}. - - - Once - - - AtStartup - - - Weekly - - - An access denied error occurred when registering scheduled job definition {0}. Try running PowerShell with elevated user rights; that is, Run As Administrator. - - - Cannot convert a ScheduledJobTrigger object with TriggerFrequency value of {0}. - - - An unknown trigger type was returned from Task Scheduler for scheduled job definition {0} with trigger ID {1}. - - - The scheduled job definition {0} could not be saved because one of the values in the ArgumentList parameter cannot be converted to XML. If possible, change the ArgumentList values to types that are easily converted to XML, such as strings, integers, and hash tables. {1} - {0} is the name of the scheduled job definition that cannot be registered -{1} is the inner exception message from .Net serialization, or empty if no exception message. - - - Commands that interact with the host program, such as Write-Host, cannot be included in PowerShell scheduled jobs because scheduled jobs do not interact with the host program. Use an alternate command that does not interact with the host program, such as Write-Output or Out-File. - - - The RepetitionInterval parameter value must be less than or equal to the RepetitionDuration parameter value. - - - The RepetitionInterval parameter value must be greater than 1 minute. - - - The RepetitionInterval and RepetitionDuration Job trigger parameters must be specified together. - - - The Repetition parameters cannot have negative values. - - - The Repetition parameters are not valid for the {0} job trigger type. - - - The RepetitionInterval parameter cannot have a value of zero unless the RepetitionDuration parameter also has a zero value. A zero value removes repetition behavior from the Job trigger. - - - An error occurred while attempting to rename scheduled job from {0} to {1}. - - - An error occurred while attempting to rename scheduled job from {0} to {1} with error message: {2}. - - - An unrecoverable error occurred while renaming the scheduled job from {0} to {1}. The scheduled job will be removed. - - - An unrecoverable error occurred while renaming the scheduled job from {0} to {1} with message {2}. The scheduled job will be removed. - - - An error occurred while running scheduled job definition {0} from the Task Scheduler. - - - An error occurred while running scheduled job definition {0} from the Task Scheduler because {1}. - - - You cannot specify the RepetitionDuration and RepeatIndefinitely parameters in the same command. - - - When you use the RepeatIndefinitely parameter, the RepetitionInterval parameter is required. - - - the scheduled job definition could not be found - - - the scheduled job definition is disabled - - diff --git a/src/Microsoft.PowerShell.Security/resources/CertificateProviderStrings.resx b/src/Microsoft.PowerShell.Security/resources/CertificateProviderStrings.resx index ded11aab0a2..c45c640bbb0 100644 --- a/src/Microsoft.PowerShell.Security/resources/CertificateProviderStrings.resx +++ b/src/Microsoft.PowerShell.Security/resources/CertificateProviderStrings.resx @@ -144,9 +144,6 @@ Invoke Certificate Manager - - {0} is not supported in the current operating system. - Item: {0} Destination: {1} diff --git a/src/Microsoft.PowerShell.Security/resources/SignatureCommands.resx b/src/Microsoft.PowerShell.Security/resources/SignatureCommands.resx index 53f5371fc4c..cbbbd5e5712 100644 --- a/src/Microsoft.PowerShell.Security/resources/SignatureCommands.resx +++ b/src/Microsoft.PowerShell.Security/resources/SignatureCommands.resx @@ -121,7 +121,7 @@ Cannot sign code. The specified certificate is not suitable for code signing. - Cannot sign code. The TimeStamp server URL must be fully qualified in the form of http://<server url> + Cannot sign code. The TimeStamp server URL must be fully qualified in the form of http://<server url> or https://<server url>. The Get-AuthenticodeSignature cmdlet does not support directories. Supply a path to a file and retry. diff --git a/src/Microsoft.PowerShell.Security/security/AclCommands.cs b/src/Microsoft.PowerShell.Security/security/AclCommands.cs index c0994a57a53..1b5beffa49e 100644 --- a/src/Microsoft.PowerShell.Security/security/AclCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/AclCommands.cs @@ -291,8 +291,7 @@ public static AuthorizationRuleCollection GetAccess(PSObject instance) } // Get DACL - CommonObjectSecurity cos = sd as CommonObjectSecurity; - if (cos != null) + if (sd is CommonObjectSecurity cos) { return cos.GetAccessRules(true, true, typeof(NTAccount)); } @@ -326,8 +325,7 @@ public static AuthorizationRuleCollection GetAudit(PSObject instance) PSTraceSource.NewArgumentException(nameof(instance)); } - CommonObjectSecurity cos = sd as CommonObjectSecurity; - if (cos != null) + if (sd is CommonObjectSecurity cos) { return cos.GetAuditRules(true, true, typeof(NTAccount)); } @@ -625,7 +623,7 @@ public GetAclCommand() /// security descriptor. Default is the current location. /// [Parameter(Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "ByPath")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public string[] Path { get @@ -664,8 +662,8 @@ public PSObject InputObject /// security descriptor. Default is the current location. /// [Parameter(ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "ByLiteralPath")] - [Alias("PSPath")] - [ValidateNotNullOrEmpty()] + [Alias("PSPath", "LP")] + [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] LiteralPath { @@ -687,7 +685,7 @@ public string[] LiteralPath /// Gets or sets the audit flag of the command. This flag /// determines if audit rules should also be retrieved. /// - [Parameter()] + [Parameter] public SwitchParameter Audit { get @@ -718,7 +716,7 @@ private SwitchParameter AllCentralAccessPolicies /// determines whether the information about all central access policies /// available on the machine should be displayed. /// - [Parameter()] + [Parameter] public SwitchParameter AllCentralAccessPolicies { get @@ -931,7 +929,7 @@ public PSObject InputObject /// security descriptor. /// [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "ByLiteralPath")] - [Alias("PSPath")] + [Alias("PSPath", "LP")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] LiteralPath { @@ -1027,7 +1025,7 @@ public SwitchParameter ClearCentralAccessPolicy /// If true, the security descriptor is also passed /// down the output pipeline. /// - [Parameter()] + [Parameter] public SwitchParameter Passthru { get diff --git a/src/Microsoft.PowerShell.Security/security/CatalogCommands.cs b/src/Microsoft.PowerShell.Security/security/CatalogCommands.cs index a86bf657853..4d0cad2d3e9 100644 --- a/src/Microsoft.PowerShell.Security/security/CatalogCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/CatalogCommands.cs @@ -145,7 +145,7 @@ public NewFileCatalogCommand() : base("New-FileCatalog") { } /// /// Catalog version. /// - [Parameter()] + [Parameter] public int CatalogVersion { get @@ -160,7 +160,7 @@ public int CatalogVersion } // Based on the Catalog version we will decide which hashing Algorithm to use - private int catalogVersion = 1; + private int catalogVersion = 2; /// /// Generate the Catalog for the Path. @@ -223,7 +223,7 @@ public TestFileCatalogCommand() : base("Test-FileCatalog") { } /// /// - [Parameter()] + [Parameter] public SwitchParameter Detailed { get { return detailed; } @@ -236,7 +236,7 @@ public SwitchParameter Detailed /// /// Patterns used to exclude files from DiskPaths and Catalog. /// - [Parameter()] + [Parameter] public string[] FilesToSkip { get diff --git a/src/Microsoft.PowerShell.Security/security/CertificateCommands.cs b/src/Microsoft.PowerShell.Security/security/CertificateCommands.cs index 4723aa7fbf0..e3a386ee507 100644 --- a/src/Microsoft.PowerShell.Security/security/CertificateCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/CertificateCommands.cs @@ -44,7 +44,7 @@ public string[] FilePath /// certificate. /// [Parameter(ValueFromPipelineByPropertyName = true, Mandatory = true, ParameterSetName = "ByLiteralPath")] - [Alias("PSPath")] + [Alias("PSPath", "LP")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] LiteralPath { @@ -208,8 +208,11 @@ protected override void ProcessRecord() private static X509Certificate2 GetCertFromPfxFile(string path, SecureString password) { + // No overload found in X509CertificateLoader that takes SecureString + #pragma warning disable SYSLIB0057 var cert = new X509Certificate2(path, password, X509KeyStorageFlags.DefaultKeySet); return cert; + #pragma warning restore SYSLIB0057 } } } diff --git a/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs b/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs index d0c71e82260..2a05a9bf30d 100644 --- a/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs +++ b/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs @@ -26,7 +26,7 @@ using Dbg = System.Management.Automation; using DWORD = System.UInt32; using Runspaces = System.Management.Automation.Runspaces; -using Security = System.Management.Automation.Security; +using SMASecurity = System.Management.Automation.Security; namespace Microsoft.PowerShell.Commands { @@ -214,9 +214,9 @@ public override string ToString() // to differ only by upper/lower case. If they do, that's really // a code bug, and the effect is to just display both strings. - return string.Equals(_punycodeName, _unicodeName) ? - _punycodeName : - _unicodeName + " (" + _punycodeName + ")"; + return string.Equals(_punycodeName, _unicodeName, StringComparison.Ordinal) + ? _punycodeName + : _unicodeName + " (" + _punycodeName + ")"; } } @@ -275,7 +275,7 @@ protected override bool ReleaseHandle() if (handle != IntPtr.Zero) { - fResult = Security.NativeMethods.CertCloseStore(handle, 0); + fResult = SMASecurity.NativeMethods.CertCloseStore(handle, 0); handle = IntPtr.Zero; } @@ -318,25 +318,25 @@ public void Open(bool includeArchivedCerts) _valid = false; _open = false; - Security.NativeMethods.CertOpenStoreFlags StoreFlags = - Security.NativeMethods.CertOpenStoreFlags.CERT_STORE_SHARE_STORE_FLAG | - Security.NativeMethods.CertOpenStoreFlags.CERT_STORE_SHARE_CONTEXT_FLAG | - Security.NativeMethods.CertOpenStoreFlags.CERT_STORE_OPEN_EXISTING_FLAG | - Security.NativeMethods.CertOpenStoreFlags.CERT_STORE_MAXIMUM_ALLOWED_FLAG; + SMASecurity.NativeMethods.CertOpenStoreFlags StoreFlags = + SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_STORE_SHARE_STORE_FLAG | + SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_STORE_SHARE_CONTEXT_FLAG | + SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_STORE_OPEN_EXISTING_FLAG | + SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_STORE_MAXIMUM_ALLOWED_FLAG; if (includeArchivedCerts) { - StoreFlags |= Security.NativeMethods.CertOpenStoreFlags.CERT_STORE_ENUM_ARCHIVED_FLAG; + StoreFlags |= SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_STORE_ENUM_ARCHIVED_FLAG; } switch (_storeLocation.Location) { case StoreLocation.LocalMachine: - StoreFlags |= Security.NativeMethods.CertOpenStoreFlags.CERT_SYSTEM_STORE_LOCAL_MACHINE; + StoreFlags |= SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_SYSTEM_STORE_LOCAL_MACHINE; break; case StoreLocation.CurrentUser: - StoreFlags |= Security.NativeMethods.CertOpenStoreFlags.CERT_SYSTEM_STORE_CURRENT_USER; + StoreFlags |= SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_SYSTEM_STORE_CURRENT_USER; break; default: @@ -344,9 +344,9 @@ public void Open(bool includeArchivedCerts) break; } - IntPtr hCertStore = Security.NativeMethods.CertOpenStore( - Security.NativeMethods.CertOpenStoreProvider.CERT_STORE_PROV_SYSTEM, - Security.NativeMethods.CertOpenStoreEncodingType.X509_ASN_ENCODING, + IntPtr hCertStore = SMASecurity.NativeMethods.CertOpenStore( + SMASecurity.NativeMethods.CertOpenStoreProvider.CERT_STORE_PROV_SYSTEM, + SMASecurity.NativeMethods.CertOpenStoreEncodingType.X509_ASN_ENCODING, IntPtr.Zero, // hCryptProv StoreFlags, _storeName); @@ -364,10 +364,10 @@ public void Open(bool includeArchivedCerts) "UserDS", StringComparison.OrdinalIgnoreCase)) { - if (!Security.NativeMethods.CertControlStore( + if (!SMASecurity.NativeMethods.CertControlStore( _storeHandle.Handle, 0, - Security.NativeMethods.CertControlStoreType.CERT_STORE_CTRL_AUTO_RESYNC, + SMASecurity.NativeMethods.CertControlStoreType.CERT_STORE_CTRL_AUTO_RESYNC, IntPtr.Zero)) { _storeHandle = null; @@ -391,12 +391,12 @@ public IntPtr GetNextCert(IntPtr certContext) if (!_open) { throw Marshal.GetExceptionForHR( - Security.NativeMethods.CRYPT_E_NOT_FOUND); + SMASecurity.NativeMethods.CRYPT_E_NOT_FOUND); } if (Valid) { - certContext = Security.NativeMethods.CertEnumCertificatesInStore( + certContext = SMASecurity.NativeMethods.CertEnumCertificatesInStore( _storeHandle.Handle, certContext); } @@ -415,18 +415,18 @@ public IntPtr GetCertByName(string Name) if (!_open) { throw Marshal.GetExceptionForHR( - Security.NativeMethods.CRYPT_E_NOT_FOUND); + SMASecurity.NativeMethods.CRYPT_E_NOT_FOUND); } if (Valid) { if (DownLevelHelper.HashLookupSupported()) { - certContext = Security.NativeMethods.CertFindCertificateInStore( + certContext = SMASecurity.NativeMethods.CertFindCertificateInStore( _storeHandle.Handle, - Security.NativeMethods.CertOpenStoreEncodingType.X509_ASN_ENCODING, + SMASecurity.NativeMethods.CertOpenStoreEncodingType.X509_ASN_ENCODING, 0, // dwFindFlags - Security.NativeMethods.CertFindType.CERT_FIND_HASH_STR, + SMASecurity.NativeMethods.CertFindType.CERT_FIND_HASH_STR, Name, IntPtr.Zero); // pPrevCertContext } @@ -464,7 +464,7 @@ public IntPtr GetCertByName(string Name) public void FreeCert(IntPtr certContext) { - Security.NativeMethods.CertFreeCertificateContext(certContext); + SMASecurity.NativeMethods.CertFreeCertificateContext(certContext); } /// @@ -556,6 +556,7 @@ internal enum CertificateProviderItem [CmdletProvider("Certificate", ProviderCapabilities.ShouldProcess)] [OutputType(typeof(string), typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.ResolvePath)] [OutputType(typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.PushLocation)] + [OutputType(typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.PopLocation)] [OutputType(typeof(Microsoft.PowerShell.Commands.X509StoreLocation), typeof(X509Certificate2), ProviderCmdlet = ProviderCmdlet.GetItem)] [OutputType(typeof(X509Store), typeof(X509Certificate2), ProviderCmdlet = ProviderCmdlet.GetChildItem)] public sealed class CertificateProvider : NavigationCmdletProvider, ICmdletProviderSupportsHelp @@ -723,16 +724,11 @@ protected override void RemoveItem( ThrowInvalidOperation(errorId, message); } - if (DynamicParameters != null) + if (DynamicParameters != null && DynamicParameters is ProviderRemoveItemDynamicParameters dp) { - ProviderRemoveItemDynamicParameters dp = - DynamicParameters as ProviderRemoveItemDynamicParameters; - if (dp != null) + if (dp.DeleteKey) { - if (dp.DeleteKey) - { - fDeleteKey = true; - } + fDeleteKey = true; } } @@ -887,9 +883,8 @@ protected override void MoveItem( object store = GetItemAtPath(destination, false, out isDestContainer); X509Certificate2 certificate = cert as X509Certificate2; - X509NativeStore certstore = store as X509NativeStore; - if (certstore != null) + if (store is X509NativeStore certstore) { certstore.Open(true); @@ -925,7 +920,7 @@ protected override void MoveItem( /// /// The path of the certificate store to create. /// - /// + /// /// Ignored. /// Only support store. /// @@ -973,15 +968,15 @@ protected override void NewItem( ThrowInvalidOperation(errorId, message); } - const Security.NativeMethods.CertOpenStoreFlags StoreFlags = - Security.NativeMethods.CertOpenStoreFlags.CERT_STORE_CREATE_NEW_FLAG | - Security.NativeMethods.CertOpenStoreFlags.CERT_STORE_MAXIMUM_ALLOWED_FLAG | - Security.NativeMethods.CertOpenStoreFlags.CERT_SYSTEM_STORE_LOCAL_MACHINE; + const SMASecurity.NativeMethods.CertOpenStoreFlags StoreFlags = + SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_STORE_CREATE_NEW_FLAG | + SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_STORE_MAXIMUM_ALLOWED_FLAG | + SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_SYSTEM_STORE_LOCAL_MACHINE; // Create new store - IntPtr hCertStore = Security.NativeMethods.CertOpenStore( - Security.NativeMethods.CertOpenStoreProvider.CERT_STORE_PROV_SYSTEM, - Security.NativeMethods.CertOpenStoreEncodingType.X509_ASN_ENCODING, + IntPtr hCertStore = SMASecurity.NativeMethods.CertOpenStore( + SMASecurity.NativeMethods.CertOpenStoreProvider.CERT_STORE_PROV_SYSTEM, + SMASecurity.NativeMethods.CertOpenStoreEncodingType.X509_ASN_ENCODING, IntPtr.Zero, // hCryptProv StoreFlags, pathElements[1]); @@ -992,7 +987,7 @@ protected override void NewItem( else // free native store handle { bool fResult = false; - fResult = Security.NativeMethods.CertCloseStore(hCertStore, 0); + fResult = SMASecurity.NativeMethods.CertCloseStore(hCertStore, 0); } X509Store outStore = new(pathElements[1], StoreLocation.LocalMachine); @@ -1067,23 +1062,18 @@ protected override bool HasChildItems(string path) if ((item != null) && isContainer) { - X509StoreLocation storeLocation = item as X509StoreLocation; - if (storeLocation != null) + if (item is X509StoreLocation storeLocation) { result = storeLocation.StoreNames.Count > 0; } - else + else if (item is X509NativeStore store) { - X509NativeStore store = item as X509NativeStore; - if (store != null) + store.Open(IncludeArchivedCerts()); + IntPtr certContext = store.GetFirstCert(); + if (certContext != IntPtr.Zero) { - store.Open(IncludeArchivedCerts()); - IntPtr certContext = store.GetFirstCert(); - if (certContext != IntPtr.Zero) - { - store.FreeCert(certContext); - result = true; - } + store.FreeCert(certContext); + result = true; } } } @@ -1258,20 +1248,15 @@ protected override void GetItem(string path) return; } - X509StoreLocation storeLocation = item as X509StoreLocation; - if (storeLocation != null) // store location + if (item is X509StoreLocation storeLocation) // store location { WriteItemObject(item, path, isContainer); } - else // store + else if (item is X509NativeStore store) // store { - X509NativeStore store = item as X509NativeStore; - if (store != null) - { - // create X509Store - X509Store outStore = new(store.StoreName, store.Location.Location); - WriteItemObject(outStore, path, isContainer); - } + // create X509Store + X509Store outStore = new(store.StoreName, store.Location.Location); + WriteItemObject(outStore, path, isContainer); } } } @@ -1567,7 +1552,7 @@ private static string NormalizePath(string path) string[] elts = GetPathElements(path); - path = string.Join("\\", elts); + path = string.Join('\\', elts); } return path; @@ -1615,8 +1600,8 @@ private static string[] GetPathElements(string path) private void DoDeleteKey(IntPtr pProvInfo) { IntPtr hProv = IntPtr.Zero; - Security.NativeMethods.CRYPT_KEY_PROV_INFO keyProvInfo = - Marshal.PtrToStructure(pProvInfo); + SMASecurity.NativeMethods.CRYPT_KEY_PROV_INFO keyProvInfo = + Marshal.PtrToStructure(pProvInfo); IntPtr hWnd = DetectUIHelper.GetOwnerWindow(Host); @@ -1624,33 +1609,33 @@ private void DoDeleteKey(IntPtr pProvInfo) { if (hWnd != IntPtr.Zero) { - if (Security.NativeMethods.CryptAcquireContext( + if (SMASecurity.NativeMethods.CryptAcquireContext( ref hProv, keyProvInfo.pwszContainerName, keyProvInfo.pwszProvName, (int)keyProvInfo.dwProvType, - (uint)Security.NativeMethods.ProviderFlagsEnum.CRYPT_VERIFYCONTEXT)) + (uint)SMASecurity.NativeMethods.ProviderFlagsEnum.CRYPT_VERIFYCONTEXT)) { unsafe { void* pWnd = hWnd.ToPointer(); - Security.NativeMethods.CryptSetProvParam( + SMASecurity.NativeMethods.CryptSetProvParam( hProv, - Security.NativeMethods.ProviderParam.PP_CLIENT_HWND, + SMASecurity.NativeMethods.ProviderParam.PP_CLIENT_HWND, &pWnd, 0); - Security.NativeMethods.CryptReleaseContext(hProv, 0); + SMASecurity.NativeMethods.CryptReleaseContext(hProv, 0); } } } - if (!Security.NativeMethods.CryptAcquireContext( + if (!SMASecurity.NativeMethods.CryptAcquireContext( ref hProv, keyProvInfo.pwszContainerName, keyProvInfo.pwszProvName, (int)keyProvInfo.dwProvType, - keyProvInfo.dwFlags | (uint)Security.NativeMethods.ProviderFlagsEnum.CRYPT_DELETEKEYSET | - (hWnd == IntPtr.Zero ? (uint)Security.NativeMethods.ProviderFlagsEnum.CRYPT_SILENT : 0))) + keyProvInfo.dwFlags | (uint)SMASecurity.NativeMethods.ProviderFlagsEnum.CRYPT_DELETEKEYSET | + (hWnd == IntPtr.Zero ? (uint)SMASecurity.NativeMethods.ProviderFlagsEnum.CRYPT_SILENT : 0))) { ThrowErrorRemoting(Marshal.GetLastWin32Error()); } @@ -1663,21 +1648,21 @@ private void DoDeleteKey(IntPtr pProvInfo) IntPtr hCNGProv = IntPtr.Zero; IntPtr hCNGKey = IntPtr.Zero; - if ((keyProvInfo.dwFlags & (uint)Security.NativeMethods.ProviderFlagsEnum.CRYPT_MACHINE_KEYSET) != 0) + if ((keyProvInfo.dwFlags & (uint)SMASecurity.NativeMethods.ProviderFlagsEnum.CRYPT_MACHINE_KEYSET) != 0) { - cngKeyFlag = (uint)Security.NativeMethods.NCryptDeletKeyFlag.NCRYPT_MACHINE_KEY_FLAG; + cngKeyFlag = (uint)SMASecurity.NativeMethods.NCryptDeletKeyFlag.NCRYPT_MACHINE_KEY_FLAG; } if (hWnd == IntPtr.Zero || - (keyProvInfo.dwFlags & (uint)Security.NativeMethods.ProviderFlagsEnum.CRYPT_SILENT) != 0) + (keyProvInfo.dwFlags & (uint)SMASecurity.NativeMethods.ProviderFlagsEnum.CRYPT_SILENT) != 0) { - cngKeyFlag |= (uint)Security.NativeMethods.NCryptDeletKeyFlag.NCRYPT_SILENT_FLAG; + cngKeyFlag |= (uint)SMASecurity.NativeMethods.NCryptDeletKeyFlag.NCRYPT_SILENT_FLAG; } int stat = 0; try { - stat = Security.NativeMethods.NCryptOpenStorageProvider( + stat = SMASecurity.NativeMethods.NCryptOpenStorageProvider( ref hCNGProv, keyProvInfo.pwszProvName, 0); @@ -1686,7 +1671,7 @@ private void DoDeleteKey(IntPtr pProvInfo) ThrowErrorRemoting(stat); } - stat = Security.NativeMethods.NCryptOpenKey( + stat = SMASecurity.NativeMethods.NCryptOpenKey( hCNGProv, ref hCNGKey, keyProvInfo.pwszContainerName, @@ -1697,21 +1682,21 @@ private void DoDeleteKey(IntPtr pProvInfo) ThrowErrorRemoting(stat); } - if ((cngKeyFlag & (uint)Security.NativeMethods.NCryptDeletKeyFlag.NCRYPT_SILENT_FLAG) != 0) + if ((cngKeyFlag & (uint)SMASecurity.NativeMethods.NCryptDeletKeyFlag.NCRYPT_SILENT_FLAG) != 0) { unsafe { void* pWnd = hWnd.ToPointer(); - Security.NativeMethods.NCryptSetProperty( + SMASecurity.NativeMethods.NCryptSetProperty( hCNGProv, - Security.NativeMethods.NCRYPT_WINDOW_HANDLE_PROPERTY, + SMASecurity.NativeMethods.NCRYPT_WINDOW_HANDLE_PROPERTY, &pWnd, sizeof(void*), 0); // dwFlags } } - stat = Security.NativeMethods.NCryptDeleteKey(hCNGKey, 0); + stat = SMASecurity.NativeMethods.NCryptDeleteKey(hCNGKey, 0); if (stat != 0) { ThrowErrorRemoting(stat); @@ -1722,10 +1707,10 @@ private void DoDeleteKey(IntPtr pProvInfo) finally { if (hCNGProv != IntPtr.Zero) - result = Security.NativeMethods.NCryptFreeObject(hCNGProv); + result = SMASecurity.NativeMethods.NCryptFreeObject(hCNGProv); if (hCNGKey != IntPtr.Zero) - result = Security.NativeMethods.NCryptFreeObject(hCNGKey); + result = SMASecurity.NativeMethods.NCryptFreeObject(hCNGKey); } } } @@ -1741,7 +1726,7 @@ private void DoDeleteKey(IntPtr pProvInfo) private void RemoveCertStore(string storeName, bool fDeleteKey, string sourcePath) { // if recurse is true, remove every cert in the store - IntPtr localName = Security.NativeMethods.CryptFindLocalizedName(storeName); + IntPtr localName = SMASecurity.NativeMethods.CryptFindLocalizedName(storeName); string[] pathElements = GetPathElements(sourcePath); if (localName == IntPtr.Zero)//not find, we can remove { @@ -1766,17 +1751,17 @@ private void RemoveCertStore(string storeName, bool fDeleteKey, string sourcePat certContext = store.GetNextCert(certContext); } // remove the cert store - const Security.NativeMethods.CertOpenStoreFlags StoreFlags = - Security.NativeMethods.CertOpenStoreFlags.CERT_STORE_READONLY_FLAG | - Security.NativeMethods.CertOpenStoreFlags.CERT_STORE_OPEN_EXISTING_FLAG | - Security.NativeMethods.CertOpenStoreFlags.CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG | - Security.NativeMethods.CertOpenStoreFlags.CERT_STORE_DELETE_FLAG | - Security.NativeMethods.CertOpenStoreFlags.CERT_SYSTEM_STORE_LOCAL_MACHINE; + const SMASecurity.NativeMethods.CertOpenStoreFlags StoreFlags = + SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_STORE_READONLY_FLAG | + SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_STORE_OPEN_EXISTING_FLAG | + SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG | + SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_STORE_DELETE_FLAG | + SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_SYSTEM_STORE_LOCAL_MACHINE; // delete store - IntPtr hCertStore = Security.NativeMethods.CertOpenStore( - Security.NativeMethods.CertOpenStoreProvider.CERT_STORE_PROV_SYSTEM, - Security.NativeMethods.CertOpenStoreEncodingType.X509_ASN_ENCODING, + IntPtr hCertStore = SMASecurity.NativeMethods.CertOpenStore( + SMASecurity.NativeMethods.CertOpenStoreProvider.CERT_STORE_PROV_SYSTEM, + SMASecurity.NativeMethods.CertOpenStoreEncodingType.X509_ASN_ENCODING, IntPtr.Zero, // hCryptProv StoreFlags, storeName); @@ -1848,17 +1833,17 @@ private void DoRemove(X509Certificate2 cert, bool fDeleteKey, bool fMachine, str if (fDeleteKey) { // it is fine if below call fails - if (Security.NativeMethods.CertGetCertificateContextProperty( + if (SMASecurity.NativeMethods.CertGetCertificateContextProperty( cert.Handle, - Security.NativeMethods.CertPropertyId.CERT_KEY_PROV_INFO_PROP_ID, + SMASecurity.NativeMethods.CertPropertyId.CERT_KEY_PROV_INFO_PROP_ID, IntPtr.Zero, ref provSize)) { pProvInfo = Marshal.AllocHGlobal((int)provSize); - if (Security.NativeMethods.CertGetCertificateContextProperty( + if (SMASecurity.NativeMethods.CertGetCertificateContextProperty( cert.Handle, - Security.NativeMethods.CertPropertyId.CERT_KEY_PROV_INFO_PROP_ID, + SMASecurity.NativeMethods.CertPropertyId.CERT_KEY_PROV_INFO_PROP_ID, pProvInfo, ref provSize)) { @@ -1878,8 +1863,8 @@ private void DoRemove(X509Certificate2 cert, bool fDeleteKey, bool fMachine, str // do remove certificate // should not use the original handle - if (!Security.NativeMethods.CertDeleteCertificateFromStore( - Security.NativeMethods.CertDuplicateCertificateContext(cert.Handle))) + if (!SMASecurity.NativeMethods.CertDeleteCertificateFromStore( + SMASecurity.NativeMethods.CertDuplicateCertificateContext(cert.Handle))) { throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } @@ -1887,8 +1872,8 @@ private void DoRemove(X509Certificate2 cert, bool fDeleteKey, bool fMachine, str // commit the change to physical store if (sourcePath.Contains("UserDS")) { - Security.NativeMethods.CERT_CONTEXT context = - Marshal.PtrToStructure(cert.Handle); + SMASecurity.NativeMethods.CERT_CONTEXT context = + Marshal.PtrToStructure(cert.Handle); CommitUserDS(context.hCertStore); } @@ -1915,10 +1900,10 @@ private void DoRemove(X509Certificate2 cert, bool fDeleteKey, bool fMachine, str /// No return. private static void CommitUserDS(IntPtr storeHandle) { - if (!Security.NativeMethods.CertControlStore( + if (!SMASecurity.NativeMethods.CertControlStore( storeHandle, 0, - Security.NativeMethods.CertControlStoreType.CERT_STORE_CTRL_COMMIT, + SMASecurity.NativeMethods.CertControlStoreType.CERT_STORE_CTRL_COMMIT, IntPtr.Zero)) { throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); @@ -1940,7 +1925,7 @@ private void DoMove(string destination, X509Certificate2 cert, X509NativeStore s IntPtr outCert = IntPtr.Zero; // duplicate cert first - dupCert = Security.NativeMethods.CertDuplicateCertificateContext(cert.Handle); + dupCert = SMASecurity.NativeMethods.CertDuplicateCertificateContext(cert.Handle); if (dupCert == IntPtr.Zero) { @@ -1948,16 +1933,16 @@ private void DoMove(string destination, X509Certificate2 cert, X509NativeStore s } else { - if (!Security.NativeMethods.CertAddCertificateContextToStore( + if (!SMASecurity.NativeMethods.CertAddCertificateContextToStore( store.StoreHandle, cert.Handle, - (uint)Security.NativeMethods.AddCertificateContext.CERT_STORE_ADD_ALWAYS, + (uint)SMASecurity.NativeMethods.AddCertificateContext.CERT_STORE_ADD_ALWAYS, ref outCert)) { throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } - if (!Security.NativeMethods.CertDeleteCertificateFromStore(dupCert)) + if (!SMASecurity.NativeMethods.CertDeleteCertificateFromStore(dupCert)) { throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } @@ -1973,7 +1958,7 @@ private void DoMove(string destination, X509Certificate2 cert, X509NativeStore s if (sourcePath.Contains("UserDS")) { - Security.NativeMethods.CERT_CONTEXT context = Marshal.PtrToStructure(cert.Handle); + SMASecurity.NativeMethods.CERT_CONTEXT context = Marshal.PtrToStructure(cert.Handle); CommitUserDS(context.hCertStore); } @@ -2545,10 +2530,7 @@ private X509NativeStore GetStore(string storePath, } } - if (s_storeCache == null) - { - s_storeCache = new X509NativeStore(storeLocation, storeName); - } + s_storeCache ??= new X509NativeStore(storeLocation, storeName); return s_storeCache; } @@ -2646,51 +2628,46 @@ private CertificateFilterInfo GetFilter() { CertificateFilterInfo filter = null; - if (DynamicParameters != null) + if (DynamicParameters != null && DynamicParameters is CertificateProviderDynamicParameters dp) { - CertificateProviderDynamicParameters dp = - DynamicParameters as CertificateProviderDynamicParameters; - if (dp != null) + if (dp.CodeSigningCert) { - if (dp.CodeSigningCert) - { - filter = new CertificateFilterInfo(); - filter.Purpose = CertificatePurpose.CodeSigning; - } + filter = new CertificateFilterInfo(); + filter.Purpose = CertificatePurpose.CodeSigning; + } - if (dp.DocumentEncryptionCert) - { - filter ??= new CertificateFilterInfo(); - filter.Purpose = CertificatePurpose.DocumentEncryption; - } + if (dp.DocumentEncryptionCert) + { + filter ??= new CertificateFilterInfo(); + filter.Purpose = CertificatePurpose.DocumentEncryption; + } - if (dp.DnsName != null) - { - filter ??= new CertificateFilterInfo(); - filter.DnsName = new WildcardPattern(dp.DnsName, WildcardOptions.IgnoreCase); - } + if (dp.DnsName != null) + { + filter ??= new CertificateFilterInfo(); + filter.DnsName = new WildcardPattern(dp.DnsName, WildcardOptions.IgnoreCase); + } - if (dp.Eku != null) + if (dp.Eku != null) + { + filter ??= new CertificateFilterInfo(); + filter.Eku = new List(); + foreach (var pattern in dp.Eku) { - filter ??= new CertificateFilterInfo(); - filter.Eku = new List(); - foreach (var pattern in dp.Eku) - { - filter.Eku.Add(new WildcardPattern(pattern, WildcardOptions.IgnoreCase)); - } + filter.Eku.Add(new WildcardPattern(pattern, WildcardOptions.IgnoreCase)); } + } - if (dp.ExpiringInDays >= 0) - { - filter ??= new CertificateFilterInfo(); - filter.Expiring = DateTime.Now.AddDays(dp.ExpiringInDays); - } + if (dp.ExpiringInDays >= 0) + { + filter ??= new CertificateFilterInfo(); + filter.Expiring = DateTime.Now.AddDays(dp.ExpiringInDays); + } - if (dp.SSLServerAuthentication) - { - filter ??= new CertificateFilterInfo(); - filter.SSLServerAuthentication = true; - } + if (dp.SSLServerAuthentication) + { + filter ??= new CertificateFilterInfo(); + filter.SSLServerAuthentication = true; } } @@ -3151,9 +3128,9 @@ public static bool ReadSendAsTrustedIssuerProperty(X509Certificate2 cert) int propSize = 0; // try to get the property // it is fine if fail for not there - if (Security.NativeMethods.CertGetCertificateContextProperty( + if (SMASecurity.NativeMethods.CertGetCertificateContextProperty( cert.Handle, - Security.NativeMethods.CertPropertyId.CERT_SEND_AS_TRUSTED_ISSUER_PROP_ID, + SMASecurity.NativeMethods.CertPropertyId.CERT_SEND_AS_TRUSTED_ISSUER_PROP_ID, IntPtr.Zero, ref propSize)) { @@ -3164,7 +3141,7 @@ public static bool ReadSendAsTrustedIssuerProperty(X509Certificate2 cert) { // if fail int error = Marshal.GetLastWin32Error(); - if (error != Security.NativeMethods.CRYPT_E_NOT_FOUND) + if (error != SMASecurity.NativeMethods.CRYPT_E_NOT_FOUND) { throw new System.ComponentModel.Win32Exception(error); } @@ -3183,7 +3160,7 @@ public static void WriteSendAsTrustedIssuerProperty(X509Certificate2 cert, strin if (DownLevelHelper.TrustedIssuerSupported()) { IntPtr propertyPtr = IntPtr.Zero; - Security.NativeMethods.CRYPT_DATA_BLOB dataBlob = new(); + SMASecurity.NativeMethods.CRYPT_DATA_BLOB dataBlob = new(); dataBlob.cbData = 0; dataBlob.pbData = IntPtr.Zero; X509Certificate certFromStore = null; @@ -3230,9 +3207,9 @@ public static void WriteSendAsTrustedIssuerProperty(X509Certificate2 cert, strin } // set property - if (!Security.NativeMethods.CertSetCertificateContextProperty( + if (!SMASecurity.NativeMethods.CertSetCertificateContextProperty( certFromStore != null ? certFromStore.Handle : cert.Handle, - Security.NativeMethods.CertPropertyId.CERT_SEND_AS_TRUSTED_ISSUER_PROP_ID, + SMASecurity.NativeMethods.CertPropertyId.CERT_SEND_AS_TRUSTED_ISSUER_PROP_ID, 0, propertyPtr)) { @@ -3249,7 +3226,7 @@ public static void WriteSendAsTrustedIssuerProperty(X509Certificate2 cert, strin } else { - Marshal.ThrowExceptionForHR(Security.NativeMethods.NTE_NOT_SUPPORTED); + Marshal.ThrowExceptionForHR(SMASecurity.NativeMethods.NTE_NOT_SUPPORTED); } } @@ -3313,17 +3290,13 @@ public EnhancedKeyUsageProperty(X509Certificate2 cert) foreach (X509Extension extension in cert.Extensions) { // Filter to the OID for EKU - if (extension.Oid.Value == "2.5.29.37") + if (extension.Oid.Value == "2.5.29.37" && extension is X509EnhancedKeyUsageExtension ext) { - X509EnhancedKeyUsageExtension ext = extension as X509EnhancedKeyUsageExtension; - if (ext != null) + OidCollection oids = ext.EnhancedKeyUsages; + foreach (Oid oid in oids) { - OidCollection oids = ext.EnhancedKeyUsages; - foreach (Oid oid in oids) - { - EnhancedKeyUsageRepresentation ekuString = new(oid.FriendlyName, oid.Value); - _ekuList.Add(ekuString); - } + EnhancedKeyUsageRepresentation ekuString = new(oid.FriendlyName, oid.Value); + _ekuList.Add(ekuString); } } } @@ -3336,20 +3309,30 @@ public EnhancedKeyUsageProperty(X509Certificate2 cert) public sealed class DnsNameProperty { private readonly List _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="; /// /// Get property of DnsNameList. /// - public List DnsNameList + public List DnsNameList => _dnsList; + + private DnsNameRepresentation GetDnsNameRepresentation(string dnsName) { - get + string unicodeName; + + try { - return _dnsList; + unicodeName = idnMapping.GetUnicode(dnsName); } + catch (ArgumentException) + { + // The name is not valid Punycode, assume it's valid ASCII. + unicodeName = dnsName; + } + + return new DnsNameRepresentation(dnsName, unicodeName); } /// @@ -3357,61 +3340,32 @@ public List DnsNameList /// public DnsNameProperty(X509Certificate2 cert) { - string name; - string unicodeName; - DnsNameRepresentation dnsName; _dnsList = new List(); // 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); } } } @@ -3486,12 +3440,12 @@ internal static IntPtr GetOwnerWindow(PSHost host) if (hWnd == IntPtr.Zero) { - hWnd = Security.NativeMethods.GetConsoleWindow(); + hWnd = SMASecurity.NativeMethods.GetConsoleWindow(); } if (hWnd == IntPtr.Zero) { - hWnd = Security.NativeMethods.GetDesktopWindow(); + hWnd = SMASecurity.NativeMethods.GetDesktopWindow(); } } } @@ -3506,7 +3460,7 @@ private static bool IsUIAllowed(PSHost host) uint SessionId; uint ProcessId = (uint)System.Diagnostics.Process.GetCurrentProcess().Id; - if (!Security.NativeMethods.ProcessIdToSessionId(ProcessId, out SessionId)) + if (!SMASecurity.NativeMethods.ProcessIdToSessionId(ProcessId, out SessionId)) return false; if (SessionId == 0) @@ -3549,20 +3503,19 @@ internal static class Crypt32Helpers /// /// Get a list of store names at the specified location. /// - [ArchitectureSensitive] internal static List GetStoreNamesAtLocation(StoreLocation location) { - Security.NativeMethods.CertStoreFlags locationFlag = - Security.NativeMethods.CertStoreFlags.CERT_SYSTEM_STORE_CURRENT_USER; + SMASecurity.NativeMethods.CertStoreFlags locationFlag = + SMASecurity.NativeMethods.CertStoreFlags.CERT_SYSTEM_STORE_CURRENT_USER; switch (location) { case StoreLocation.CurrentUser: - locationFlag = Security.NativeMethods.CertStoreFlags.CERT_SYSTEM_STORE_CURRENT_USER; + locationFlag = SMASecurity.NativeMethods.CertStoreFlags.CERT_SYSTEM_STORE_CURRENT_USER; break; case StoreLocation.LocalMachine: - locationFlag = Security.NativeMethods.CertStoreFlags.CERT_SYSTEM_STORE_LOCAL_MACHINE; + locationFlag = SMASecurity.NativeMethods.CertStoreFlags.CERT_SYSTEM_STORE_LOCAL_MACHINE; break; default: @@ -3570,7 +3523,7 @@ internal static List GetStoreNamesAtLocation(StoreLocation location) break; } - Security.NativeMethods.CertEnumSystemStoreCallBackProto callBack = new(CertEnumSystemStoreCallBack); + SMASecurity.NativeMethods.CertEnumSystemStoreCallBackProto callBack = new(CertEnumSystemStoreCallBack); // Return a new list to avoid synchronization issues. @@ -3579,7 +3532,7 @@ internal static List GetStoreNamesAtLocation(StoreLocation location) { storeNames.Clear(); - Security.NativeMethods.CertEnumSystemStore(locationFlag, IntPtr.Zero, + SMASecurity.NativeMethods.CertEnumSystemStore(locationFlag, IntPtr.Zero, IntPtr.Zero, callBack); foreach (string name in storeNames) { diff --git a/src/Microsoft.PowerShell.Security/security/CmsCommands.cs b/src/Microsoft.PowerShell.Security/security/CmsCommands.cs index 9fde804d220..5e8e6d19b46 100644 --- a/src/Microsoft.PowerShell.Security/security/CmsCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/CmsCommands.cs @@ -35,8 +35,8 @@ public CmsMessageRecipient[] To /// Gets or sets the content of the CMS Message. /// [Parameter(Position = 1, Mandatory = true, ValueFromPipeline = true, ParameterSetName = "ByContent")] - [AllowNull()] - [AllowEmptyString()] + [AllowNull] + [AllowEmptyString] public PSObject Content { get; @@ -202,8 +202,8 @@ public sealed class GetCmsMessageCommand : PSCmdlet /// Gets or sets the content of the CMS Message. /// [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ParameterSetName = "ByContent")] - [AllowNull()] - [AllowEmptyString()] + [AllowNull] + [AllowEmptyString] public string Content { get; @@ -308,8 +308,7 @@ protected override void EndProcessing() } // Extract out the bytes and Base64 decode them - int startIndex, endIndex; - byte[] contentBytes = CmsUtils.RemoveAsciiArmor(actualContent, CmsUtils.BEGIN_CMS_SIGIL, CmsUtils.END_CMS_SIGIL, out startIndex, out endIndex); + byte[] contentBytes = CmsUtils.RemoveAsciiArmor(actualContent, CmsUtils.BEGIN_CMS_SIGIL, CmsUtils.END_CMS_SIGIL, out int _, out int _); if (contentBytes == null) { ErrorRecord error = new( @@ -351,8 +350,8 @@ public sealed class UnprotectCmsMessageCommand : PSCmdlet /// Gets or sets the content of the CMS Message. /// [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "ByContent")] - [AllowNull()] - [AllowEmptyString()] + [AllowNull] + [AllowEmptyString] public string Content { get; @@ -398,7 +397,7 @@ public string LiteralPath /// Determines whether to include the decrypted content in its original context, /// rather than just output the decrypted content itself. /// - [Parameter()] + [Parameter] public SwitchParameter IncludeContext { get; diff --git a/src/Microsoft.PowerShell.Security/security/CredentialCommands.cs b/src/Microsoft.PowerShell.Security/security/CredentialCommands.cs index 33238c0ca6c..cc354ee1531 100644 --- a/src/Microsoft.PowerShell.Security/security/CredentialCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/CredentialCommands.cs @@ -9,7 +9,7 @@ namespace Microsoft.PowerShell.Commands /// /// Defines the implementation of the 'get-credential' cmdlet. /// The get-credential Cmdlet establishes a credential object called a - /// Msh credential, by pairing a given username with + /// PSCredential, by pairing a given username with /// a prompted password. That credential object can then be used for other /// operations involving security. /// @@ -33,7 +33,7 @@ public sealed class GetCredentialCommand : PSCmdlet /// [Parameter(Position = 0, ParameterSetName = credentialSet)] [ValidateNotNull] - [Credential()] + [Credential] public PSCredential Credential { get; set; } /// @@ -55,7 +55,7 @@ public string Message /// Gets and sets the user supplied username to be used while creating the PSCredential. /// [Parameter(Position = 0, Mandatory = false, ParameterSetName = messageSet)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public string UserName { get { return _userName; } diff --git a/src/Microsoft.PowerShell.Security/security/SecureStringCommands.cs b/src/Microsoft.PowerShell.Security/security/SecureStringCommands.cs index 6333541e242..c727476c0a3 100644 --- a/src/Microsoft.PowerShell.Security/security/SecureStringCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/SecureStringCommands.cs @@ -239,7 +239,7 @@ public ConvertToSecureStringCommand() : base("ConvertTo-SecureString") { } /// Gets or sets the unsecured string to be imported. /// [Parameter(Position = 0, ValueFromPipeline = true, Mandatory = true)] - public String String + public string String { get { @@ -326,7 +326,7 @@ protected override void ProcessRecord() // representation, then parse it into its components. byte[] inputBytes = Convert.FromBase64String(remainingData); string dataPackage = System.Text.Encoding.Unicode.GetString(inputBytes); - string[] dataElements = dataPackage.Split(Utils.Separators.Pipe); + string[] dataElements = dataPackage.Split('|'); if (dataElements.Length == 3) { @@ -359,8 +359,7 @@ protected override void ProcessRecord() } else { - importedString = new SecureString(); - foreach (char currentChar in String) { importedString.AppendChar(currentChar); } + importedString = SecureStringHelper.FromPlainTextString(String); } } catch (ArgumentException e) diff --git a/src/Microsoft.PowerShell.Security/security/SignatureCommands.cs b/src/Microsoft.PowerShell.Security/security/SignatureCommands.cs index 5d5bb06b667..53a662ca4f9 100644 --- a/src/Microsoft.PowerShell.Security/security/SignatureCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/SignatureCommands.cs @@ -44,7 +44,7 @@ public string[] FilePath /// digital signature. /// [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "ByLiteralPath")] - [Alias("PSPath")] + [Alias("PSPath", "LP")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] LiteralPath { @@ -294,7 +294,7 @@ protected override Signature PerformAction(string filePath) /// protected override Signature PerformAction(string sourcePathOrExtension, byte[] content) { - return SignatureHelper.GetSignature(sourcePathOrExtension, System.Text.Encoding.Unicode.GetString(content)); + return SignatureHelper.GetSignature(sourcePathOrExtension, content); } } @@ -374,10 +374,7 @@ public string TimestampServer set { - if (value == null) - { - value = string.Empty; - } + value ??= string.Empty; _timestampServer = value; } @@ -404,12 +401,12 @@ public string HashAlgorithm } } - private string _hashAlgorithm = null; + private string _hashAlgorithm = "SHA256"; /// /// Property that sets force parameter. /// - [Parameter()] + [Parameter] public SwitchParameter Force { get diff --git a/src/Microsoft.PowerShell.Security/security/certificateproviderexceptions.cs b/src/Microsoft.PowerShell.Security/security/certificateproviderexceptions.cs index efd205e5769..6d602d2d99f 100644 --- a/src/Microsoft.PowerShell.Security/security/certificateproviderexceptions.cs +++ b/src/Microsoft.PowerShell.Security/security/certificateproviderexceptions.cs @@ -12,7 +12,6 @@ namespace Microsoft.PowerShell.Commands /// Defines the base class for exceptions thrown by the /// certificate provider when the specified item cannot be located. /// - [Serializable] public class CertificateProviderItemNotFoundException : SystemException { /// @@ -60,10 +59,11 @@ public CertificateProviderItemNotFoundException(string message, /// /// The streaming context. /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected CertificateProviderItemNotFoundException(SerializationInfo info, - StreamingContext context) - : base(info, context) + StreamingContext context) { + throw new NotSupportedException(); } /// @@ -83,7 +83,6 @@ internal CertificateProviderItemNotFoundException(Exception innerException) /// Defines the exception thrown by the certificate provider /// when the specified X509 certificate cannot be located. /// - [Serializable] public class CertificateNotFoundException : CertificateProviderItemNotFoundException { @@ -134,10 +133,11 @@ public CertificateNotFoundException(string message, /// /// The streaming context. /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected CertificateNotFoundException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } /// @@ -157,7 +157,6 @@ internal CertificateNotFoundException(Exception innerException) /// Defines the exception thrown by the certificate provider /// when the specified X509 store cannot be located. /// - [Serializable] public class CertificateStoreNotFoundException : CertificateProviderItemNotFoundException { @@ -170,6 +169,23 @@ public CertificateStoreNotFoundException() { } + /// + /// Initializes a new instance of the CertificateStoreNotFoundException + /// class with the specified serialization information, and context. + /// + /// + /// The serialization information. + /// + /// + /// The streaming context. + /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + protected CertificateStoreNotFoundException(SerializationInfo info, + StreamingContext context) + { + throw new NotSupportedException(); + } + /// /// Initializes a new instance of the CertificateStoreNotFoundException /// class with the specified message. @@ -198,22 +214,6 @@ public CertificateStoreNotFoundException(string message, { } - /// - /// Initializes a new instance of the CertificateStoreNotFoundException - /// class with the specified serialization information, and context. - /// - /// - /// The serialization information. - /// - /// - /// The streaming context. - /// - protected CertificateStoreNotFoundException(SerializationInfo info, - StreamingContext context) - : base(info, context) - { - } - /// /// Initializes a new instance of the CertificateStoreNotFoundException /// class with the specified inner exception. @@ -231,7 +231,6 @@ internal CertificateStoreNotFoundException(Exception innerException) /// Defines the exception thrown by the certificate provider /// when the specified X509 store location cannot be located. /// - [Serializable] public class CertificateStoreLocationNotFoundException : CertificateProviderItemNotFoundException { @@ -244,6 +243,23 @@ public CertificateStoreLocationNotFoundException() { } + /// + /// Initializes a new instance of the CertificateStoreLocationNotFoundException + /// class with the specified serialization information, and context. + /// + /// + /// The serialization information. + /// + /// + /// The streaming context. + /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + protected CertificateStoreLocationNotFoundException(SerializationInfo info, + StreamingContext context) + { + throw new NotSupportedException(); + } + /// /// Initializes a new instance of the CertificateStoreLocationNotFoundException /// class with the specified message. @@ -272,22 +288,6 @@ public CertificateStoreLocationNotFoundException(string message, { } - /// - /// Initializes a new instance of the CertificateStoreLocationNotFoundException - /// class with the specified serialization information, and context. - /// - /// - /// The serialization information. - /// - /// - /// The streaming context. - /// - protected CertificateStoreLocationNotFoundException(SerializationInfo info, - StreamingContext context) - : base(info, context) - { - } - /// /// Initializes a new instance of the CertificateStoreLocationNotFoundException /// class with the specified inner exception. diff --git a/src/Microsoft.PowerShell.Security/singleshell/installer/MshSecurityMshSnapin.cs b/src/Microsoft.PowerShell.Security/singleshell/installer/MshSecurityMshSnapin.cs index efc4ec63daa..e92502e3911 100644 --- a/src/Microsoft.PowerShell.Security/singleshell/installer/MshSecurityMshSnapin.cs +++ b/src/Microsoft.PowerShell.Security/singleshell/installer/MshSecurityMshSnapin.cs @@ -16,10 +16,8 @@ namespace Microsoft.PowerShell { /// - /// MshSecurityMshSnapin (or MshSecurityMshSnapinInstaller) is a class for facilitating registry - /// of necessary information for monad security mshsnapin. - /// - /// This class will be built with monad security dll. + /// PSSecurityPSSnapIn is a class for facilitating registry + /// of necessary information for PowerShell security PSSnapin. /// [RunInstaller(true)] public sealed class PSSecurityPSSnapIn : PSSnapIn @@ -33,7 +31,7 @@ public PSSecurityPSSnapIn() } /// - /// Get name of this mshsnapin. + /// Get name of this PSSnapin. /// public override string Name { @@ -44,7 +42,7 @@ public override string Name } /// - /// Get the default vendor string for this mshsnapin. + /// Get the default vendor string for this PSSnapin. /// public override string Vendor { @@ -66,7 +64,7 @@ public override string VendorResource } /// - /// Get the default description string for this mshsnapin. + /// Get the default description string for this PSSnapin. /// public override string Description { diff --git a/src/Microsoft.WSMan.Management/ConfigProvider.cs b/src/Microsoft.WSMan.Management/ConfigProvider.cs index 9e3d9bd05b1..f1fc3a36277 100644 --- a/src/Microsoft.WSMan.Management/ConfigProvider.cs +++ b/src/Microsoft.WSMan.Management/ConfigProvider.cs @@ -617,7 +617,10 @@ protected override void GetItem(string path) SessionObjCache.TryGetValue(host, out sessionobj); XmlDocument xmlResource = FindResourceValue(sessionobj, uri, null); - if (xmlResource == null) { return; } + if (xmlResource == null) + { + return; + } // if endswith '\', removes it. if (path.EndsWith(WSManStringLiterals.DefaultPathSeparator.ToString(), StringComparison.OrdinalIgnoreCase)) @@ -1021,20 +1024,15 @@ protected override void SetItem(string path, object value) strPathChk = strPathChk + WSManStringLiterals.containerPlugin + WSManStringLiterals.DefaultPathSeparator; if (path.EndsWith(strPathChk + currentpluginname, StringComparison.OrdinalIgnoreCase)) { - if (WSManStringLiterals.ConfigRunAsUserName.Equals(ChildName, StringComparison.OrdinalIgnoreCase)) + if (WSManStringLiterals.ConfigRunAsUserName.Equals(ChildName, StringComparison.OrdinalIgnoreCase) && value is PSCredential runAsCredentials) { - PSCredential runAsCredentials = value as PSCredential; - - if (runAsCredentials != null) - { - // UserName - value = runAsCredentials.UserName; + // UserName + value = runAsCredentials.UserName; - pluginConfiguration.UpdateOneConfiguration( - ".", - WSManStringLiterals.ConfigRunAsPasswordName, - GetStringFromSecureString(runAsCredentials.Password)); - } + pluginConfiguration.UpdateOneConfiguration( + ".", + WSManStringLiterals.ConfigRunAsPasswordName, + GetStringFromSecureString(runAsCredentials.Password)); } if (WSManStringLiterals.ConfigRunAsPasswordName.Equals(ChildName, StringComparison.OrdinalIgnoreCase)) @@ -1301,8 +1299,7 @@ protected override void SetItem(string path, object value) } } - WSManProviderSetItemDynamicParameters dynParams = DynamicParameters as WSManProviderSetItemDynamicParameters; - if (dynParams != null) + if (DynamicParameters is WSManProviderSetItemDynamicParameters dynParams) { if (dynParams.Concatenate) { @@ -1965,9 +1962,8 @@ protected override object NewItemDynamicParameters(string path, string itemTypeN private void NewItemCreateComputerConnection(string Name) { helper = new WSManHelper(this); - WSManProviderNewItemComputerParameters dynParams = DynamicParameters as WSManProviderNewItemComputerParameters; string parametersetName = "ComputerName"; - if (dynParams != null) + if (DynamicParameters is WSManProviderNewItemComputerParameters dynParams) { if (dynParams.ConnectionURI != null) { @@ -2064,8 +2060,7 @@ private void NewItemPluginOrPluginChild(object sessionobj, string path, string h // to create a new plugin if (path.EndsWith(strPathChk, StringComparison.OrdinalIgnoreCase)) { - WSManProviderNewItemPluginParameters niParams = DynamicParameters as WSManProviderNewItemPluginParameters; - if (niParams != null) + if (DynamicParameters is WSManProviderNewItemPluginParameters niParams) { if (string.IsNullOrEmpty(niParams.File)) { @@ -2155,8 +2150,7 @@ private void NewItemPluginOrPluginChild(object sessionobj, string path, string h strPathChk += WSManStringLiterals.containerResources; if (path.EndsWith(strPathChk, StringComparison.OrdinalIgnoreCase)) { - WSManProviderNewItemResourceParameters niParams = DynamicParameters as WSManProviderNewItemResourceParameters; - if (niParams != null) + if (DynamicParameters is WSManProviderNewItemResourceParameters niParams) { mshObj.Properties.Add(new PSNoteProperty("Resource", niParams.ResourceUri)); mshObj.Properties.Add(new PSNoteProperty("Capability", niParams.Capability)); @@ -3413,10 +3407,7 @@ private static string NormalizePath(string path, string host) /// private PSObject GetItemValue(string path) { - if (string.IsNullOrEmpty(path) || (path.Length == 0)) - { - throw new ArgumentNullException(path); - } + ArgumentException.ThrowIfNullOrEmpty(path); // if endswith '\', removes it. if (path.EndsWith(WSManStringLiterals.DefaultPathSeparator.ToString(), StringComparison.OrdinalIgnoreCase)) @@ -5059,9 +5050,9 @@ private static ArrayList ProcessPluginSecurityLevel(ArrayList arrSecurity, XmlDo /// Resource URI for the XML. /// Name of the Host. /// Type of Operation. - ///List of Resources. - ///List of Securities - ///List of initialization parameters. + /// List of Resources. + /// List of Securities + /// List of initialization parameters. /// An Configuration XML, ready to send to server. private static string ConstructPluginXml(PSObject objinputparam, string ResourceURI, string host, string Operation, ArrayList resources, ArrayList securities, ArrayList initParams) { @@ -5193,10 +5184,9 @@ private object ValidateAndGetUserObject(string configurationName, object value) /// Value to append. private static string GetStringFromSecureString(object propertyValue) { - SecureString value = propertyValue as SecureString; string passwordValueToAdd = string.Empty; - if (value != null) + if (propertyValue is SecureString value) { IntPtr ptr = Marshal.SecureStringToBSTR(value); passwordValueToAdd = Marshal.PtrToStringAuto(ptr); @@ -5607,15 +5597,15 @@ public string ApplicationName [Parameter] [ValidateNotNullOrEmpty] [Parameter(ParameterSetName = "nameSet")] - [ValidateRange(1, Int32.MaxValue)] - public Int32 Port + [ValidateRange(1, int.MaxValue)] + public int Port { get { return port; } set { port = value; } } - private Int32 port = 0; + private int port = 0; /// /// The following is the definition of the input parameter "UseSSL". @@ -5766,7 +5756,7 @@ public string File /// Parameter for RunAs credentials for a Plugin. /// [ValidateNotNull] - [Parameter()] + [Parameter] public PSCredential RunAsCredential { get { return this.runAsCredentials; } @@ -5779,7 +5769,7 @@ public PSCredential RunAsCredential /// /// Parameter for Plugin Host Process configuration (Shared or Separate). /// - [Parameter()] + [Parameter] public SwitchParameter UseSharedProcess { get { return this.sharedHost; } @@ -5792,7 +5782,7 @@ public SwitchParameter UseSharedProcess /// /// Parameter for Auto Restart configuration for Plugin. /// - [Parameter()] + [Parameter] public SwitchParameter AutoRestart { get { return this.autoRestart; } @@ -5805,7 +5795,7 @@ public SwitchParameter AutoRestart /// /// Parameter for Idle timeout for HostProcess. /// - [Parameter()] + [Parameter] public uint? ProcessIdleTimeoutSec { get @@ -5946,7 +5936,7 @@ public string Issuer /// /// Parameter Subject. /// - [Parameter()] + [Parameter] [ValidateNotNullOrEmpty] public string Subject { @@ -6191,7 +6181,7 @@ public class WSManProviderSetItemDynamicParameters /// /// Parameter Concatenate. /// - [Parameter()] + [Parameter] public SwitchParameter Concatenate { get { return _concatenate; } diff --git a/src/Microsoft.WSMan.Management/CredSSP.cs b/src/Microsoft.WSMan.Management/CredSSP.cs index cd5e6d3fc4d..03c71f0edb1 100644 --- a/src/Microsoft.WSMan.Management/CredSSP.cs +++ b/src/Microsoft.WSMan.Management/CredSSP.cs @@ -200,7 +200,8 @@ private void DisableServerSideSettings() return; } - string inputXml = string.Format(CultureInfo.InvariantCulture, + string inputXml = string.Format( + CultureInfo.InvariantCulture, @"false", helper.Service_CredSSP_XMLNmsp); @@ -389,6 +390,7 @@ protected override void BeginProcessing() /// authentication is achieved via a trusted X509 certificate or Kerberos. /// [Cmdlet(VerbsLifecycle.Enable, "WSManCredSSP", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096719")] + [OutputType(typeof(XmlElement))] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Cred")] [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SSP")] public class EnableWSManCredSSPCommand : WSManCredSSPCommandBase, IDisposable/*, IDynamicParameters*/ @@ -411,7 +413,7 @@ public string[] DelegateComputer /// /// Property that sets force parameter. /// - [Parameter()] + [Parameter] public SwitchParameter Force { get { return force; } @@ -512,6 +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)); @@ -591,9 +594,11 @@ private void EnableServerSideSettings() try { XmlDocument xmldoc = new XmlDocument(); - string newxmlcontent = string.Format(CultureInfo.InvariantCulture, + string newxmlcontent = string.Format( + CultureInfo.InvariantCulture, @"true", helper.Service_CredSSP_XMLNmsp); + // push the xml string with credssp enabled xmldoc.LoadXml(m_SessionObj.Put(helper.Service_CredSSP_Uri, newxmlcontent, 0)); WriteObject(xmldoc.FirstChild); @@ -736,6 +741,7 @@ private void UpdateGPORegistrySettings(string applicationname, string[] delegate [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Cred")] [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SSP")] [Cmdlet(VerbsCommon.Get, "WSManCredSSP", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096838")] + [OutputType(typeof(string))] public class GetWSManCredSSPCommand : PSCmdlet, IDisposable { #region private diff --git a/src/Microsoft.WSMan.Management/CurrentConfigurations.cs b/src/Microsoft.WSMan.Management/CurrentConfigurations.cs index 0129554c30b..f4c252c4d1d 100644 --- a/src/Microsoft.WSMan.Management/CurrentConfigurations.cs +++ b/src/Microsoft.WSMan.Management/CurrentConfigurations.cs @@ -61,10 +61,7 @@ public XmlDocument RootDocument /// Current server session. public CurrentConfigurations(IWSManSession serverSession) { - if (serverSession == null) - { - throw new ArgumentNullException(nameof(serverSession)); - } + ArgumentNullException.ThrowIfNull(serverSession); this.rootDocument = new XmlDocument(); this.serverSession = serverSession; @@ -79,10 +76,7 @@ public CurrentConfigurations(IWSManSession serverSession) /// False, if operation failed. public bool RefreshCurrentConfiguration(string responseOfGet) { - if (string.IsNullOrEmpty(responseOfGet)) - { - throw new ArgumentNullException(nameof(responseOfGet)); - } + ArgumentException.ThrowIfNullOrEmpty(responseOfGet); this.rootDocument.LoadXml(responseOfGet); this.documentElement = this.rootDocument.DocumentElement; @@ -98,13 +92,10 @@ public bool RefreshCurrentConfiguration(string responseOfGet) /// Issues a PUT request with the ResourceUri provided. /// /// Resource URI to use. - /// False, if operation is not succesful. + /// False, if operation is not successful. public void PutConfigurationOnServer(string resourceUri) { - if (string.IsNullOrEmpty(resourceUri)) - { - throw new ArgumentNullException(nameof(resourceUri)); - } + ArgumentException.ThrowIfNullOrEmpty(resourceUri); this.serverSession.Put(resourceUri, this.rootDocument.InnerXml, 0); } @@ -117,10 +108,7 @@ public void PutConfigurationOnServer(string resourceUri) /// Path with namespace to the node from Root element. Must not end with '/'. public void RemoveOneConfiguration(string pathToNodeFromRoot) { - if (pathToNodeFromRoot == null) - { - throw new ArgumentNullException(nameof(pathToNodeFromRoot)); - } + ArgumentNullException.ThrowIfNull(pathToNodeFromRoot); XmlNode nodeToRemove = this.documentElement.SelectSingleNode( @@ -150,20 +138,9 @@ public void RemoveOneConfiguration(string pathToNodeFromRoot) /// Value of the configurations. public void UpdateOneConfiguration(string pathToNodeFromRoot, string configurationName, string configurationValue) { - if (pathToNodeFromRoot == null) - { - throw new ArgumentNullException(nameof(pathToNodeFromRoot)); - } - - if (string.IsNullOrEmpty(configurationName)) - { - throw new ArgumentNullException(nameof(configurationName)); - } - - if (configurationValue == null) - { - throw new ArgumentNullException(nameof(configurationValue)); - } + ArgumentNullException.ThrowIfNull(pathToNodeFromRoot); + ArgumentException.ThrowIfNullOrEmpty(configurationName); + ArgumentNullException.ThrowIfNull(configurationValue); XmlNode nodeToUpdate = this.documentElement.SelectSingleNode( @@ -195,10 +172,7 @@ public void UpdateOneConfiguration(string pathToNodeFromRoot, string configurati /// Value of the Node, or Null if no node present. public string GetOneConfiguration(string pathFromRoot) { - if (pathFromRoot == null) - { - throw new ArgumentNullException(nameof(pathFromRoot)); - } + ArgumentNullException.ThrowIfNull(pathFromRoot); XmlNode requiredNode = this.documentElement.SelectSingleNode( diff --git a/src/Microsoft.WSMan.Management/Interop.cs b/src/Microsoft.WSMan.Management/Interop.cs index 6685770b0ef..e5dd462e2f7 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 @@ -860,7 +860,7 @@ string Error [ComImport] [TypeLibType((short)4288)] #if CORECLR - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] #else [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] #endif @@ -1005,7 +1005,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/InvokeWSManAction.cs b/src/Microsoft.WSMan.Management/InvokeWSManAction.cs index 36f65e52705..da92a680ab3 100644 --- a/src/Microsoft.WSMan.Management/InvokeWSManAction.cs +++ b/src/Microsoft.WSMan.Management/InvokeWSManAction.cs @@ -24,6 +24,7 @@ namespace Microsoft.WSMan.Management /// -SelectorSet {Name=Spooler} /// [Cmdlet(VerbsLifecycle.Invoke, "WSManAction", DefaultParameterSetName = "URI", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096843")] + [OutputType(typeof(XmlElement))] public class InvokeWSManActionCommand : AuthenticatingWSManCommand, IDisposable { /// @@ -146,15 +147,15 @@ public Hashtable OptionSet /// [Parameter(ParameterSetName = "ComputerName")] [ValidateNotNullOrEmpty] - [ValidateRange(1, Int32.MaxValue)] - public Int32 Port + [ValidateRange(1, int.MaxValue)] + public int Port { get { return port; } set { port = value; } } - private Int32 port = 0; + private int port = 0; /// /// The following is the definition of the input parameter "SelectorSet". diff --git a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj index 84fc158cc0f..5122f4b51c5 100644 --- a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj +++ b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Microsoft.WSMan.Management/NewWSManSession.cs b/src/Microsoft.WSMan.Management/NewWSManSession.cs index 30959f3ced5..09b22af9924 100644 --- a/src/Microsoft.WSMan.Management/NewWSManSession.cs +++ b/src/Microsoft.WSMan.Management/NewWSManSession.cs @@ -26,6 +26,7 @@ namespace Microsoft.WSMan.Management /// Connect-WSMan. /// [Cmdlet(VerbsCommon.New, "WSManSessionOption", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096845")] + [OutputType(typeof(SessionOption))] public class NewWSManSessionOptionCommand : PSCmdlet { /// @@ -170,8 +171,8 @@ public SwitchParameter SkipRevocationCheck /// [Parameter] [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SPN")] - [ValidateRange(0, Int32.MaxValue)] - public Int32 SPNPort + [ValidateRange(0, int.MaxValue)] + public int SPNPort { get { @@ -184,7 +185,7 @@ public Int32 SPNPort } } - private Int32 spnport; + private int spnport; /// /// The following is the definition of the input parameter "Timeout". @@ -192,8 +193,8 @@ public Int32 SPNPort /// [Parameter] [Alias("OperationTimeoutMSec")] - [ValidateRange(0, Int32.MaxValue)] - public Int32 OperationTimeout + [ValidateRange(0, int.MaxValue)] + public int OperationTimeout { get { @@ -206,7 +207,7 @@ public Int32 OperationTimeout } } - private Int32 operationtimeout; + private int operationtimeout; /// /// The following is the definition of the input parameter "UnEncrypted". diff --git a/src/Microsoft.WSMan.Management/PingWSMan.cs b/src/Microsoft.WSMan.Management/PingWSMan.cs index a04167a0f1b..88b443a6ef0 100644 --- a/src/Microsoft.WSMan.Management/PingWSMan.cs +++ b/src/Microsoft.WSMan.Management/PingWSMan.cs @@ -23,6 +23,7 @@ namespace Microsoft.WSMan.Management /// service is running. /// [Cmdlet(VerbsDiagnostic.Test, "WSMan", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2097114")] + [OutputType(typeof(XmlElement))] public class TestWSManCommand : AuthenticatingWSManCommand, IDisposable { /// @@ -95,15 +96,15 @@ public override AuthenticationMechanism Authentication /// [Parameter(ParameterSetName = "ComputerName")] [ValidateNotNullOrEmpty] - [ValidateRange(1, Int32.MaxValue)] - public Int32 Port + [ValidateRange(1, int.MaxValue)] + public int Port { get { return port; } set { port = value; } } - private Int32 port = 0; + private int port = 0; /// /// The following is the definition of the input parameter "UseSSL". diff --git a/src/Microsoft.WSMan.Management/Set-QuickConfig.cs b/src/Microsoft.WSMan.Management/Set-QuickConfig.cs index c318ea76477..9ad9e39c332 100644 --- a/src/Microsoft.WSMan.Management/Set-QuickConfig.cs +++ b/src/Microsoft.WSMan.Management/Set-QuickConfig.cs @@ -30,6 +30,7 @@ namespace Microsoft.WSMan.Management /// 4. Enable firewall exception for WS-Management traffic. /// [Cmdlet(VerbsCommon.Set, "WSManQuickConfig", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097112")] + [OutputType(typeof(string))] public class SetWSManQuickConfigCommand : PSCmdlet, IDisposable { /// @@ -55,7 +56,7 @@ public SwitchParameter UseSSL /// Property that sets force parameter. This will allow /// configuring WinRM without prompting the user. /// - [Parameter()] + [Parameter] public SwitchParameter Force { get { return force; } @@ -68,7 +69,7 @@ public SwitchParameter Force /// /// Property that will allow configuring WinRM with Public profile exception enabled. /// - [Parameter()] + [Parameter] public SwitchParameter SkipNetworkProfileCheck { get { return skipNetworkProfileCheck; } diff --git a/src/Microsoft.WSMan.Management/WSManConnections.cs b/src/Microsoft.WSMan.Management/WSManConnections.cs index 85c5d1ba0ae..cac5196740f 100644 --- a/src/Microsoft.WSMan.Management/WSManConnections.cs +++ b/src/Microsoft.WSMan.Management/WSManConnections.cs @@ -213,15 +213,15 @@ public Hashtable OptionSet [Parameter] [ValidateNotNullOrEmpty] [Parameter(ParameterSetName = "ComputerName")] - [ValidateRange(1, Int32.MaxValue)] - public Int32 Port + [ValidateRange(1, int.MaxValue)] + public int Port { get { return port; } set { port = value; } } - private Int32 port = 0; + private int port = 0; /// /// The following is the definition of the input parameter "SessionOption". @@ -360,10 +360,7 @@ public string ComputerName protected override void BeginProcessing() { WSManHelper helper = new WSManHelper(this); - if (computername == null) - { - computername = "localhost"; - } + computername ??= "localhost"; if (this.SessionState.Path.CurrentProviderLocation(WSManStringLiterals.rootpath).Path.StartsWith(WSManStringLiterals.rootpath + ":" + WSManStringLiterals.DefaultPathSeparator + computername, StringComparison.OrdinalIgnoreCase)) { diff --git a/src/Microsoft.WSMan.Management/WSManInstance.cs b/src/Microsoft.WSMan.Management/WSManInstance.cs index 733316c785f..c96b002123d 100644 --- a/src/Microsoft.WSMan.Management/WSManInstance.cs +++ b/src/Microsoft.WSMan.Management/WSManInstance.cs @@ -29,6 +29,7 @@ namespace Microsoft.WSMan.Management /// -SelectorSet {Name=Spooler} /// [Cmdlet(VerbsCommon.Get, "WSManInstance", DefaultParameterSetName = "GetInstance", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096627")] + [OutputType(typeof(XmlElement))] public class GetWSManInstanceCommand : AuthenticatingWSManCommand, IDisposable { #region parameter @@ -247,7 +248,7 @@ public Hashtable OptionSet /// [Parameter(ParameterSetName = "Enumerate")] [Parameter(ParameterSetName = "GetInstance")] - public Int32 Port + public int Port { get { @@ -260,7 +261,7 @@ public Int32 Port } } - private Int32 port = 0; + private int port = 0; /// /// The following is the definition of the input parameter "Associations". @@ -325,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 { @@ -685,6 +686,7 @@ protected override void EndProcessing() /// -SelectorSet {Name=Spooler} /// [Cmdlet(VerbsCommon.Set, "WSManInstance", DefaultParameterSetName = "ComputerName", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096937")] + [OutputType(typeof(XmlElement), typeof(string))] public class SetWSManInstanceCommand : AuthenticatingWSManCommand, IDisposable { #region Parameters @@ -822,15 +824,15 @@ public Hashtable OptionSet /// [Parameter(ParameterSetName = "ComputerName")] [ValidateNotNullOrEmpty] - [ValidateRange(1, Int32.MaxValue)] - public Int32 Port + [ValidateRange(1, int.MaxValue)] + public int Port { get { return port; } set { port = value; } } - private Int32 port = 0; + private int port = 0; /// /// The following is the definition of the input parameter "ResourceURI". @@ -1146,15 +1148,15 @@ public Hashtable OptionSet /// [Parameter(ParameterSetName = "ComputerName")] [ValidateNotNullOrEmpty] - [ValidateRange(1, Int32.MaxValue)] - public Int32 Port + [ValidateRange(1, int.MaxValue)] + public int Port { get { return port; } set { port = value; } } - private Int32 port = 0; + private int port = 0; /// /// The following is the definition of the input parameter "ResourceURI". @@ -1324,6 +1326,7 @@ protected override void ProcessRecord() /// using specified ValueSet or input File. /// [Cmdlet(VerbsCommon.New, "WSManInstance", DefaultParameterSetName = "ComputerName", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096933")] + [OutputType(typeof(XmlElement))] public class NewWSManInstanceCommand : AuthenticatingWSManCommand, IDisposable { /// @@ -1428,15 +1431,15 @@ public Hashtable OptionSet /// [Parameter(ParameterSetName = "ComputerName")] [ValidateNotNullOrEmpty] - [ValidateRange(1, Int32.MaxValue)] - public Int32 Port + [ValidateRange(1, int.MaxValue)] + public int Port { get { return port; } set { port = value; } } - private Int32 port = 0; + private int port = 0; /// /// The following is the definition of the input parameter "ResourceURI". diff --git a/src/Microsoft.WSMan.Management/WsManHelper.cs b/src/Microsoft.WSMan.Management/WsManHelper.cs index 000cb871abb..482f24aa9c1 100644 --- a/src/Microsoft.WSMan.Management/WsManHelper.cs +++ b/src/Microsoft.WSMan.Management/WsManHelper.cs @@ -178,15 +178,8 @@ private static string FormatResourceMsgFromResourcetextS( string resourceName, object[] args) { - if (resourceManager == null) - { - throw new ArgumentNullException(nameof(resourceManager)); - } - - if (string.IsNullOrEmpty(resourceName)) - { - throw new ArgumentNullException(nameof(resourceName)); - } + ArgumentNullException.ThrowIfNull(resourceManager); + ArgumentException.ThrowIfNullOrEmpty(resourceName); string template = resourceManager.GetString(resourceName); @@ -370,17 +363,8 @@ internal string ReadFile(string path) } finally { - if (_sr != null) - { - // _sr.Close(); - _sr.Dispose(); - } - - if (_fs != null) - { - // _fs.Close(); - _fs.Dispose(); - } + _sr?.Dispose(); + _fs?.Dispose(); } return strOut; @@ -627,7 +611,7 @@ internal static void ValidateSpecifiedAuthentication(AuthenticationMechanism aut if ((credential != null) && (certificateThumbprint != null)) { string message = FormatResourceMsgFromResourcetextS( - "AmbiguosAuthentication", + "AmbiguousAuthentication", "CertificateThumbPrint", "credential"); throw new InvalidOperationException(message); @@ -638,7 +622,7 @@ internal static void ValidateSpecifiedAuthentication(AuthenticationMechanism aut (certificateThumbprint != null)) { string message = FormatResourceMsgFromResourcetextS( - "AmbiguosAuthentication", + "AmbiguousAuthentication", "CertificateThumbPrint", authentication.ToString()); throw new InvalidOperationException(message); @@ -1078,13 +1062,10 @@ internal static void LoadResourceData() { try { - string filepath = System.Environment.ExpandEnvironmentVariables("%Windir%") + "\\System32\\Winrm\\" + -#if CORECLR - "0409" /* TODO: don't assume it is always English on CSS? */ -#else - string.Concat("0", string.Format(CultureInfo.CurrentCulture, "{0:x2}", checked((uint)CultureInfo.CurrentUICulture.LCID))) -#endif - + "\\" + "winrm.ini"; + string winDir = System.Environment.ExpandEnvironmentVariables("%Windir%"); + uint lcid = checked((uint)CultureInfo.CurrentUICulture.LCID); + string filepath = string.Create(CultureInfo.CurrentCulture, $@"{winDir}\System32\Winrm\0{lcid:x2}\winrm.ini"); + if (File.Exists(filepath)) { FileStream _fs = new FileStream(filepath, FileMode.Open, FileAccess.Read); diff --git a/src/Microsoft.WSMan.Management/resources/WsManResources.resx b/src/Microsoft.WSMan.Management/resources/WsManResources.resx index c55aab1132f..a25f6c4801f 100644 --- a/src/Microsoft.WSMan.Management/resources/WsManResources.resx +++ b/src/Microsoft.WSMan.Management/resources/WsManResources.resx @@ -191,7 +191,7 @@ Do you want to enable CredSSP authentication? This command cannot be used because the parameter matches a non-text property on the ResourceURI.Check the input parameters and run your command. - + A {0} cannot be specified when {1} is specified. diff --git a/src/Microsoft.WSMan.Management/resources/WsManResources.txt b/src/Microsoft.WSMan.Management/resources/WsManResources.txt index 50cf1bf1fe5..10702e5ccea 100644 --- a/src/Microsoft.WSMan.Management/resources/WsManResources.txt +++ b/src/Microsoft.WSMan.Management/resources/WsManResources.txt @@ -56,7 +56,7 @@ CredSSPServiceConfigured=This computer is configured to receive credentials from CredSSPServiceNotConfigured=This computer is not configured to receive credentials from a remote client computer. QuickConfigContinueCaption=WinRM Quick Configuration QuickConfigContinueQuery=Running the Set-WSManQuickConfig command has significant security implications, as it enables remote management through the WinRM service on this computer.\nThis command:\n 1. Checks whether the WinRM service is running. If the WinRM service is not running, the service is started.\n 2. Sets the WinRM service startup type to automatic.\n 3. Creates a listener to accept requests on any IP address. By default, the transport is HTTP.\n 4. Enables a firewall exception for WS-Management traffic.\n 5. Enables Kerberos and Negotiate service authentication.\nDo you want to enable remote management through the WinRM service on this computer? -AmbiguosAuthentication=A {0} cannot be specified when {1} is specified. +AmbiguousAuthentication=A {0} cannot be specified when {1} is specified. CmdletNotAvailable=This PowerShell cmdlet is not available on for Windows XP and Windows Server 2003. InvalidValueType=This command cannot be used because the parameter value type is invalid. {0} configuration expects a value of Type {1}. Verify that the value is correct and try again. ClearItemOnRunAsPassword=The RunAsPassword value cannot be removed. Remove the values for RunAsUser and RunAsPassword in PowerShell by calling the Clear-Item cmdlet with the value for -Path attribute equal to the value of RunAsUser. diff --git a/src/Modules/PSGalleryModules.csproj b/src/Modules/PSGalleryModules.csproj index ae838f27601..b90e537ca21 100644 --- a/src/Modules/PSGalleryModules.csproj +++ b/src/Modules/PSGalleryModules.csproj @@ -5,18 +5,19 @@ Microsoft Corporation (c) Microsoft Corporation. - net6.0 + net10.0 true - + + - - - + + + 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 0270ceffca0..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/powershell71-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 da8f8707945..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/powershell71-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 1f4cc15e118..adab0df2849 100644 --- a/src/Modules/Unix/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 +++ b/src/Modules/Unix/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 @@ -1,14 +1,14 @@ @{ -GUID="A94C8C7E-9810-47C0-B8AF-65089C13A35A" -Author="PowerShell" -CompanyName="Microsoft Corporation" -Copyright="Copyright (c) Microsoft Corporation." -ModuleVersion="7.0.0.0" +GUID = "A94C8C7E-9810-47C0-B8AF-65089C13A35A" +Author = "PowerShell" +CompanyName = "Microsoft Corporation" +Copyright = "Copyright (c) Microsoft Corporation." +ModuleVersion = "7.0.0.0" CompatiblePSEditions = @("Core") -PowerShellVersion="3.0" +PowerShellVersion = "3.0" FunctionsToExport = @() -CmdletsToExport="Get-Credential", "Get-ExecutionPolicy", "Set-ExecutionPolicy", "ConvertFrom-SecureString", "ConvertTo-SecureString", "Get-PfxCertificate" , "Protect-CmsMessage", "Unprotect-CmsMessage", "Get-CmsMessage" +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/powershell71-help' +NestedModules = "Microsoft.PowerShell.Security.dll" +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 b883acdd0d6..df841837696 100644 --- a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 +++ b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 @@ -19,30 +19,17 @@ CmdletsToExport = @( 'New-Object', 'Select-Object', 'Sort-Object', 'Tee-Object', 'Register-ObjectEvent', 'Write-Output', 'Import-PowerShellDataFile', 'Write-Progress', 'Disable-PSBreakpoint', 'Enable-PSBreakpoint', 'Get-PSBreakpoint', 'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession', - 'Import-PSSession', 'Get-Random', 'Invoke-RestMethod', 'Debug-Runspace', 'Get-Runspace', + 'Import-PSSession', 'Get-Random', 'Get-SecureRandom', 'Invoke-RestMethod', 'Debug-Runspace', 'Get-Runspace', 'Disable-RunspaceDebug', 'Enable-RunspaceDebug', 'Get-RunspaceDebug', 'Start-Sleep', 'Join-String', 'Out-String', 'Select-String', 'ConvertFrom-StringData', 'Format-Table', 'New-TemporaryFile', 'New-TimeSpan', 'Get-TraceSource', 'Set-TraceSource', 'Add-Type', 'Get-TypeData', 'Remove-TypeData', 'Update-TypeData', 'Get-UICulture', 'Get-Unique', 'Get-Uptime', 'Clear-Variable', 'Get-Variable', 'New-Variable', 'Remove-Variable', 'Set-Variable', 'Get-Verb', 'Write-Verbose', 'Write-Warning', 'Invoke-WebRequest', - 'Format-Wide', 'ConvertTo-Xml', 'Select-Xml', 'Get-Error', 'Update-List', 'Unblock-File' + 'Format-Wide', 'ConvertTo-Xml', 'Select-Xml', 'Get-Error', 'Update-List', 'Unblock-File', 'ConvertTo-CliXml', + 'ConvertFrom-CliXml' ) FunctionsToExport = @() AliasesToExport = @('fhx') NestedModules = @("Microsoft.PowerShell.Commands.Utility.dll") -HelpInfoURI = 'https://aka.ms/powershell71-help' -PrivateData = @{ - PSData = @{ - ExperimentalFeatures = @( - @{ - Name = 'Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace' - Description = 'Enables -BreakAll parameter on Debug-Runspace and Debug-Job cmdlets to allow users to decide if they want PowerShell to break immediately in the current location when they attach a debugger. Enables -Runspace parameter on *-PSBreakpoint cmdlets to support management of breakpoints in another runspace.' - } - @{ - Name = 'Microsoft.PowerShell.Utility.PSImportPSDataFileSkipLimitCheck' - Description = 'Enable -SkipLimitCheck switch for Import-PowerShellDataFile to not enforce built-in hashtable limits' - } - ) - } -} +HelpInfoURI = 'https://aka.ms/powershell75-help' } diff --git a/src/Modules/Windows/CimCmdlets/CimCmdlets.psd1 b/src/Modules/Windows/CimCmdlets/CimCmdlets.psd1 index 4b38d4abc9e..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/powershell71-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 ed2344b51b2..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/powershell71-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 f7cd1dc6ace..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/powershell71-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 cbc5b2dc78e..0953b2d1cca 100644 --- a/src/Modules/Windows/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 +++ b/src/Modules/Windows/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 @@ -1,14 +1,18 @@ @{ -GUID="A94C8C7E-9810-47C0-B8AF-65089C13A35A" -Author="PowerShell" -CompanyName="Microsoft Corporation" -Copyright="Copyright (c) Microsoft Corporation." -ModuleVersion="7.0.0.0" +GUID = "A94C8C7E-9810-47C0-B8AF-65089C13A35A" +Author = "PowerShell" +CompanyName = "Microsoft Corporation" +Copyright = "Copyright (c) Microsoft Corporation." +ModuleVersion = "7.0.0.0" CompatiblePSEditions = @("Core") -PowerShellVersion="3.0" +PowerShellVersion = "3.0" FunctionsToExport = @() -CmdletsToExport="Get-Acl", "Set-Acl", "Get-PfxCertificate", "Get-Credential", "Get-ExecutionPolicy", "Set-ExecutionPolicy", "Get-AuthenticodeSignature", "Set-AuthenticodeSignature", "ConvertFrom-SecureString", "ConvertTo-SecureString", "Get-CmsMessage", "Unprotect-CmsMessage", "Protect-CmsMessage" , "New-FileCatalog" , "Test-FileCatalog" +CmdletsToExport = "Get-Acl", "Set-Acl", "Get-PfxCertificate", "Get-Credential", "Get-ExecutionPolicy", "Set-ExecutionPolicy", "Get-AuthenticodeSignature", "Set-AuthenticodeSignature", "ConvertFrom-SecureString", "ConvertTo-SecureString", "Get-CmsMessage", "Unprotect-CmsMessage", "Protect-CmsMessage" , "New-FileCatalog" , "Test-FileCatalog" AliasesToExport = @() -NestedModules="Microsoft.PowerShell.Security.dll" -HelpInfoURI = 'https://aka.ms/powershell71-help' +NestedModules = "Microsoft.PowerShell.Security.dll" +# 'Security.types.ps1xml' refers to types from 'Microsoft.PowerShell.Security.dll' and thus requiring to load the assembly before processing the type file. +# 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/powershell75-help' } diff --git a/src/Modules/Windows/Microsoft.PowerShell.Security/Security.types.ps1xml b/src/Modules/Windows/Microsoft.PowerShell.Security/Security.types.ps1xml new file mode 100644 index 00000000000..b1171c98e6a --- /dev/null +++ b/src/Modules/Windows/Microsoft.PowerShell.Security/Security.types.ps1xml @@ -0,0 +1,124 @@ + + + + + + System.Security.AccessControl.ObjectSecurity + + + Path + + Microsoft.PowerShell.Commands.SecurityDescriptorCommandsBase + GetPath + + + + Owner + + Microsoft.PowerShell.Commands.SecurityDescriptorCommandsBase + GetOwner + + + + Group + + Microsoft.PowerShell.Commands.SecurityDescriptorCommandsBase + GetGroup + + + + Access + + Microsoft.PowerShell.Commands.SecurityDescriptorCommandsBase + GetAccess + + + + Sddl + + Microsoft.PowerShell.Commands.SecurityDescriptorCommandsBase + GetSddl + + + + AccessToString + + $toString = ""; + $first = $true; + if ( ! $this.Access ) { return "" } + foreach($ace in $this.Access) + { + if($first) + { + $first = $false; + } + else + { + $tostring += "`n"; + } + $toString += $ace.IdentityReference.ToString(); + $toString += " "; + $toString += $ace.AccessControlType.ToString(); + $toString += " "; + if($ace -is [System.Security.AccessControl.FileSystemAccessRule]) + { + $toString += $ace.FileSystemRights.ToString(); + } + elseif($ace -is [System.Security.AccessControl.RegistryAccessRule]) + { + $toString += $ace.RegistryRights.ToString(); + } + } + return $toString; + + + + AuditToString + + $toString = ""; + $first = $true; + if ( ! (& { Set-StrictMode -Version 1; $this.audit }) ) { return "" } + foreach($ace in (& { Set-StrictMode -Version 1; $this.audit })) + { + if($first) + { + $first = $false; + } + else + { + $tostring += "`n"; + } + $toString += $ace.IdentityReference.ToString(); + $toString += " "; + $toString += $ace.AuditFlags.ToString(); + $toString += " "; + if($ace -is [System.Security.AccessControl.FileSystemAuditRule]) + { + $toString += $ace.FileSystemRights.ToString(); + } + elseif($ace -is [System.Security.AccessControl.RegistryAuditRule]) + { + $toString += $ace.RegistryRights.ToString(); + } + } + return $toString; + + + + + + 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 5ea504197fa..2043543a8a5 100644 --- a/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 +++ b/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 @@ -17,31 +17,17 @@ CmdletsToExport = @( 'Show-Markdown', 'Get-MarkdownOption', 'Set-MarkdownOption', 'Add-Member', 'Get-Member', 'Compare-Object', 'Group-Object', 'Measure-Object', 'New-Object', 'Select-Object', 'Sort-Object', 'Tee-Object', 'Register-ObjectEvent', 'Write-Output', 'Import-PowerShellDataFile', 'Write-Progress', 'Disable-PSBreakpoint', 'Enable-PSBreakpoint', 'Get-PSBreakpoint', - 'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession', 'Import-PSSession', 'Get-Random', + 'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession', 'Import-PSSession', 'Get-Random', 'Get-SecureRandom' 'Invoke-RestMethod', 'Debug-Runspace', 'Get-Runspace', 'Disable-RunspaceDebug', 'Enable-RunspaceDebug', 'Get-RunspaceDebug', 'ConvertFrom-SddlString', 'Start-Sleep', 'Join-String', 'Out-String', 'Select-String', 'ConvertFrom-StringData', 'Format-Table', 'New-TemporaryFile', 'New-TimeSpan', 'Get-TraceSource', 'Set-TraceSource', 'Add-Type', 'Get-TypeData', 'Remove-TypeData', 'Update-TypeData', 'Get-UICulture', 'Get-Unique', 'Get-Uptime', 'Clear-Variable', 'Get-Variable', 'New-Variable', 'Remove-Variable', 'Set-Variable', 'Get-Verb', 'Write-Verbose', 'Write-Warning', 'Invoke-WebRequest', 'Format-Wide', 'ConvertTo-Xml', 'Select-Xml', 'Get-Error', 'Update-List', - 'Out-GridView', 'Show-Command', 'Out-Printer' + 'Out-GridView', 'Show-Command', 'Out-Printer', 'ConvertTo-CliXml', 'ConvertFrom-CliXml' ) FunctionsToExport = @() AliasesToExport = @('fhx') NestedModules = @("Microsoft.PowerShell.Commands.Utility.dll") -HelpInfoURI = 'https://aka.ms/powershell71-help' -PrivateData = @{ - PSData = @{ - ExperimentalFeatures = @( - @{ - Name = 'Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace' - Description = 'Enables -BreakAll parameter on Debug-Runspace and Debug-Job cmdlets to allow users to decide if they want PowerShell to break immediately in the current location when they attach a debugger. Enables -Runspace parameter on *-PSBreakpoint cmdlets to support management of breakpoints in another runspace.' - } - @{ - Name = 'Microsoft.PowerShell.Utility.PSImportPSDataFileSkipLimitCheck' - Description = 'Enable -NoLimit switch for Import-PowerShellDataFile to not enforce built-in hashtable limits' - } - ) - } -} +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 d2bb2398541..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/powershell71-help' +HelpInfoURI = 'https://aka.ms/powershell75-help' } diff --git a/src/Modules/Windows/PSDiagnostics/PSDiagnostics.psd1 b/src/Modules/Windows/PSDiagnostics/PSDiagnostics.psd1 index dded04d4920..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/powershell71-help" + HelpInfoUri="https://aka.ms/powershell75-help" } diff --git a/src/Modules/nuget.config b/src/Modules/nuget.config index f5a7f806a36..388a65572dd 100644 --- a/src/Modules/nuget.config +++ b/src/Modules/nuget.config @@ -2,8 +2,7 @@ - - + diff --git a/src/PowerShell.Core.Instrumentation/PowerShell.Core.Instrumentation.man b/src/PowerShell.Core.Instrumentation/PowerShell.Core.Instrumentation.man index 5d4dd473f4b..fb221cfe964 100644 --- a/src/PowerShell.Core.Instrumentation/PowerShell.Core.Instrumentation.man +++ b/src/PowerShell.Core.Instrumentation/PowerShell.Core.Instrumentation.man @@ -2208,6 +2208,18 @@ value="0x6017" version="1" /> + + this cell is the table header, footer or body --> bottom @@ -381,4 +381,4 @@ - \ No newline at end of file + diff --git a/src/System.Management.Automation/AssemblyInfo.cs b/src/System.Management.Automation/AssemblyInfo.cs index 1963b331ef1..559d809fe72 100644 --- a/src/System.Management.Automation/AssemblyInfo.cs +++ b/src/System.Management.Automation/AssemblyInfo.cs @@ -6,33 +6,11 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("powershell-tests,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +[assembly: InternalsVisibleTo("powershell-perf,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] -[assembly: InternalsVisibleTo("Microsoft.Test.Management.Automation.GPowershell.Analyzers,PublicKey=00240000048000009400000006020000002400005253413100040000010001003f8c902c8fe7ac83af7401b14c1bd103973b26dfafb2b77eda478a2539b979b56ce47f36336741b4ec52bbc51fecd51ba23810cec47070f3e29a2261a2d1d08e4b2b4b457beaa91460055f78cc89f21cd028377af0cc5e6c04699b6856a1e49d5fad3ef16d3c3d6010f40df0a7d6cc2ee11744b5cfb42e0f19a52b8a29dc31b0")] - -#if NOT_SIGNED -// These attributes aren't every used, it's just a hack to get VS to not complain -// about access when editing using the project files that don't actually build. -[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Commands.Utility")] -[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Commands.Management")] -[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Security")] -[assembly: InternalsVisibleTo(@"System.Management.Automation.Remoting")] -[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.ConsoleHost")] -#else [assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Commands.Utility" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Commands.Management" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Security" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo(@"System.Management.Automation.Remoting" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo(@"Microsoft.PowerShell.ConsoleHost" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] -#endif - -namespace System.Management.Automation -{ - internal static class NTVerpVars - { - internal const int PRODUCTMAJORVERSION = 10; - internal const int PRODUCTMINORVERSION = 0; - internal const int PRODUCTBUILD = 10032; - internal const int PRODUCTBUILD_QFE = 0; - internal const int PACKAGEBUILD_QFE = 814; - } -} +[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.DscSubsystem" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/src/System.Management.Automation/CoreCLR/CorePsAssemblyLoadContext.cs b/src/System.Management.Automation/CoreCLR/CorePsAssemblyLoadContext.cs index 5bd9be5c62b..5a15df53ca8 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsAssemblyLoadContext.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsAssemblyLoadContext.cs @@ -9,13 +9,14 @@ using System.Runtime.InteropServices; using System.Reflection; using System.Runtime.Loader; +using Microsoft.PowerShell.Telemetry; namespace System.Management.Automation { /// /// The powershell custom AssemblyLoadContext implementation. /// - internal partial class PowerShellAssemblyLoadContext + internal sealed partial class PowerShellAssemblyLoadContext { #region Resource_Strings @@ -36,16 +37,19 @@ internal partial class PowerShellAssemblyLoadContext /// /// Initialize a singleton of PowerShellAssemblyLoadContext. /// - internal static PowerShellAssemblyLoadContext InitializeSingleton(string basePaths) + internal static PowerShellAssemblyLoadContext InitializeSingleton(string basePaths, bool throwOnReentry) { lock (s_syncObj) { - if (Instance != null) + if (Instance is null) + { + Instance = new PowerShellAssemblyLoadContext(basePaths); + } + else if (throwOnReentry) { throw new InvalidOperationException(SingletonAlreadyInitialized); } - Instance = new PowerShellAssemblyLoadContext(basePaths); return Instance; } } @@ -232,6 +236,9 @@ internal IEnumerable GetAssembly(string namespaceQualifiedTypeName) /// | /// |--- 'osx-x64' subfolder /// | |--- native.dylib + /// | + /// |--- 'osx-arm64' subfolder + /// | |--- native.dylib /// internal static IntPtr NativeDllHandler(Assembly assembly, string libraryName) { @@ -343,9 +350,6 @@ private bool TryFindInGAC(AssemblyName assemblyName, out string assemblyFilePath return false; } - bool assemblyFound = false; - char dirSeparator = IO.Path.DirectorySeparatorChar; - if (string.IsNullOrEmpty(_winDir)) { // cache value of '_winDir' folder in member variable. @@ -355,21 +359,21 @@ private bool TryFindInGAC(AssemblyName assemblyName, out string assemblyFilePath if (string.IsNullOrEmpty(_gacPathMSIL)) { // cache value of '_gacPathMSIL' folder in member variable. - _gacPathMSIL = $"{_winDir}{dirSeparator}Microsoft.NET{dirSeparator}assembly{dirSeparator}GAC_MSIL"; + _gacPathMSIL = Path.Join(_winDir, "Microsoft.NET", "assembly", "GAC_MSIL"); } - assemblyFound = FindInGac(_gacPathMSIL, assemblyName, out assemblyFilePath); + bool assemblyFound = FindInGac(_gacPathMSIL, assemblyName, out assemblyFilePath); if (!assemblyFound) { - string gacBitnessAwarePath = null; + string gacBitnessAwarePath; if (Environment.Is64BitProcess) { if (string.IsNullOrEmpty(_gacPath64)) { - // cache value of '_gacPath64' folder in member variable. - _gacPath64 = $"{_winDir}{dirSeparator}Microsoft.NET{dirSeparator}assembly{dirSeparator}GAC_64"; + var gacName = RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? "GAC_Arm64" : "GAC_64"; + _gacPath64 = Path.Join(_winDir, "Microsoft.NET", "assembly", gacName); } gacBitnessAwarePath = _gacPath64; @@ -378,8 +382,7 @@ private bool TryFindInGAC(AssemblyName assemblyName, out string assemblyFilePath { if (string.IsNullOrEmpty(_gacPath32)) { - // cache value of '_gacPath32' folder in member variable. - _gacPath32 = $"{_winDir}{dirSeparator}Microsoft.NET{dirSeparator}assembly{dirSeparator}GAC_32"; + _gacPath32 = Path.Join(_winDir, "Microsoft.NET", "assembly", "GAC_32"); } gacBitnessAwarePath = _gacPath32; @@ -397,13 +400,12 @@ private static bool FindInGac(string gacRoot, AssemblyName assemblyName, out str bool assemblyFound = false; assemblyPath = null; - char dirSeparator = IO.Path.DirectorySeparatorChar; - string tempAssemblyDirPath = $"{gacRoot}{dirSeparator}{assemblyName.Name}"; + string tempAssemblyDirPath = Path.Join(gacRoot, assemblyName.Name); if (Directory.Exists(tempAssemblyDirPath)) { // Enumerate all directories, sort by name and select the last. This selects the latest version. - var chosenVersionDirectory = Directory.EnumerateDirectories(tempAssemblyDirPath).OrderBy(d => d).LastOrDefault(); + var chosenVersionDirectory = Directory.EnumerateDirectories(tempAssemblyDirPath).Order().LastOrDefault(); if (!string.IsNullOrEmpty(chosenVersionDirectory)) { @@ -540,19 +542,19 @@ private static string GetNativeDllSubFolderName(out string ext) ext = string.Empty; var processArch = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant(); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (Platform.IsWindows) { folderName = "win-" + processArch; ext = ".dll"; } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + else if (Platform.IsLinux) { folderName = "linux-" + processArch; ext = ".so"; } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + else if (Platform.IsMacOS) { - folderName = "osx-x64"; + folderName = "osx-" + processArch; ext = ".dylib"; } @@ -581,10 +583,44 @@ public static class PowerShellAssemblyLoadContextInitializer /// public static void SetPowerShellAssemblyLoadContext([MarshalAs(UnmanagedType.LPWStr)] string basePaths) { - if (string.IsNullOrEmpty(basePaths)) - throw new ArgumentNullException(nameof(basePaths)); + ArgumentException.ThrowIfNullOrEmpty(basePaths); + + // Disallow calling this method from native code for more than once. + PowerShellAssemblyLoadContext.InitializeSingleton(basePaths, throwOnReentry: true); + } + } + + /// + /// Provides helper functions to facilitate calling managed code from a native PowerShell host. + /// + public static unsafe class PowerShellUnsafeAssemblyLoad + { + /// + /// Load an assembly in memory from unmanaged code. + /// + /// + /// This API is covered by the experimental feature 'PSLoadAssemblyFromNativeCode', + /// and it may be deprecated and removed in future. + /// + /// Unmanaged pointer to assembly data buffer. + /// Size in bytes of the assembly data buffer. + /// Returns zero on success and non-zero on failure. + [UnmanagedCallersOnly] + public static int LoadAssemblyFromNativeMemory(IntPtr data, int size) + { + int result = 0; + try + { + using var stream = new UnmanagedMemoryStream((byte*)data, size); + AssemblyLoadContext.Default.LoadFromStream(stream); + } + catch + { + result = -1; + } - PowerShellAssemblyLoadContext.InitializeSingleton(basePaths); + ApplicationInsightsTelemetry.SendUseTelemetry("PowerShellUnsafeAssemblyLoad", result == 0 ? "1" : "0"); + return result; } } } diff --git a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs index b5e657d497f..dc5db5f2c48 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs @@ -5,19 +5,16 @@ using System.ComponentModel; using System.IO; using System.Runtime.InteropServices; - +using System.Management.Automation.Internal; using Microsoft.Win32; -using Microsoft.Win32.SafeHandles; namespace System.Management.Automation { /// /// These are platform abstractions and platform specific implementations. /// - public static class Platform + public static partial class Platform { - private static string _tempDirectory = null; - /// /// True if the current platform is Linux. /// @@ -25,7 +22,7 @@ public static bool IsLinux { get { - return RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + return OperatingSystem.IsLinux(); } } @@ -36,7 +33,7 @@ public static bool IsMacOS { get { - return RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + return OperatingSystem.IsMacOS(); } } @@ -47,7 +44,7 @@ public static bool IsWindows { get { - return RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + return OperatingSystem.IsWindows(); } } @@ -72,7 +69,10 @@ public static bool IsNanoServer #if UNIX return false; #else - if (_isNanoServer.HasValue) { return _isNanoServer.Value; } + if (_isNanoServer.HasValue) + { + return _isNanoServer.Value; + } _isNanoServer = false; using (RegistryKey regKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Server\ServerLevels")) @@ -102,7 +102,10 @@ public static bool IsIoT #if UNIX return false; #else - if (_isIoT.HasValue) { return _isIoT.Value; } + if (_isIoT.HasValue) + { + return _isIoT.Value; + } _isIoT = false; using (RegistryKey regKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion")) @@ -132,7 +135,10 @@ public static bool IsWindowsDesktop #if UNIX return false; #else - if (_isWindowsDesktop.HasValue) { return _isWindowsDesktop.Value; } + if (_isWindowsDesktop.HasValue) + { + return _isWindowsDesktop.Value; + } _isWindowsDesktop = !IsNanoServer && !IsIoT; return _isWindowsDesktop.Value; @@ -140,6 +146,21 @@ public static bool IsWindowsDesktop } } + /// + /// Gets a value indicating whether the underlying system supports single-threaded apartment. + /// + public static bool IsStaSupported + { + get + { +#if UNIX + return false; +#else + return _isStaSupported.Value; +#endif + } + } + #if UNIX // Gets the location for cache and config folders. internal static readonly string CacheDirectory = Platform.SelectProductNameForDirectory(Platform.XDG_Type.CACHE); @@ -149,13 +170,27 @@ public static bool IsWindowsDesktop internal static readonly string CacheDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\Microsoft\PowerShell"; internal static readonly string ConfigDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal) + @"\PowerShell"; + private static readonly Lazy _isStaSupported = new Lazy(() => + { + 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) + { + Interop.Windows.CoUninitialize(); + } + + return result != Interop.Windows.E_NOTIMPL; + }); + private static bool? _isNanoServer = null; private static bool? _isIoT = null; private static bool? _isWindowsDesktop = null; #endif // format files - internal static readonly List FormatFileNames = new() + internal static readonly string[] FormatFileNames = new string[] { "Certificate.format.ps1xml", "Diagnostics.format.ps1xml", @@ -183,43 +218,37 @@ internal static class CommonEnvVariableNames #endif } +#if UNIX + private static string s_tempHome = null; + /// - /// Remove the temporary directory created for the current process. + /// Get the 'HOME' environment variable or create a temporary home directory if the environment variable is not set. /// - internal static void RemoveTemporaryDirectory() + private static string GetHomeOrCreateTempHome() { - if (_tempDirectory == null) + const string tempHomeFolderName = "pwsh-{0}-98288ff9-5712-4a14-9a11-23693b9cd91a"; + + string envHome = Environment.GetEnvironmentVariable("HOME") ?? s_tempHome; + if (envHome is not null) { - return; + return envHome; } try { - Directory.Delete(_tempDirectory, true); + s_tempHome = Path.Combine(Path.GetTempPath(), StringUtil.Format(tempHomeFolderName, Environment.UserName)); + Directory.CreateDirectory(s_tempHome); } - catch - { - // ignore if there is a failure - } - - _tempDirectory = null; - } - - /// - /// Get a temporary directory to use for the current process. - /// - internal static string GetTemporaryDirectory() - { - if (_tempDirectory != null) + catch (UnauthorizedAccessException) { - return _tempDirectory; + // Directory creation may fail if the account doesn't have filesystem permission such as some service accounts. + // Return an empty string in this case so the process working directory will be used. + s_tempHome = string.Empty; } - _tempDirectory = PsUtils.GetTemporaryDirectory(); - return _tempDirectory; + return s_tempHome; } -#if UNIX /// /// X Desktop Group configuration type enum. /// @@ -239,230 +268,100 @@ public enum XDG_Type DEFAULT } - private static string s_tempHomeDir = null; - /// /// Function for choosing directory location of PowerShell for profile loading. /// - public static string SelectProductNameForDirectory(Platform.XDG_Type dirpath) + public static string SelectProductNameForDirectory(XDG_Type dirpath) { // TODO: XDG_DATA_DIRS implementation as per GitHub issue #1060 - string xdgconfighome = System.Environment.GetEnvironmentVariable("XDG_CONFIG_HOME"); - string xdgdatahome = System.Environment.GetEnvironmentVariable("XDG_DATA_HOME"); - string xdgcachehome = System.Environment.GetEnvironmentVariable("XDG_CACHE_HOME"); - string envHome = System.Environment.GetEnvironmentVariable(CommonEnvVariableNames.Home); - if (envHome == null) - { - s_tempHomeDir ??= GetTemporaryDirectory(); - envHome = s_tempHomeDir; - } + string xdgconfighome = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME"); + string xdgdatahome = Environment.GetEnvironmentVariable("XDG_DATA_HOME"); + string xdgcachehome = Environment.GetEnvironmentVariable("XDG_CACHE_HOME"); + string envHome = GetHomeOrCreateTempHome(); string xdgConfigHomeDefault = Path.Combine(envHome, ".config", "powershell"); string xdgDataHomeDefault = Path.Combine(envHome, ".local", "share", "powershell"); string xdgModuleDefault = Path.Combine(xdgDataHomeDefault, "Modules"); string xdgCacheDefault = Path.Combine(envHome, ".cache", "powershell"); - switch (dirpath) + try { - case Platform.XDG_Type.CONFIG: - // the user has set XDG_CONFIG_HOME corresponding to profile path - if (string.IsNullOrEmpty(xdgconfighome)) - { - // xdg values have not been set - return xdgConfigHomeDefault; - } - - else - { - return Path.Combine(xdgconfighome, "powershell"); - } - - case Platform.XDG_Type.DATA: - // the user has set XDG_DATA_HOME corresponding to module path - if (string.IsNullOrEmpty(xdgdatahome)) - { - // create the xdg folder if needed - if (!Directory.Exists(xdgDataHomeDefault)) + switch (dirpath) + { + case XDG_Type.CONFIG: + // Use 'XDG_CONFIG_HOME' if it's set, otherwise use the default path. + return string.IsNullOrEmpty(xdgconfighome) + ? xdgConfigHomeDefault + : Path.Combine(xdgconfighome, "powershell"); + + case XDG_Type.DATA: + // Use 'XDG_DATA_HOME' if it's set, otherwise use the default path. + if (string.IsNullOrEmpty(xdgdatahome)) { - try - { - Directory.CreateDirectory(xdgDataHomeDefault); - } - catch (UnauthorizedAccessException) - { - // service accounts won't have permission to create user folder - return GetTemporaryDirectory(); - } + // Create the default data directory if it doesn't exist. + Directory.CreateDirectory(xdgDataHomeDefault); + return xdgDataHomeDefault; } - - return xdgDataHomeDefault; - } - else - { return Path.Combine(xdgdatahome, "powershell"); - } - case Platform.XDG_Type.USER_MODULES: - // the user has set XDG_DATA_HOME corresponding to module path - if (string.IsNullOrEmpty(xdgdatahome)) - { - // xdg values have not been set - if (!Directory.Exists(xdgModuleDefault)) // module folder not always guaranteed to exist + case XDG_Type.USER_MODULES: + // Use 'XDG_DATA_HOME' if it's set, otherwise use the default path. + if (string.IsNullOrEmpty(xdgdatahome)) { - try - { - Directory.CreateDirectory(xdgModuleDefault); - } - catch (UnauthorizedAccessException) - { - // service accounts won't have permission to create user folder - return GetTemporaryDirectory(); - } + Directory.CreateDirectory(xdgModuleDefault); + return xdgModuleDefault; } - - return xdgModuleDefault; - } - else - { return Path.Combine(xdgdatahome, "powershell", "Modules"); - } - - case Platform.XDG_Type.SHARED_MODULES: - return "/usr/local/share/powershell/Modules"; - case Platform.XDG_Type.CACHE: - // the user has set XDG_CACHE_HOME - if (string.IsNullOrEmpty(xdgcachehome)) - { - // xdg values have not been set - if (!Directory.Exists(xdgCacheDefault)) // module folder not always guaranteed to exist - { - try - { - Directory.CreateDirectory(xdgCacheDefault); - } - catch (UnauthorizedAccessException) - { - // service accounts won't have permission to create user folder - return GetTemporaryDirectory(); - } - } + case XDG_Type.SHARED_MODULES: + return "/usr/local/share/powershell/Modules"; - return xdgCacheDefault; - } - else - { - if (!Directory.Exists(Path.Combine(xdgcachehome, "powershell"))) + case XDG_Type.CACHE: + // Use 'XDG_CACHE_HOME' if it's set, otherwise use the default path. + if (string.IsNullOrEmpty(xdgcachehome)) { - try - { - Directory.CreateDirectory(Path.Combine(xdgcachehome, "powershell")); - } - catch (UnauthorizedAccessException) - { - // service accounts won't have permission to create user folder - return GetTemporaryDirectory(); - } + Directory.CreateDirectory(xdgCacheDefault); + return xdgCacheDefault; } - return Path.Combine(xdgcachehome, "powershell"); - } - - case Platform.XDG_Type.DEFAULT: - // default for profile location - return xdgConfigHomeDefault; + string cachePath = Path.Combine(xdgcachehome, "powershell"); + Directory.CreateDirectory(cachePath); + return cachePath; - default: - // xdgConfigHomeDefault needs to be created in the edge case that we do not have the folder or it was deleted - // This folder is the default in the event of all other failures for data storage - if (!Directory.Exists(xdgConfigHomeDefault)) - { - try - { - Directory.CreateDirectory(xdgConfigHomeDefault); - } - catch - { - Console.Error.WriteLine("Failed to create default data directory: " + xdgConfigHomeDefault); - } - } + case XDG_Type.DEFAULT: + // Use 'xdgConfigHomeDefault' for 'XDG_Type.DEFAULT' and create the directory if it doesn't exist. + Directory.CreateDirectory(xdgConfigHomeDefault); + return xdgConfigHomeDefault; - return xdgConfigHomeDefault; + default: + throw new InvalidOperationException("Unreachable code."); + } + } + catch (UnauthorizedAccessException) + { + // Directory creation may fail if the account doesn't have filesystem permission such as some service accounts. + // Return an empty string in this case so the process working directory will be used. + return string.Empty; } } #endif /// - /// The code is copied from the .NET implementation. - /// - internal static string GetFolderPath(System.Environment.SpecialFolder folder) - { - return InternalGetFolderPath(folder); - } - - /// - /// The API set 'api-ms-win-shell-shellfolders-l1-1-0.dll' was removed from NanoServer, so we cannot depend on 'SHGetFolderPathW' - /// to get the special folder paths. Instead, we need to rely on the basic environment variables to get the special folder paths. + /// Mimic 'Environment.GetFolderPath(folder)' on Unix. /// - /// - /// The path to the specified system special folder, if that folder physically exists on your computer. - /// Otherwise, an empty string (string.Empty). - /// - private static string InternalGetFolderPath(System.Environment.SpecialFolder folder) + internal static string GetFolderPath(Environment.SpecialFolder folder) { - string folderPath = null; #if UNIX - string envHome = System.Environment.GetEnvironmentVariable(Platform.CommonEnvVariableNames.Home); - if (envHome == null) - { - envHome = Platform.GetTemporaryDirectory(); - } - - switch (folder) + return folder switch { - case System.Environment.SpecialFolder.ProgramFiles: - folderPath = "/bin"; - if (!System.IO.Directory.Exists(folderPath)) { folderPath = null; } - - break; - case System.Environment.SpecialFolder.ProgramFilesX86: - folderPath = "/usr/bin"; - if (!System.IO.Directory.Exists(folderPath)) { folderPath = null; } - - break; - case System.Environment.SpecialFolder.System: - case System.Environment.SpecialFolder.SystemX86: - folderPath = "/sbin"; - if (!System.IO.Directory.Exists(folderPath)) { folderPath = null; } - - break; - case System.Environment.SpecialFolder.Personal: - folderPath = envHome; - break; - case System.Environment.SpecialFolder.LocalApplicationData: - folderPath = System.IO.Path.Combine(envHome, ".config"); - if (!System.IO.Directory.Exists(folderPath)) - { - try - { - System.IO.Directory.CreateDirectory(folderPath); - } - catch (UnauthorizedAccessException) - { - // directory creation may fail if the account doesn't have filesystem permission such as some service accounts - folderPath = string.Empty; - } - } - - break; - default: - throw new NotSupportedException(); - } + Environment.SpecialFolder.ProgramFiles => Directory.Exists("/bin") ? "/bin" : string.Empty, + Environment.SpecialFolder.MyDocuments => GetHomeOrCreateTempHome(), + _ => throw new NotSupportedException() + }; #else - folderPath = System.Environment.GetFolderPath(folder); + return Environment.GetFolderPath(folder); #endif - return folderPath ?? string.Empty; } // Platform methods prefixed NonWindows are: @@ -475,21 +374,11 @@ private static string InternalGetFolderPath(System.Environment.SpecialFolder fol // - only to be used with the IsWindows feature query, and only if // no other more specific feature query makes sense - internal static bool NonWindowsIsHardLink(ref IntPtr handle) - { - return Unix.IsHardLink(ref handle); - } - internal static bool NonWindowsIsHardLink(FileSystemInfo fileInfo) { return Unix.IsHardLink(fileInfo); } - internal static string NonWindowsInternalGetTarget(string path) - { - return Unix.NativeMethods.FollowSymLink(path); - } - internal static string NonWindowsGetUserFromPid(int path) { return Unix.NativeMethods.GetUserFromPid(path); @@ -532,11 +421,9 @@ internal static bool NonWindowsIsSameFileSystemItem(string pathOne, string pathT return Unix.NativeMethods.IsSameFileSystemItem(pathOne, pathTwo); } - internal static bool NonWindowsGetInodeData(string path, out System.ValueTuple inodeData) + internal static bool NonWindowsGetInodeData(string path, out ValueTuple inodeData) { - UInt64 device = 0UL; - UInt64 inode = 0UL; - var result = Unix.NativeMethods.GetInodeData(path, out device, out inode); + var result = Unix.NativeMethods.GetInodeData(path, out ulong device, out ulong inode); inodeData = (device, inode); return result == 0; @@ -557,6 +444,16 @@ internal static int NonWindowsGetProcessParentPid(int pid) return IsMacOS ? Unix.NativeMethods.GetPPid(pid) : Unix.GetProcFSParentPid(pid); } + internal static bool NonWindowsKillProcess(int pid) + { + return Unix.NativeMethods.KillProcess(pid); + } + + internal static int NonWindowsWaitPid(int pid, bool nohang) + { + return Unix.NativeMethods.WaitPid(pid, nohang); + } + // Please note that `Win32Exception(Marshal.GetLastWin32Error())` // works *correctly* on Linux in that it creates an exception with // the string perror would give you for the last set value of errno. @@ -564,10 +461,10 @@ internal static int NonWindowsGetProcessParentPid(int pid) // to a PAL value and calls strerror_r underneath to generate the message. /// Unix specific implementations of required functionality. - internal static class Unix + internal static partial class Unix { - private static Dictionary usernameCache = new(); - private static Dictionary groupnameCache = new(); + private static readonly Dictionary usernameCache = new(); + private static readonly Dictionary groupnameCache = new(); /// The type of a Unix file system item. public enum ItemType @@ -697,82 +594,69 @@ public class CommonStat private const char CanRead = 'r'; private const char CanWrite = 'w'; private const char CanExecute = 'x'; - - // helper for getting unix mode - private Dictionary modeMap = new() - { - { StatMask.OwnerRead, CanRead }, - { StatMask.OwnerWrite, CanWrite }, - { StatMask.OwnerExecute, CanExecute }, - { StatMask.GroupRead, CanRead }, - { StatMask.GroupWrite, CanWrite }, - { StatMask.GroupExecute, CanExecute }, - { StatMask.OtherRead, CanRead }, - { StatMask.OtherWrite, CanWrite }, - { StatMask.OtherExecute, CanExecute }, - }; - - private StatMask[] permissions = new StatMask[] - { - StatMask.OwnerRead, - StatMask.OwnerWrite, - StatMask.OwnerExecute, - StatMask.GroupRead, - StatMask.GroupWrite, - StatMask.GroupExecute, - StatMask.OtherRead, - StatMask.OtherWrite, - StatMask.OtherExecute - }; + private const char NoPerm = '-'; + private const char SetAndExec = 's'; + private const char SetAndNotExec = 'S'; + private const char StickyAndExec = 't'; + private const char StickyAndNotExec = 'T'; // The item type and the character representation for the first element in the stat string - private Dictionary itemTypeTable = new() + private static readonly Dictionary itemTypeTable = new() { - { ItemType.BlockDevice, 'b' }, + { ItemType.BlockDevice, 'b' }, { ItemType.CharacterDevice, 'c' }, - { ItemType.Directory, 'd' }, - { ItemType.File, '-' }, - { ItemType.NamedPipe, 'p' }, - { ItemType.Socket, 's' }, - { ItemType.SymbolicLink, 'l' }, + { ItemType.Directory, 'd' }, + { ItemType.File, '-' }, + { ItemType.NamedPipe, 'p' }, + { ItemType.Socket, 's' }, + { ItemType.SymbolicLink, 'l' }, }; + // We'll create a few common mode strings here to reduce allocations and improve performance a bit. + private const string OwnerReadGroupReadOtherRead = "-r--r--r--"; + private const string OwnerReadWriteGroupReadOtherRead = "-rw-r--r--"; + private const string DirectoryOwnerFullGroupReadExecOtherReadExec = "drwxr-xr-x"; + /// Convert the mode to a string which is usable in our formatting. /// The mode converted into a Unix style string similar to the output of ls. public string GetModeString() { - int offset = 0; - char[] modeCharacters = new char[10]; - modeCharacters[offset++] = itemTypeTable[ItemType]; + // On an Ubuntu system (docker), these 3 are roughly 70% of all the permissions + if ((Mode & 0xFFF) == 292) + { + return OwnerReadGroupReadOtherRead; + } - foreach (StatMask permission in permissions) + if ((Mode & 0xFFF) == 420) { - // determine whether we are setuid, sticky, or the usual rwx. - if ((Mode & (int)permission) == (int)permission) - { - if ((permission == StatMask.OwnerExecute && IsSetUid) || (permission == StatMask.GroupExecute && IsSetGid)) - { - // Check for setuid and add 's' - modeCharacters[offset] = 's'; - } - else if (permission == StatMask.OtherExecute && IsSticky && (ItemType == ItemType.Directory)) - { - // Directories are sticky, rather than setuid - modeCharacters[offset] = 't'; - } - else - { - modeCharacters[offset] = modeMap[permission]; - } - } - else - { - modeCharacters[offset] = '-'; - } + return OwnerReadWriteGroupReadOtherRead; + } - offset++; + if (ItemType == ItemType.Directory & (Mode & 0xFFF) == 493) + { + return DirectoryOwnerFullGroupReadExecOtherReadExec; } + Span 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); + return new string(modeCharacters); } @@ -823,15 +707,6 @@ internal static ErrorCategory GetErrorCategory(int errno) return (ErrorCategory)Unix.NativeMethods.GetErrorCategory(errno); } - /// Is this a hardlink. - /// The handle to a file. - /// A boolean that represents whether the item is a hardlink. - public static bool IsHardLink(ref IntPtr handle) - { - // TODO:PSL implement using fstat to query inode refcount to see if it is a hard link - return false; - } - /// Determine if the item is a hardlink. /// A FileSystemInfo to check to determine if it is a hardlink. /// A boolean that represents whether the item is a hardlink. @@ -972,20 +847,40 @@ public static int GetProcFSParentPid(int pid) { const int invalidPid = -1; - // read /proc//stat - // 4th column will contain the ppid, 92 in the example below - // ex: 93 (bash) S 92 93 2 4294967295 ... - var path = $"/proc/{pid}/stat"; + // read /proc//status + // Row beginning with PPid: \d is the parent process id. + // This used to check /proc//stat but that file was meant + // to be a space delimited line but it contains a value which + // could contain spaces itself. Using the status file is a lot + // simpler because each line contains a record with a simple + // label. + // https://github.com/PowerShell/PowerShell/issues/17541#issuecomment-1159911577 + var path = $"/proc/{pid}/status"; try { - var stat = System.IO.File.ReadAllText(path); - var parts = stat.Split(' ', 5); - if (parts.Length < 5) + using FileStream fs = File.OpenRead(path); + using StreamReader sr = new(fs); + string line; + while ((line = sr.ReadLine()) != null) { - return invalidPid; + if (!line.StartsWith("PPid:\t", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + string[] lineSplit = line.Split('\t', 2, StringSplitOptions.RemoveEmptyEntries); + if (lineSplit.Length != 2) + { + continue; + } + + if (int.TryParse(lineSplit[1].Trim(), out var ppid)) + { + return ppid; + } } - return Int32.Parse(parts[3]); + return invalidPid; } catch (Exception) { @@ -994,31 +889,40 @@ public static int GetProcFSParentPid(int pid) } /// The native methods class. - internal static class NativeMethods + internal static partial class NativeMethods { private const string psLib = "libpsl-native"; // Ansi is a misnomer, it is hardcoded to UTF-8 on Linux and macOS - // C bools are 1 byte and so must be marshaled as I1 + // C bools are 1 byte and so must be marshalled as I1 - [DllImport(psLib, CharSet = CharSet.Ansi)] - internal static extern int GetErrorCategory(int errno); + [LibraryImport(psLib)] + internal static partial int GetErrorCategory(int errno); - [DllImport(psLib)] - internal static extern int GetPPid(int pid); + [LibraryImport(psLib)] + internal static partial int GetPPid(int pid); - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] - internal static extern int GetLinkCount([MarshalAs(UnmanagedType.LPStr)] string filePath, out int linkCount); + [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8, SetLastError = true)] + internal static partial int GetLinkCount(string filePath, out int linkCount); - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] + [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)] [return: MarshalAs(UnmanagedType.I1)] - internal static extern bool IsExecutable([MarshalAs(UnmanagedType.LPStr)] string filePath); + internal static partial bool IsExecutable(string filePath); - [DllImport(psLib, CharSet = CharSet.Ansi)] - internal static extern uint GetCurrentThreadId(); + [LibraryImport(psLib)] + internal static partial uint GetCurrentThreadId(); - // This is a struct tm from . - [StructLayout(LayoutKind.Sequential)] + [LibraryImport(psLib)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool KillProcess(int pid); + + [LibraryImport(psLib)] + internal static partial int WaitPid(int pid, [MarshalAs(UnmanagedType.Bool)] bool nohang); + + // This is the struct `private_tm` from setdate.h in libpsl-native. + // Packing is set to 4 to match the unmanaged declaration. + // https://github.com/PowerShell/PowerShell-Native/blob/c5575ceb064e60355b9fee33eabae6c6d2708d14/src/libpsl-native/src/setdate.h#L23 + [StructLayout(LayoutKind.Sequential, Pack = 4)] internal unsafe struct UnixTm { /// Seconds (0-60). @@ -1065,33 +969,25 @@ internal static UnixTm DateTimeToUnixTm(DateTime date) return tm; } - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] - internal static extern unsafe int SetDate(UnixTm* tm); + [LibraryImport(psLib, SetLastError = true)] + internal static unsafe partial int SetDate(UnixTm* tm); - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] - internal static extern int CreateSymLink([MarshalAs(UnmanagedType.LPStr)] string filePath, - [MarshalAs(UnmanagedType.LPStr)] string target); + [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)] + internal static partial int CreateSymLink(string filePath, string target); - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] - internal static extern int CreateHardLink([MarshalAs(UnmanagedType.LPStr)] string filePath, - [MarshalAs(UnmanagedType.LPStr)] string target); - - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] - [return: MarshalAs(UnmanagedType.LPStr)] - internal static extern string FollowSymLink([MarshalAs(UnmanagedType.LPStr)] string filePath); + [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)] + internal static partial int CreateHardLink(string filePath, string target); - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] + [LibraryImport(psLib)] [return: MarshalAs(UnmanagedType.LPStr)] - internal static extern string GetUserFromPid(int pid); + internal static partial string GetUserFromPid(int pid); - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] + [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)] [return: MarshalAs(UnmanagedType.I1)] - internal static extern bool IsSameFileSystemItem([MarshalAs(UnmanagedType.LPStr)] string filePathOne, - [MarshalAs(UnmanagedType.LPStr)] string filePathTwo); + internal static partial bool IsSameFileSystemItem(string filePathOne, string filePathTwo); - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] - internal static extern int GetInodeData([MarshalAs(UnmanagedType.LPStr)] string path, - out UInt64 device, out UInt64 inode); + [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)] + internal static partial int GetInodeData(string path, out ulong device, out ulong inode); /// /// This is a struct from getcommonstat.h in the native library. @@ -1169,17 +1065,17 @@ internal struct CommonStatStruct internal int IsSticky; } - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] - internal static extern unsafe int GetCommonLStat(string filePath, [Out] out CommonStatStruct cs); + [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8, SetLastError = true)] + internal static unsafe partial int GetCommonLStat(string filePath, out CommonStatStruct cs); - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] - internal static extern unsafe int GetCommonStat(string filePath, [Out] out CommonStatStruct cs); + [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8, SetLastError = true)] + internal static unsafe partial int GetCommonStat(string filePath, out CommonStatStruct cs); - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] - internal static extern string GetPwUid(int id); + [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)] + internal static partial string GetPwUid(int id); - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] - internal static extern string GetGrGid(int id); + [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)] + internal static partial string GetGrGid(int id); } } } diff --git a/src/System.Management.Automation/CoreCLR/CorePsStub.cs b/src/System.Management.Automation/CoreCLR/CorePsStub.cs index d99bcbb398b..e439cc30ff2 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsStub.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsStub.cs @@ -431,14 +431,31 @@ namespace System.Management.Automation.Security /// /// Application white listing security policies only affect Windows OSs. /// - internal sealed class SystemPolicy + public sealed class SystemPolicy { private SystemPolicy() { } + /// + /// Writes to PowerShell WDAC Audit mode ETW log. + /// + /// Current execution context. + /// Audit message title. + /// Audit message message. + /// Fully Qualified ID. + /// Stops code execution and goes into debugger mode. + internal static void LogWDACAuditMessage( + ExecutionContext context, + string title, + string message, + string fqid, + bool dropIntoDebugger = false) + { + } + /// /// Gets the system lockdown policy. /// - /// Always return SystemEnforcementMode.None in CSS (trusted) + /// Always return SystemEnforcementMode.None on non-Windows platforms. public static SystemEnforcementMode GetSystemLockdownPolicy() { return SystemEnforcementMode.None; @@ -447,7 +464,7 @@ public static SystemEnforcementMode GetSystemLockdownPolicy() /// /// Gets lockdown policy as applied to a file. /// - /// Always return SystemEnforcementMode.None in CSS (trusted) + /// Always return SystemEnforcementMode.None on non-Windows platforms. public static SystemEnforcementMode GetLockdownPolicy(string path, System.Runtime.InteropServices.SafeHandle handle) { return SystemEnforcementMode.None; @@ -457,12 +474,26 @@ internal static bool IsClassInApprovedList(Guid clsid) { throw new NotImplementedException("SystemPolicy.IsClassInApprovedList not implemented"); } + + /// + /// Gets the system wide script file policy enforcement for an open file. + /// Based on system WDAC (Windows Defender Application Control) or AppLocker policies. + /// + /// Script file path for policy check. + /// FileStream object to script file path. + /// Policy check result for script file. + public static SystemScriptFileEnforcement GetFilePolicyEnforcement( + string filePath, + System.IO.FileStream fileStream) + { + return SystemScriptFileEnforcement.None; + } } /// /// How the policy is being enforced. /// - internal enum SystemEnforcementMode + public enum SystemEnforcementMode { /// Not enforced at all None = 0, @@ -473,6 +504,37 @@ internal enum SystemEnforcementMode /// Enabled, enforce restrictions Enforce = 2 } + + /// + /// System wide policy enforcement for a specific script file. + /// + public enum SystemScriptFileEnforcement + { + /// + /// No policy enforcement. + /// + None = 0, + + /// + /// Script file is blocked from running. + /// + Block = 1, + + /// + /// Script file is allowed to run without restrictions (FullLanguage mode). + /// + Allow = 2, + + /// + /// Script file is allowed to run in ConstrainedLanguage mode only. + /// + AllowConstrained = 3, + + /// + /// Script file is allowed to run in FullLanguage mode but will emit ConstrainedLanguage restriction audit logs. + /// + AllowConstrainedAudit = 4 + } } // Porting note: Tracing is absolutely not available on Linux diff --git a/src/System.Management.Automation/DscSupport/CimDSCParser.cs b/src/System.Management.Automation/DscSupport/CimDSCParser.cs index 2901148813b..6160577beb2 100644 --- a/src/System.Management.Automation/DscSupport/CimDSCParser.cs +++ b/src/System.Management.Automation/DscSupport/CimDSCParser.cs @@ -22,6 +22,8 @@ using Microsoft.Management.Infrastructure.Serialization; using Microsoft.PowerShell.Commands; +using static Microsoft.PowerShell.SecureStringHelper; + namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal { /// @@ -259,12 +261,7 @@ private static object ConvertCimInstancePsCredential(string providerName, CimIns throw invalidOperationException; } - // Extract the password into a SecureString. - var password = new SecureString(); - foreach (char t in plainPassWord) - { - password.AppendChar(t); - } + SecureString password = SecureStringHelper.FromPlainTextString(plainPassWord); password.MakeReadOnly(); return new PSCredential(userName, password); @@ -320,8 +317,8 @@ public override object Transform(EngineIntrinsics engineIntrinsics, object input /// internal class CimDSCParser { - private CimMofDeserializer _deserializer; - private CimMofDeserializer.OnClassNeeded _onClassNeeded; + private readonly CimMofDeserializer _deserializer; + private readonly CimMofDeserializer.OnClassNeeded _onClassNeeded; /// /// @@ -532,7 +529,7 @@ public static class DscClassCache private const string reservedProperties = "^(Require|Trigger|Notify|Before|After|Subscribe)$"; - private static PSTraceSource s_tracer = PSTraceSource.GetTracer("DSC", "DSC Class Cache"); + private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("DSC", "DSC Class Cache"); // Constants for items in the module qualified name (Module\Version\ClassName) private const int IndexModuleName = 0; @@ -564,10 +561,7 @@ private static Dictionary ClassCache { get { - if (t_classCache == null) - { - t_classCache = new Dictionary(StringComparer.OrdinalIgnoreCase); - } + t_classCache ??= new Dictionary(StringComparer.OrdinalIgnoreCase); return t_classCache; } @@ -583,10 +577,7 @@ private static Dictionary> ByClassModuleCache { get { - if (t_byClassModuleCache == null) - { - t_byClassModuleCache = new Dictionary>(StringComparer.OrdinalIgnoreCase); - } + t_byClassModuleCache ??= new Dictionary>(StringComparer.OrdinalIgnoreCase); return t_byClassModuleCache; } @@ -602,10 +593,7 @@ private static Dictionary> ByClassModuleCache { get { - if (t_byFileClassCache == null) - { - t_byFileClassCache = new Dictionary>(StringComparer.OrdinalIgnoreCase); - } + t_byFileClassCache ??= new Dictionary>(StringComparer.OrdinalIgnoreCase); return t_byFileClassCache; } @@ -621,10 +609,7 @@ private static HashSet ScriptKeywordFileCache { get { - if (t_scriptKeywordFileCache == null) - { - t_scriptKeywordFileCache = new HashSet(StringComparer.OrdinalIgnoreCase); - } + t_scriptKeywordFileCache ??= new HashSet(StringComparer.OrdinalIgnoreCase); return t_scriptKeywordFileCache; } @@ -724,7 +709,7 @@ public static void Initialize(Collection errors, List moduleP continue; } - foreach (var schemaFile in Directory.EnumerateDirectories(resources).SelectMany(d => Directory.EnumerateFiles(d, "*.schema.mof"))) + foreach (var schemaFile in Directory.EnumerateDirectories(resources).SelectMany(static d => Directory.EnumerateFiles(d, "*.schema.mof"))) { ImportClasses(schemaFile, s_defaultModuleInfoForResource, errors); } @@ -741,12 +726,12 @@ public static void Initialize(Collection errors, List moduleP if (!Directory.Exists(systemResourceRoot)) { - configSystemPath = Platform.GetFolderPath(Environment.SpecialFolder.System); + configSystemPath = Environment.GetFolderPath(Environment.SpecialFolder.System); systemResourceRoot = Path.Combine(configSystemPath, "Configuration"); inboxModulePath = InboxDscResourceModulePath; } - var programFilesDirectory = Platform.GetFolderPath(Environment.SpecialFolder.ProgramFiles); + var programFilesDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); Debug.Assert(programFilesDirectory != null, "Program Files environment variable does not exist!"); var customResourceRoot = Path.Combine(programFilesDirectory, "WindowsPowerShell\\Configuration"); Debug.Assert(Directory.Exists(customResourceRoot), "%ProgramFiles%\\WindowsPowerShell\\Configuration Directory does not exist"); @@ -776,7 +761,7 @@ public static void Initialize(Collection errors, List moduleP continue; } - foreach (var schemaFile in Directory.EnumerateDirectories(resources).SelectMany(d => Directory.EnumerateFiles(d, "*.schema.mof"))) + foreach (var schemaFile in Directory.EnumerateDirectories(resources).SelectMany(static d => Directory.EnumerateFiles(d, "*.schema.mof"))) { ImportClasses(schemaFile, s_defaultModuleInfoForResource, errors); } @@ -822,7 +807,10 @@ private static void LoadDSCResourceIntoCache(Collection errors, List< { foreach (string moduleDir in modulePathList) { - if (!Directory.Exists(moduleDir)) continue; + if (!Directory.Exists(moduleDir)) + { + continue; + } var dscResourcesPath = Path.Combine(moduleDir, "DscResources"); if (Directory.Exists(dscResourcesPath)) @@ -933,7 +921,7 @@ private static CimClass MyClassCallback(string serverName, string namespaceName, { foreach (KeyValuePair cimClass in ClassCache) { - string cachedClassName = cimClass.Key.Split(Utils.Separators.Backslash)[IndexClassName]; + string cachedClassName = cimClass.Key.Split('\\')[IndexClassName]; if (string.Equals(cachedClassName, className, StringComparison.OrdinalIgnoreCase)) { return cimClass.Value.CimClassInstance; @@ -985,10 +973,7 @@ public static List ImportClasses(string path, Tuple m { // Ignore modules with invalid schemas. s_tracer.WriteLine("DSC ClassCache: Error importing file '{0}', with error '{1}'. Skipping file.", path, e); - if (errors != null) - { - errors.Add(e); - } + errors?.Add(e); } if (classes != null) @@ -1010,15 +995,12 @@ public static List ImportClasses(string path, Tuple m // allow sharing of nested objects. if (!IsSameNestedObject(cimClass, c)) { - var files = string.Join(",", GetFileDefiningClass(className)); + var files = string.Join(',', GetFileDefiningClass(className)); PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException( ParserStrings.DuplicateCimClassDefinition, className, path, files); e.SetErrorId("DuplicateCimClassDefinition"); - if (errors != null) - { - errors.Add(e); - } + errors?.Add(e); } } @@ -1107,7 +1089,7 @@ public static void ClearCache() /// private static string GetModuleQualifiedResourceName(string moduleName, string moduleVersion, string className, string resourceName) { - return string.Format(CultureInfo.InvariantCulture, "{0}\\{1}\\{2}\\{3}", moduleName, moduleVersion, className, resourceName); + return string.Create(CultureInfo.InvariantCulture, $"{moduleName}\\{moduleVersion}\\{className}\\{resourceName}"); } /// @@ -1120,7 +1102,7 @@ private static string GetModuleQualifiedResourceName(string moduleName, string m private static List> FindResourceInCache(string moduleName, string className, string resourceName) { return (from cacheEntry in ClassCache - let splittedName = cacheEntry.Key.Split(Utils.Separators.Backslash) + let splittedName = cacheEntry.Key.Split('\\') let cachedClassName = splittedName[IndexClassName] let cachedModuleName = splittedName[IndexModuleName] let cachedResourceName = splittedName[IndexFriendlyName] @@ -1146,7 +1128,7 @@ private static List GetCachedClasses() public static List GetCachedClassesForModule(PSModuleInfo module) { List cachedClasses = new(); - var moduleQualifiedName = string.Format(CultureInfo.InvariantCulture, "{0}\\{1}", module.Name, module.Version.ToString()); + var moduleQualifiedName = string.Create(CultureInfo.InvariantCulture, $"{module.Name}\\{module.Version}"); foreach (var dscClassCacheEntry in ClassCache) { if (dscClassCacheEntry.Key.StartsWith(moduleQualifiedName, StringComparison.OrdinalIgnoreCase)) @@ -1316,7 +1298,7 @@ public static Collection GetCachedKeywords() foreach (KeyValuePair cachedClass in ClassCache) { - string[] splittedName = cachedClass.Key.Split(Utils.Separators.Backslash); + string[] splittedName = cachedClass.Key.Split('\\'); string moduleName = splittedName[IndexModuleName]; string moduleVersion = splittedName[IndexModuleVersion]; @@ -1622,8 +1604,8 @@ public static void LoadDefaultCimKeywords(Collection errors) /// /// Load the default system CIM classes and create the corresponding keywords. - /// A dictionary to add the defined functions to, may be null. /// + /// A dictionary to add the defined functions to, may be null. public static void LoadDefaultCimKeywords(Dictionary functionsToDefine) { LoadDefaultCimKeywords(functionsToDefine, null, null, false); @@ -1896,10 +1878,7 @@ private static ParseError[] ImportResourceCheckSemantics(DynamicKeywordStatement { if (keywordAst.Keyword.Keyword.Equals("Node")) { - if (errorList == null) - { - errorList = new List(); - } + errorList ??= new List(); errorList.Add(new ParseError(kwAst.Extent, "ImportDscResourceInsideNode", @@ -1956,8 +1935,7 @@ private static ParseError[] CheckMandatoryPropertiesPresent(DynamicKeywordStatem object evalResultObject; if (IsConstantValueVisitor.IsConstant(pair.Item1, out evalResultObject, forAttribute: false, forRequires: false)) { - var presentName = evalResultObject as string; - if (presentName != null) + if (evalResultObject is string presentName) { if (mandatoryPropertiesNames.Remove(presentName) && mandatoryPropertiesNames.Count == 0) { @@ -2043,7 +2021,7 @@ public static void LoadResourcesFromModule(IScriptExtent scriptExtent, { string moduleString = moduleToImport.Version == null ? moduleToImport.Name - : string.Format(CultureInfo.CurrentCulture, "<{0}, {1}>", moduleToImport.Name, moduleToImport.Version); + : string.Create(CultureInfo.CurrentCulture, $"<{moduleToImport.Name}, {moduleToImport.Version}>"); errorList.Add(new ParseError(scriptExtent, "ModuleNotFoundDuringParse", string.Format(CultureInfo.CurrentCulture, ParserStrings.ModuleNotFoundDuringParse, moduleString))); @@ -2151,8 +2129,7 @@ public static void LoadResourcesFromModule(IScriptExtent scriptExtent, { try { - string unused; - foundResources = ImportCimKeywordsFromModule(moduleInfo, resourceToImport, out unused); + foundResources = ImportCimKeywordsFromModule(moduleInfo, resourceToImport, out _); } catch (Exception) { @@ -2320,8 +2297,7 @@ internal static string GenerateMofForAst(TypeDefinitionAst typeAst) internal static string MapTypeNameToMofType(ITypeName typeName, string memberName, string className, out bool isArrayType, out string embeddedInstanceType, List embeddedInstanceTypes, ref string[] enumNames) { TypeName propTypeName; - var arrayTypeName = typeName as ArrayTypeName; - if (arrayTypeName != null) + if (typeName is ArrayTypeName arrayTypeName) { isArrayType = true; propTypeName = arrayTypeName.ElementType as TypeName; @@ -2344,7 +2320,7 @@ internal static string MapTypeNameToMofType(ITypeName typeName, string memberNam if (propTypeName._typeDefinitionAst.IsEnum) { - enumNames = propTypeName._typeDefinitionAst.Members.Select(m => m.Name).ToArray(); + enumNames = propTypeName._typeDefinitionAst.Members.Select(static m => m.Name).ToArray(); isArrayType = false; embeddedInstanceType = null; return "string"; @@ -2364,9 +2340,9 @@ internal static string MapTypeNameToMofType(ITypeName typeName, string memberNam private static void GenerateMofForAst(TypeDefinitionAst typeAst, StringBuilder sb, List embeddedInstanceTypes) { var className = typeAst.Name; - sb.AppendFormat(CultureInfo.InvariantCulture, "[ClassVersion(\"1.0.0\"), FriendlyName(\"{0}\")]\nclass {0}", className); + sb.Append(CultureInfo.InvariantCulture, $"[ClassVersion(\"1.0.0\"), FriendlyName(\"{className}\")]\nclass {className}"); - if (typeAst.Attributes.Any(a => a.TypeName.GetReflectionAttributeType() == typeof(DscResourceAttribute))) + if (typeAst.Attributes.Any(static a => a.TypeName.GetReflectionAttributeType() == typeof(DscResourceAttribute))) { sb.Append(" : OMI_BaseResource"); } @@ -2384,15 +2360,13 @@ private static void GenerateMofForAst(TypeDefinitionAst typeAst, StringBuilder s while (bases.Count > 0) { var b = bases.Dequeue(); - var tc = b as TypeConstraintAst; - if (tc != null) + if (b is TypeConstraintAst tc) { b = tc.TypeName.GetReflectionType(); if (b == null) { - var td = tc.TypeName as TypeName; - if (td != null && td._typeDefinitionAst != null) + if (tc.TypeName is TypeName td && td._typeDefinitionAst != null) { ProcessMembers(sb, embeddedInstanceTypes, td._typeDefinitionAst, className); foreach (var b1 in td._typeDefinitionAst.BaseTypes) @@ -2434,8 +2408,7 @@ private static bool GetResourceMethodsLineNumber(TypeDefinitionAst typeDefinitio methodsLinePosition = new Dictionary(); foreach (var member in typeDefinitionAst.Members) { - var functionMemberAst = member as FunctionMemberAst; - if (functionMemberAst != null) + if (member is FunctionMemberAst functionMemberAst) { if (functionMemberAst.Name.Equals(getMethodName, StringComparison.OrdinalIgnoreCase)) { @@ -2481,7 +2454,7 @@ public static bool GetResourceMethodsLinePosition(PSModuleInfo moduleInfo, strin if (moduleInfo.NestedModules != null) { - foreach (var nestedModule in moduleInfo.NestedModules.Where(m => !string.IsNullOrEmpty(m.Path))) + foreach (var nestedModule in moduleInfo.NestedModules.Where(static m => !string.IsNullOrEmpty(m.Path))) { moduleFiles.Add(nestedModule.Path); } @@ -2515,9 +2488,7 @@ private static void ProcessMembers(StringBuilder sb, List embeddedInstan { foreach (var member in typeDefinitionAst.Members) { - var property = member as PropertyMemberAst; - - if (property == null || property.IsStatic || + if (member is not PropertyMemberAst property || property.IsStatic || property.Attributes.All(a => a.TypeName.GetReflectionAttributeType() != typeof(DscPropertyAttribute))) { continue; @@ -2557,14 +2528,12 @@ private static void ProcessMembers(StringBuilder sb, List embeddedInstan out embeddedInstanceType, embeddedInstanceTypes, ref enumNames); } + string mofAttr = MapAttributesToMof(enumNames, attributes, embeddedInstanceType); string arrayAffix = isArrayType ? "[]" : string.Empty; - sb.AppendFormat(CultureInfo.InvariantCulture, - " {0}{1} {2}{3};\n", - MapAttributesToMof(enumNames, attributes, embeddedInstanceType), - mofType, - member.Name, - arrayAffix); + sb.Append( + CultureInfo.InvariantCulture, + $" {mofAttr}{mofType} {member.Name}{arrayAffix};\n"); } } @@ -2622,13 +2591,15 @@ private static bool GetResourceDefinitionsFromModule(string fileName, out IEnume resourceDefinitions = ast.FindAll(n => { - var typeAst = n as TypeDefinitionAst; - if (typeAst != null) + if (n is TypeDefinitionAst typeAst) { for (int i = 0; i < typeAst.Attributes.Count; i++) { var a = typeAst.Attributes[i]; - if (a.TypeName.GetReflectionAttributeType() == typeof(DscResourceAttribute)) return true; + if (a.TypeName.GetReflectionAttributeType() == typeof(DscResourceAttribute)) + { + return true; + } } } @@ -2682,7 +2653,10 @@ private static bool ImportKeywordsFromScriptFile(string fileName, PSModuleInfo m } } - if (skip) continue; + if (skip) + { + continue; + } // Parse the Resource Attribute to see if RunAs behavior is specified for the resource. DSCResourceRunAsCredential runAsBehavior = DSCResourceRunAsCredential.Default; @@ -2692,13 +2666,9 @@ private static bool ImportKeywordsFromScriptFile(string fileName, PSModuleInfo m { foreach (var na in attr.NamedArguments) { - if (na.ArgumentName.Equals("RunAsCredential", StringComparison.OrdinalIgnoreCase)) + if (na.ArgumentName.Equals("RunAsCredential", StringComparison.OrdinalIgnoreCase) && attr.GetAttribute() is DscResourceAttribute dscResourceAttribute) { - var dscResourceAttribute = attr.GetAttribute() as DscResourceAttribute; - if (dscResourceAttribute != null) - { - runAsBehavior = dscResourceAttribute.RunAsCredential; - } + runAsBehavior = dscResourceAttribute.RunAsCredential; } } } @@ -2715,16 +2685,16 @@ private static bool ImportKeywordsFromScriptFile(string fileName, PSModuleInfo m private static readonly Dictionary s_mapPrimitiveDotNetTypeToMof = new() { { typeof(sbyte), "sint8" }, - { typeof(byte) , "uint8"}, - { typeof(short) , "sint16"}, - { typeof(ushort) , "uint16"}, - { typeof(int) , "sint32"}, - { typeof(uint) , "uint32"}, - { typeof(long) , "sint64"}, + { typeof(byte), "uint8"}, + { typeof(short), "sint16"}, + { typeof(ushort), "uint16"}, + { typeof(int), "sint32"}, + { typeof(uint), "uint32"}, + { typeof(long), "sint64"}, { typeof(ulong), "uint64" }, - { typeof(float) , "real32"}, - { typeof(double) , "real64"}, - { typeof(bool) , "boolean"}, + { typeof(float), "real32"}, + { typeof(double), "real64"}, + { typeof(bool), "boolean"}, { typeof(string), "string" }, { typeof(DateTime), "datetime" }, { typeof(PSCredential), "string" }, @@ -2932,8 +2902,7 @@ private static string MapAttributesToMof(string[] enumNames, IEnumerable bool needComma = false; foreach (var attr in customAttributes) { - var dscProperty = attr as DscPropertyAttribute; - if (dscProperty != null) + if (attr is DscPropertyAttribute dscProperty) { if (dscProperty.Key) { @@ -2956,8 +2925,7 @@ private static string MapAttributesToMof(string[] enumNames, IEnumerable continue; } - var validateSet = attr as ValidateSetAttribute; - if (validateSet != null) + if (attr is ValidateSetAttribute validateSet) { bool valueMapComma = false; StringBuilder sbValues = new(", Values{"); @@ -3072,7 +3040,7 @@ private static void GenerateMofForType(Type type, StringBuilder sb, List { var className = type.Name; // Friendly name is required by module validator to verify resource instance against the exclusive resource name list. - sb.AppendFormat(CultureInfo.InvariantCulture, "[ClassVersion(\"1.0.0\"), FriendlyName(\"{0}\")]\nclass {0}", className); + sb.Append(CultureInfo.InvariantCulture, $"[ClassVersion(\"1.0.0\"), FriendlyName(\"{className}\")]\nclass {className}"); if (type.GetCustomAttributes().Any()) { @@ -3087,9 +3055,9 @@ private static void GenerateMofForType(Type type, StringBuilder sb, List private static void ProcessMembers(Type type, StringBuilder sb, List embeddedInstanceTypes, string className) { - foreach (var member in type.GetMembers(BindingFlags.Instance | BindingFlags.Public).Where(m => m is PropertyInfo || m is FieldInfo)) + foreach (var member in type.GetMembers(BindingFlags.Instance | BindingFlags.Public).Where(static m => m is PropertyInfo || m is FieldInfo)) { - if (member.CustomAttributes.All(cad => cad.AttributeType != typeof(DscPropertyAttribute))) + if (member.CustomAttributes.All(static cad => cad.AttributeType != typeof(DscPropertyAttribute))) { continue; } @@ -3112,21 +3080,21 @@ private static void ProcessMembers(Type type, StringBuilder sb, List emb } // TODO - validate type and name - bool isArrayType; - string embeddedInstanceType; - string mofType = MapTypeToMofType(memberType, member.Name, className, out isArrayType, out embeddedInstanceType, + string mofType = MapTypeToMofType( + memberType, + member.Name, + className, + out bool isArrayType, + out string embeddedInstanceType, embeddedInstanceTypes); + + var enumNames = memberType.IsEnum ? Enum.GetNames(memberType) : null; + string mofAttr = MapAttributesToMof(enumNames, member.GetCustomAttributes(true), embeddedInstanceType); string arrayAffix = isArrayType ? "[]" : string.Empty; - var enumNames = memberType.IsEnum - ? Enum.GetNames(memberType) - : null; - sb.AppendFormat(CultureInfo.InvariantCulture, - " {0}{1} {2}{3};\n", - MapAttributesToMof(enumNames, member.GetCustomAttributes(true), embeddedInstanceType), - mofType, - member.Name, - arrayAffix); + sb.Append( + CultureInfo.InvariantCulture, + $" {mofAttr}{mofType} {member.Name}{arrayAffix};\n"); } } @@ -3141,7 +3109,7 @@ private static bool ImportKeywordsFromAssembly(PSModuleInfo module, var parser = new Microsoft.PowerShell.DesiredStateConfiguration.CimDSCParser(MyClassCallback); IEnumerable resourceDefinitions = - assembly.GetTypes().Where(t => t.GetCustomAttributes().Any()); + assembly.GetTypes().Where(static t => t.GetCustomAttributes().Any()); foreach (var r in resourceDefinitions) { @@ -3157,7 +3125,10 @@ private static bool ImportKeywordsFromAssembly(PSModuleInfo module, } } - if (skip) continue; + if (skip) + { + continue; + } var mof = GenerateMofForType(r); @@ -3275,14 +3246,15 @@ public static bool ImportCimKeywordsFromModule(PSModuleInfo module, string resou // try { - var dscResourceDirectories = Directory.GetDirectories(dscResourcesPath); - foreach (var directory in dscResourceDirectories) + foreach (var directory in Directory.EnumerateDirectories(dscResourcesPath)) { - var schemaFiles = Directory.GetFiles(directory, "*.schema.mof", SearchOption.TopDirectoryOnly); - if (schemaFiles.Length > 0) + IEnumerable schemaFiles = Directory.EnumerateFiles(directory, "*.schema.mof", SearchOption.TopDirectoryOnly); + 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) { - Debug.Assert(schemaFiles.Length == 1, "A valid DSCResource module can have only one schema mof file"); - var tempSchemaFilepath = schemaFiles[0]; var classes = GetCachedClassByFileName(tempSchemaFilepath) ?? ImportClasses(tempSchemaFilepath, new Tuple(module.Name, module.Version), errors); if (classes != null) { @@ -3635,7 +3607,7 @@ public static string GetDSCResourceUsageString(DynamicKeyword keyword) bool listKeyProperties = true; while (true) { - foreach (var prop in keyword.Properties.OrderBy(ob => ob.Key)) + foreach (var prop in keyword.Properties.OrderBy(static ob => ob.Key)) { if (string.Equals(prop.Key, "ResourceId", StringComparison.OrdinalIgnoreCase)) { @@ -3702,7 +3674,7 @@ private static StringBuilder FormatCimPropertyType(DynamicKeywordProperty prop, // Do the property values map if (prop.ValueMap != null && prop.ValueMap.Count > 0) { - formattedTypeString.Append(" { " + string.Join(" | ", prop.ValueMap.Keys.OrderBy(x => x)) + " }"); + formattedTypeString.Append(" { " + string.Join(" | ", prop.ValueMap.Keys.Order()) + " }"); } // We prepend optional property with "[" so close out it here. This way it is shown with [ ] to indication optional diff --git a/src/System.Management.Automation/DscSupport/JsonCimDSCParser.cs b/src/System.Management.Automation/DscSupport/JsonCimDSCParser.cs deleted file mode 100755 index c2221cb3f33..00000000000 --- a/src/System.Management.Automation/DscSupport/JsonCimDSCParser.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.IO; -using System.Management.Automation; -using System.Security; - -namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform -{ - /// - /// Class that does high level Cim schema parsing. - /// - internal class CimDSCParser - { - private readonly JsonDeserializer _jsonDeserializer; - - internal CimDSCParser() - { - _jsonDeserializer = JsonDeserializer.Create(); - } - - internal IEnumerable ParseSchemaJson(string filePath, bool useNewRunspace = false) - { - try - { - string json = File.ReadAllText(filePath); - string fileNameDefiningClass = Path.GetFileNameWithoutExtension(filePath); - int dotIndex = fileNameDefiningClass.IndexOf(".schema", StringComparison.InvariantCultureIgnoreCase); - if (dotIndex != -1) - { - fileNameDefiningClass = fileNameDefiningClass.Substring(0, dotIndex); - } - - IEnumerable result = _jsonDeserializer.DeserializeClasses(json, useNewRunspace); - foreach (dynamic classObject in result) - { - string superClassName = classObject.SuperClassName; - string className = classObject.ClassName; - if (string.Equals(superClassName, "OMI_BaseResource", StringComparison.OrdinalIgnoreCase)) - { - // Get the name of the file without schema.mof/json extension - if (!className.Equals(fileNameDefiningClass, StringComparison.OrdinalIgnoreCase)) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException( - ParserStrings.ClassNameNotSameAsDefiningFile, className, fileNameDefiningClass); - throw e; - } - } - } - - return result; - } - catch (Exception exception) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException( - exception, ParserStrings.CimDeserializationError, filePath); - - e.SetErrorId("CimDeserializationError"); - throw e; - } - } - } -} diff --git a/src/System.Management.Automation/DscSupport/JsonDeserializer.cs b/src/System.Management.Automation/DscSupport/JsonDeserializer.cs deleted file mode 100755 index 319560f0a09..00000000000 --- a/src/System.Management.Automation/DscSupport/JsonDeserializer.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Management.Automation; -using System.Management.Automation.Runspaces; - -namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform -{ - internal class JsonDeserializer - { - #region Constructors - - /// - /// Instantiates a default deserializer. - /// - /// Default deserializer. - public static JsonDeserializer Create() - { - return new JsonDeserializer(); - } - - #endregion Constructors - - #region Methods - - /// - /// Returns schema of Cim classes from specified json file. - /// - /// Json text to deserialize. - /// If a new runspace should be used. - /// Deserialized PSObjects. - public IEnumerable DeserializeClasses(string json, bool useNewRunspace = false) - { - if (string.IsNullOrEmpty(json)) - { - throw new ArgumentNullException(nameof(json)); - } - - System.Management.Automation.PowerShell powerShell = null; - - if (useNewRunspace) - { - // currently using RunspaceMode.NewRunspace will reset PSModulePath env var for the entire process - // this is something we want to avoid in DSC GuestConfigAgent scenario, so we use following workaround - var s_iss = InitialSessionState.CreateDefault(); - s_iss.EnvironmentVariables.Add( - new SessionStateVariableEntry( - "PSModulePath", - Environment.GetEnvironmentVariable("PSModulePath"), - description: null)); - powerShell = System.Management.Automation.PowerShell.Create(s_iss); - } - else - { - powerShell = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); - } - - using (powerShell) - { - return powerShell.AddCommand("Microsoft.PowerShell.Utility\\ConvertFrom-Json") - .AddParameter("InputObject", json) - .AddParameter("Depth", 100) // maximum supported by cmdlet - .Invoke(); - } - } - - #endregion Methods - } -} diff --git a/src/System.Management.Automation/DscSupport/JsonDscClassCache.cs b/src/System.Management.Automation/DscSupport/JsonDscClassCache.cs deleted file mode 100755 index 2fe4f7c2606..00000000000 --- a/src/System.Management.Automation/DscSupport/JsonDscClassCache.cs +++ /dev/null @@ -1,2498 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Management.Automation; -using System.Management.Automation.Language; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Security; -using System.Text; -using System.Text.RegularExpressions; - -using Microsoft.PowerShell.Commands; - -namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform -{ - /// - /// Class that defines Dsc cache entries. - /// - internal class DscClassCacheEntry - { - /// - /// Initializes a new instance of the class. - /// - public DscClassCacheEntry() - : this(DSCResourceRunAsCredential.Default, isImportedImplicitly: false, cimClassInstance: null, modulePath: string.Empty) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Run as credential value. - /// Resource is imported implicitly. - /// Class definition. - /// Path of module defining the class. - public DscClassCacheEntry(DSCResourceRunAsCredential dscResourceRunAsCredential, bool isImportedImplicitly, PSObject cimClassInstance, string modulePath) - { - DscResRunAsCred = dscResourceRunAsCredential; - IsImportedImplicitly = isImportedImplicitly; - CimClassInstance = cimClassInstance; - ModulePath = modulePath; - } - - /// - /// Gets or sets the RunAs Credentials that this DSC resource will use. - /// - public DSCResourceRunAsCredential DscResRunAsCred { get; set; } - - /// - /// Gets or sets a value indicating if we have implicitly imported this resource. - /// - public bool IsImportedImplicitly { get; set; } - - /// - /// Gets or sets CimClass instance for this resource. - /// - public PSObject CimClassInstance { get; set; } - - /// - /// Gets or sets path of the implementing module for this resource. - /// - public string ModulePath { get; set; } - } - - /// - /// DSC class cache for this runspace. - /// - [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", - Justification = "Needed Internal use only")] - public static class DscClassCache - { - private static readonly HashSet s_reservedDynamicKeywords = new HashSet(new[] { "Synchronization", "Certificate", "IIS", "SQL" }, StringComparer.OrdinalIgnoreCase); - - private static readonly HashSet s_reservedProperties = new HashSet(new[] { "Require", "Trigger", "Notify", "Before", "After", "Subscribe" }, StringComparer.OrdinalIgnoreCase); - - /// - /// Experimental feature name for DSC v3. - /// - public const string DscExperimentalFeatureName = "PS7DscSupport"; - - private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("DSC", "DSC Class Cache"); - - // Constants for items in the module qualified name (Module\Version\ClassName) - private const int ModuleNameIndex = 0; - private const int ModuleVersionIndex = 1; - private const int ClassNameIndex = 2; - private const int FriendlyNameIndex = 3; - - // Create a HashSet for fast lookup. According to MSDN, the time complexity of search for an element in a HashSet is O(1) - private static readonly HashSet s_hiddenResourceCache = - new HashSet(StringComparer.OrdinalIgnoreCase) { "MSFT_BaseConfigurationProviderRegistration", "MSFT_CimConfigurationProviderRegistration", "MSFT_PSConfigurationProviderRegistration" }; - - // A collection to prevent circular importing case when Import-DscResource does not have a module specified - [ThreadStatic] - private static readonly HashSet t_currentImportDscResourceInvocations = new(StringComparer.OrdinalIgnoreCase); - - /// - /// Gets DSC class cache for this runspace. - /// Cache stores the DSCRunAsBehavior, cim class and boolean to indicate if an Inbox resource has been implicitly imported. - /// - private static Dictionary ClassCache - { - get => t_classCache ??= new Dictionary(StringComparer.OrdinalIgnoreCase); - } - - [ThreadStatic] - private static Dictionary t_classCache; - - /// - /// Gets DSC class cache for GuestConfig; it is similar to ClassCache, but maintains values between operations. - /// - private static Dictionary GuestConfigClassCache - { - get => t_guestConfigClassCache ??= new Dictionary(StringComparer.OrdinalIgnoreCase); - } - - [ThreadStatic] - private static Dictionary t_guestConfigClassCache; - - /// - /// DSC classname to source module mapper. - /// - private static Dictionary> ByClassModuleCache - => t_byClassModuleCache ??= new Dictionary>(StringComparer.OrdinalIgnoreCase); - - [ThreadStatic] - private static Dictionary> t_byClassModuleCache; - - /// - /// Default ModuleName and ModuleVersion to use. - /// - private static readonly Tuple s_defaultModuleInfoForResource = new Tuple("PSDesiredStateConfiguration", new Version(3, 0)); - - /// - /// When this property is set to true, DSC Cache will cache multiple versions of a resource. - /// That means it will cache duplicate resource classes (class names for a resource in two different module versions are same). - /// NOTE: This property should be set to false for DSC compiler related methods/functionality, such as Import-DscResource, - /// because the Mof serializer does not support deserialization of classes with different versions. - /// - [ThreadStatic] - private static bool t_cacheResourcesFromMultipleModuleVersions; - - private static bool CacheResourcesFromMultipleModuleVersions - { - get - { - return t_cacheResourcesFromMultipleModuleVersions; - } - - set - { - t_cacheResourcesFromMultipleModuleVersions = value; - } - } - - [ThreadStatic] - private static bool t_newApiIsUsed = false; - - /// - /// Flag shows if PS7 DSC APIs were used. - /// - public static bool NewApiIsUsed - { - get - { - return t_newApiIsUsed; - } - - set - { - t_newApiIsUsed = value; - } - } - - /// - /// Initialize the class cache with the default classes in $ENV:SystemDirectory\Configuration. - /// - public static void Initialize() - { - Initialize(errors: null, modulePathList: null); - } - - /// - /// Initialize the class cache with default classes that come with PSDesiredStateConfiguration module. - /// - /// Collection of any errors encountered during initialization. - /// List of module path from where DSC PS modules will be loaded. - public static void Initialize(Collection errors, List modulePathList) - { - s_tracer.WriteLine("Initializing DSC class cache"); - - // Load the base schema files. - ClearCache(); - var dscConfigurationDirectory = Environment.GetEnvironmentVariable("DSC_HOME"); - if (string.IsNullOrEmpty(dscConfigurationDirectory)) - { - var moduleInfos = ModuleCmdletBase.GetModuleIfAvailable(new Microsoft.PowerShell.Commands.ModuleSpecification() - { - Name = "PSDesiredStateConfiguration", - - // Version in the next line is actually MinimumVersion - Version = new Version(3, 0, 0) - }); - - if (moduleInfos.Count > 0) - { - // to be consistent with Import-Module behavior, we use the first occurrence that we find in PSModulePath - var moduleDirectory = Path.GetDirectoryName(moduleInfos[0].Path); - dscConfigurationDirectory = Path.Join(moduleDirectory, "Configuration"); - } - else - { - // when all else has failed use location of system-wide PS module directory (i.e. /usr/local/share/powershell/Modules) as backup - dscConfigurationDirectory = Path.Join(ModuleIntrinsics.GetSharedModulePath(), "PSDesiredStateConfiguration", "Configuration"); - } - } - - if (!Directory.Exists(dscConfigurationDirectory)) - { - throw new DirectoryNotFoundException(string.Format(ParserStrings.PsDscMissingSchemaStore, dscConfigurationDirectory)); - } - - var resourceBaseFile = Path.Join(dscConfigurationDirectory, "BaseRegistration", "BaseResource.schema.json"); - ImportBaseClasses(resourceBaseFile, s_defaultModuleInfoForResource, errors, false); - var metaConfigFile = Path.Join(dscConfigurationDirectory, "BaseRegistration", "MSFT_DSCMetaConfiguration.json"); - ImportBaseClasses(metaConfigFile, s_defaultModuleInfoForResource, errors, false); - } - - /// - /// Import base classes from the given file. - /// - /// Path to schema file. - /// Module information. - /// Error collection that will be shown to the user. - /// Flag for implicitly imported resource. - /// Class objects from schema file. - public static IEnumerable ImportBaseClasses(string path, Tuple moduleInfo, Collection errors, bool importInBoxResourcesImplicitly) - { - if (string.IsNullOrEmpty(path)) - { - throw PSTraceSource.NewArgumentNullException(nameof(path)); - } - - s_tracer.WriteLine("DSC ClassCache: importing file: {0}", path); - - var parser = new CimDSCParser(); - - IEnumerable classes = null; - try - { - classes = parser.ParseSchemaJson(path); - } - catch (PSInvalidOperationException e) - { - // Ignore modules with invalid schemas. - s_tracer.WriteLine("DSC ClassCache: Error importing file '{0}', with error '{1}'. Skipping file.", path, e); - if (errors != null) - { - errors.Add(e); - } - } - - if (classes != null) - { - foreach (dynamic c in classes) - { - var className = c.ClassName; - - if (string.IsNullOrEmpty(className)) - { - // ClassName is empty - skipping class import - continue; - } - - string alias = GetFriendlyName(c); - var friendlyName = string.IsNullOrEmpty(alias) ? className : alias; - string moduleQualifiedResourceName = GetModuleQualifiedResourceName(moduleInfo.Item1, moduleInfo.Item2.ToString(), className, friendlyName); - DscClassCacheEntry cimClassInfo; - - if (ClassCache.TryGetValue(moduleQualifiedResourceName, out cimClassInfo)) - { - if (errors != null) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException( - ParserStrings.DuplicateCimClassDefinition, className, path, cimClassInfo.ModulePath); - - e.SetErrorId("DuplicateCimClassDefinition"); - errors.Add(e); - } - - continue; - } - - if (s_hiddenResourceCache.Contains(className)) - { - continue; - } - - var classCacheEntry = new DscClassCacheEntry(DSCResourceRunAsCredential.NotSupported, importInBoxResourcesImplicitly, c, path); - ClassCache[moduleQualifiedResourceName] = classCacheEntry; - GuestConfigClassCache[moduleQualifiedResourceName] = classCacheEntry; - ByClassModuleCache[className] = moduleInfo; - } - - var sb = new System.Text.StringBuilder(); - foreach (dynamic c in classes) - { - sb.Append(c.ClassName); - sb.Append(','); - } - - s_tracer.WriteLine("DSC ClassCache: loading file '{0}' added the following classes to the cache: {1}", path, sb.ToString()); - } - else - { - s_tracer.WriteLine("DSC ClassCache: loading file '{0}' added no classes to the cache."); - } - - return classes; - } - - /// - /// Get text from SecureString. - /// - /// Value of SecureString. - /// Decoded string. - public static string GetStringFromSecureString(SecureString value) - { - string passwordValueToAdd = string.Empty; - - if (value != null) - { - IntPtr ptr = Marshal.SecureStringToCoTaskMemUnicode(value); - passwordValueToAdd = Marshal.PtrToStringUni(ptr); - Marshal.ZeroFreeCoTaskMemUnicode(ptr); - } - - return passwordValueToAdd; - } - - /// - /// Clear out the existing collection of CIM classes and associated keywords. - /// - public static void ClearCache() - { - if (!ExperimentalFeature.IsEnabled(DscExperimentalFeatureName)) - { - throw new InvalidOperationException(ParserStrings.PS7DscSupportDisabled); - } - - s_tracer.WriteLine("DSC class: clearing the cache and associated keywords."); - ClassCache.Clear(); - ByClassModuleCache.Clear(); - CacheResourcesFromMultipleModuleVersions = false; - t_currentImportDscResourceInvocations.Clear(); - } - - private static string GetModuleQualifiedResourceName(string moduleName, string moduleVersion, string className, string resourceName) - { - return string.Format(CultureInfo.InvariantCulture, "{0}\\{1}\\{2}\\{3}", moduleName, moduleVersion, className, resourceName); - } - - private static List> FindResourceInCache(string moduleName, string className, string resourceName) - { - return (from cacheEntry in ClassCache - let splittedName = cacheEntry.Key.Split(Utils.Separators.Backslash) - let cachedClassName = splittedName[ClassNameIndex] - let cachedModuleName = splittedName[ModuleNameIndex] - let cachedResourceName = splittedName[FriendlyNameIndex] - where (string.Equals(cachedResourceName, resourceName, StringComparison.OrdinalIgnoreCase) - || (string.Equals(cachedClassName, className, StringComparison.OrdinalIgnoreCase) - && string.Equals(cachedModuleName, moduleName, StringComparison.OrdinalIgnoreCase))) - select cacheEntry).ToList(); - } - - /// - /// Returns class declaration from GuestConfigClassCache. - /// - /// Module name. - /// Module version. - /// Name of the class. - /// Friendly name of the resource. - /// Class declaration from cache. - public static PSObject GetGuestConfigCachedClass(string moduleName, string moduleVersion, string className, string resourceName) - { - if (!ExperimentalFeature.IsEnabled(DscExperimentalFeatureName)) - { - throw new InvalidOperationException(ParserStrings.PS7DscSupportDisabled); - } - - var moduleQualifiedResourceName = GetModuleQualifiedResourceName(moduleName, moduleVersion, className, string.IsNullOrEmpty(resourceName) ? className : resourceName); - DscClassCacheEntry classCacheEntry = null; - if (GuestConfigClassCache.TryGetValue(moduleQualifiedResourceName, out classCacheEntry)) - { - return classCacheEntry.CimClassInstance; - } - else - { - // if class was not found with current ResourceName then it may be a class with non-empty FriendlyName that caller does not know, so perform a broad search - string partialClassPath = string.Join('\\', moduleName, moduleVersion, className, string.Empty); - foreach (string key in GuestConfigClassCache.Keys) - { - if (key.StartsWith(partialClassPath)) - { - return GuestConfigClassCache[key].CimClassInstance; - } - } - - return null; - } - } - - /// - /// Clears GuestConfigClassCache. - /// - public static void ClearGuestConfigClassCache() - { - GuestConfigClassCache.Clear(); - } - - 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(dynamic cimClass) - { - return cimClass.FriendlyName; - } - - /// - /// Method to get the cached classes in the form of DynamicKeyword. - /// - /// Dynamic keyword collection. - public static Collection GetKeywordsFromCachedClasses() - { - if (!ExperimentalFeature.IsEnabled(DscExperimentalFeatureName)) - { - throw new InvalidOperationException(ParserStrings.PS7DscSupportDisabled); - } - - Collection keywords = new Collection(); - - foreach (KeyValuePair cachedClass in ClassCache) - { - string[] splittedName = cachedClass.Key.Split(Utils.Separators.Backslash); - string moduleName = splittedName[ModuleNameIndex]; - string moduleVersion = splittedName[ModuleVersionIndex]; - - var keyword = CreateKeywordFromCimClass(moduleName, Version.Parse(moduleVersion), cachedClass.Value.CimClassInstance, cachedClass.Value.DscResRunAsCred); - if (keyword is not null) - { - keywords.Add(keyword); - } - } - - return keywords; - } - - private static void CreateAndRegisterKeywordFromCimClass(string moduleName, Version moduleVersion, PSObject cimClass, Dictionary functionsToDefine, DSCResourceRunAsCredential runAsBehavior) - { - var keyword = CreateKeywordFromCimClass(moduleName, moduleVersion, cimClass, runAsBehavior); - if (keyword is null) - { - return; - } - - // keyword is already defined and we don't allow redefine it - if (!CacheResourcesFromMultipleModuleVersions && DynamicKeyword.ContainsKeyword(keyword.Keyword)) - { - var oldKeyword = DynamicKeyword.GetKeyword(keyword.Keyword); - if (oldKeyword.ImplementingModule is null || - !oldKeyword.ImplementingModule.Equals(moduleName, StringComparison.OrdinalIgnoreCase) || oldKeyword.ImplementingModuleVersion != moduleVersion) - { - var e = PSTraceSource.NewInvalidOperationException(ParserStrings.DuplicateKeywordDefinition, keyword.Keyword); - e.SetErrorId("DuplicateKeywordDefinition"); - throw e; - } - } - - // Add the dynamic keyword to the table - DynamicKeyword.AddKeyword(keyword); - - // And now define the driver functions in the current scope... - if (functionsToDefine != null) - { - functionsToDefine[moduleName + "\\" + keyword.Keyword] = CimKeywordImplementationFunction; - } - } - - private static DynamicKeyword CreateKeywordFromCimClass(string moduleName, Version moduleVersion, dynamic cimClass, DSCResourceRunAsCredential runAsBehavior) - { - var resourceName = cimClass.ClassName; - string alias = GetFriendlyName(cimClass); - var keywordString = string.IsNullOrEmpty(alias) ? resourceName : alias; - - // 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) - { - return null; - } - - var keyword = new DynamicKeyword() - { - BodyMode = DynamicKeywordBodyMode.Hashtable, - Keyword = keywordString, - ResourceName = resourceName, - ImplementingModule = moduleName, - ImplementingModuleVersion = moduleVersion, - SemanticCheck = CheckMandatoryPropertiesPresent - }; - - // If it's one of reserved dynamic keyword, mark it - if (s_reservedDynamicKeywords.Contains(keywordString)) - { - keyword.IsReservedKeyword = true; - } - - // see if it's a resource type i.e. it inherits from OMI_BaseResource - bool isResourceType = false; - - // previous version of this code was the only place that referenced CimSuperClass - // so to simplify things we just check superclass to be OMI_BaseResource - // with assumption that current code will not work for multi-level class inheritance (which is never used in practice according to DSC team) - // this simplification allows us to avoid linking objects together using CimSuperClass field during deserialization - if ((!string.IsNullOrEmpty(cimClass.SuperClassName)) && string.Equals("OMI_BaseResource", cimClass.SuperClassName, StringComparison.OrdinalIgnoreCase)) - { - isResourceType = true; - } - - // If it's a resource type, then a resource name is required. - keyword.NameMode = isResourceType ? DynamicKeywordNameMode.NameRequired : DynamicKeywordNameMode.NoName; - - // Add the settable properties to the keyword object - if (cimClass.ClassProperties != null) - { - foreach (var prop in cimClass.ClassProperties) - { - // If the property has the Read qualifier, skip it. - if (string.Equals(prop.Qualifiers?.Read?.ToString(), "True", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - // If it's one of our magic properties, skip it - if (IsMagicProperty(prop.Name)) - { - continue; - } - - if (runAsBehavior == DSCResourceRunAsCredential.NotSupported) - { - if (string.Equals(prop.Name, "PsDscRunAsCredential", StringComparison.OrdinalIgnoreCase)) - { - // skip adding PsDscRunAsCredential to the dynamic word for the dsc resource. - continue; - } - } - - // If it's one of our reserved properties, save it for error reporting - if (s_reservedProperties.Contains(prop.Name)) - { - keyword.HasReservedProperties = true; - continue; - } - - // Otherwise, add it to the Keyword List. - var keyProp = new System.Management.Automation.Language.DynamicKeywordProperty(); - keyProp.Name = prop.Name; - - // Copy the type name string. If it's an embedded instance, need to grab it from the ReferenceClassName - bool referenceClassNameIsNullOrEmpty = string.IsNullOrEmpty(prop.ReferenceClassName); - if (prop.CimType == "Instance" && !referenceClassNameIsNullOrEmpty) - { - keyProp.TypeConstraint = prop.ReferenceClassName; - } - else if (prop.CimType == "InstanceArray" && !referenceClassNameIsNullOrEmpty) - { - keyProp.TypeConstraint = prop.ReferenceClassName + "[]"; - } - else - { - keyProp.TypeConstraint = prop.CimType.ToString(); - } - - // Check to see if there is a Values attribute and save the list of allowed values if so. - var values = prop.Qualifiers?.Values; - if (values is not null) - { - foreach (var val in values) - { - keyProp.Values.Add(val.ToString()); - } - } - - // Check to see if there is a ValueMap attribute and save the list of allowed values if so. - var nativeValueMap = prop.Qualifiers?.ValueMap; - List valueMap = null; - if (nativeValueMap is not null) - { - valueMap = new List(); - foreach (var val in nativeValueMap) - { - valueMap.Add(val.ToString()); - } - } - - // Check to see if this property has the Required qualifier associated with it. - if (string.Equals(prop.Qualifiers?.Required?.ToString(), "True", StringComparison.OrdinalIgnoreCase)) - { - keyProp.Mandatory = true; - } - - // Check to see if this property has the Key qualifier associated with it. - if (string.Equals(prop.Qualifiers?.Key?.ToString(), "True", StringComparison.OrdinalIgnoreCase)) - { - keyProp.Mandatory = true; - keyProp.IsKey = true; - } - - // set the property to mandatory is specified for the resource. - if (runAsBehavior == DSCResourceRunAsCredential.Mandatory) - { - if (string.Equals(prop.Name, "PsDscRunAsCredential", StringComparison.OrdinalIgnoreCase)) - { - keyProp.Mandatory = true; - } - } - - if (valueMap is not null && keyProp.Values.Count > 0) - { - if (valueMap.Count != keyProp.Values.Count) - { - s_tracer.WriteLine( - "DSC CreateDynamicKeywordFromClass: the count of values for qualifier 'Values' and 'ValueMap' doesn't match. count of 'Values': {0}, count of 'ValueMap': {1}. Skip the keyword '{2}'.", - keyProp.Values.Count, - valueMap.Count, - keyword.Keyword); - return null; - } - - for (int index = 0; index < valueMap.Count; index++) - { - string key = keyProp.Values[index]; - string value = valueMap[index]; - - if (keyProp.ValueMap.ContainsKey(key)) - { - s_tracer.WriteLine( - "DSC CreateDynamicKeywordFromClass: same string value '{0}' appears more than once in qualifier 'Values'. Skip the keyword '{1}'.", - key, - keyword.Keyword); - return null; - } - - keyProp.ValueMap.Add(key, value); - } - } - - keyword.Properties.Add(prop.Name, keyProp); - } - } - - // update specific keyword with range constraints - UpdateKnownRestriction(keyword); - - return keyword; - } - - private static void UpdateKnownRestriction(DynamicKeyword keyword) - { - const int RefreshFrequencyMin = 30; - const int RefreshFrequencyMax = 44640; - - const int ConfigurationModeFrequencyMin = 15; - const int ConfigurationModeFrequencyMax = 44640; - - if ( - string.Equals( - keyword.ResourceName, - "MSFT_DSCMetaConfigurationV2", - StringComparison.OrdinalIgnoreCase) - || - string.Equals( - keyword.ResourceName, - "MSFT_DSCMetaConfiguration", - StringComparison.OrdinalIgnoreCase)) - { - if (keyword.Properties["RefreshFrequencyMins"] is not null) - { - keyword.Properties["RefreshFrequencyMins"].Range = new Tuple(RefreshFrequencyMin, RefreshFrequencyMax); - } - - if (keyword.Properties["ConfigurationModeFrequencyMins"] != null) - { - keyword.Properties["ConfigurationModeFrequencyMins"].Range = new Tuple(ConfigurationModeFrequencyMin, ConfigurationModeFrequencyMax); - } - - if (keyword.Properties["DebugMode"] is not null) - { - keyword.Properties["DebugMode"].Values.Remove("ResourceScriptBreakAll"); - keyword.Properties["DebugMode"].ValueMap.Remove("ResourceScriptBreakAll"); - } - } - } - - /// - /// Load the default system CIM classes and create the corresponding keywords. - /// - /// Collection of any errors encountered while loading keywords. - public static void LoadDefaultCimKeywords(Collection errors) - { - LoadDefaultCimKeywords(functionsToDefine: null, errors, modulePathList: null, cacheResourcesFromMultipleModuleVersions: false); - } - - /// - /// Load the default system CIM classes and create the corresponding keywords. - /// - /// A dictionary to add the defined functions to, may be null. - public static void LoadDefaultCimKeywords(Dictionary functionsToDefine) - { - LoadDefaultCimKeywords(functionsToDefine, errors: null, modulePathList: null, cacheResourcesFromMultipleModuleVersions: false); - } - - /// - /// Load the default system CIM classes and create the corresponding keywords. - /// - /// Collection of any errors encountered while loading keywords. - /// Allow caching the resources from multiple versions of modules. - public static void LoadDefaultCimKeywords(Collection errors, bool cacheResourcesFromMultipleModuleVersions) - { - LoadDefaultCimKeywords(functionsToDefine: null, errors, modulePathList: null, cacheResourcesFromMultipleModuleVersions); - } - - /// - /// Load the default system CIM classes and create the corresponding keywords. - /// - /// A dictionary to add the defined functions to, may be null. - /// Collection of any errors encountered while loading keywords. - /// List of module path from where DSC PS modules will be loaded. - /// Allow caching the resources from multiple versions of modules. - private static void LoadDefaultCimKeywords( - Dictionary functionsToDefine, - Collection errors, - List modulePathList, - bool cacheResourcesFromMultipleModuleVersions) - { - if (!ExperimentalFeature.IsEnabled(DscExperimentalFeatureName)) - { - Exception exception = new InvalidOperationException(ParserStrings.PS7DscSupportDisabled); - errors.Add(exception); - return; - } - - NewApiIsUsed = true; - DynamicKeyword.Reset(); - Initialize(errors, modulePathList); - - // Initialize->ClearCache resets CacheResourcesFromMultipleModuleVersions to false, - // workaround is to set it after Initialize method call. - // Initialize method imports all the Inbox resources and internal classes which belongs to only one version - // of the module, so it is ok if this property is not set during cache initialization. - CacheResourcesFromMultipleModuleVersions = cacheResourcesFromMultipleModuleVersions; - - foreach (dynamic cimClass in ClassCache.Values) - { - var className = cimClass.CimClassInstance.ClassName; - var moduleInfo = ByClassModuleCache[className]; - CreateAndRegisterKeywordFromCimClass(moduleInfo.Item1, moduleInfo.Item2, cimClass.CimClassInstance, functionsToDefine, cimClass.DscResRunAsCred); - } - - // And add the Node keyword definitions - if (!DynamicKeyword.ContainsKeyword("Node")) - { - // Implement dispatch to the Node keyword. - var nodeKeyword = new DynamicKeyword() - { - BodyMode = DynamicKeywordBodyMode.ScriptBlock, - ImplementingModule = s_defaultModuleInfoForResource.Item1, - ImplementingModuleVersion = s_defaultModuleInfoForResource.Item2, - NameMode = DynamicKeywordNameMode.NameRequired, - Keyword = "Node", - }; - DynamicKeyword.AddKeyword(nodeKeyword); - } - - // And add the Import-DscResource keyword definitions - if (!DynamicKeyword.ContainsKeyword("Import-DscResource")) - { - // Implement dispatch to the Node keyword. - var nodeKeyword = new DynamicKeyword() - { - BodyMode = DynamicKeywordBodyMode.Command, - ImplementingModule = s_defaultModuleInfoForResource.Item1, - ImplementingModuleVersion = s_defaultModuleInfoForResource.Item2, - NameMode = DynamicKeywordNameMode.NoName, - Keyword = "Import-DscResource", - MetaStatement = true, - PostParse = ImportResourcePostParse, - SemanticCheck = ImportResourceCheckSemantics - }; - DynamicKeyword.AddKeyword(nodeKeyword); - } - } - - // This function is called after parsing the Import-DscResource keyword and it's arguments, but before parsing anything else. - private static ParseError[] ImportResourcePostParse(DynamicKeywordStatementAst ast) - { - var elements = Ast.CopyElements(ast.CommandElements); - var commandAst = new CommandAst(ast.Extent, elements, TokenKind.Unknown, null); - - const string NameParam = "Name"; - const string ModuleNameParam = "ModuleName"; - const string ModuleVersionParam = "ModuleVersion"; - - StaticBindingResult bindingResult = StaticParameterBinder.BindCommand(commandAst, false); - - var errorList = new List(); - foreach (var bindingException in bindingResult.BindingExceptions.Values) - { - errorList.Add(new ParseError(bindingException.CommandElement.Extent, "ParameterBindingException", bindingException.BindingException.Message)); - } - - ParameterBindingResult moduleNameBindingResult = null; - ParameterBindingResult resourceNameBindingResult = null; - ParameterBindingResult moduleVersionBindingResult = null; - - foreach (var binding in bindingResult.BoundParameters) - { - // Error case when positional parameter values are specified - var boundParameterName = binding.Key; - var parameterBindingResult = binding.Value; - if (boundParameterName.All(char.IsDigit)) - { - errorList.Add(new ParseError(parameterBindingResult.Value.Extent, "ImportDscResourcePositionalParamsNotSupported", string.Format(CultureInfo.CurrentCulture, ParserStrings.ImportDscResourcePositionalParamsNotSupported))); - continue; - } - - if (NameParam.StartsWith(boundParameterName, StringComparison.OrdinalIgnoreCase)) - { - resourceNameBindingResult = parameterBindingResult; - } - else if (ModuleNameParam.StartsWith(boundParameterName, StringComparison.OrdinalIgnoreCase)) - { - moduleNameBindingResult = parameterBindingResult; - } - else if (ModuleVersionParam.StartsWith(boundParameterName, StringComparison.OrdinalIgnoreCase)) - { - moduleVersionBindingResult = parameterBindingResult; - } - else - { - errorList.Add(new ParseError(parameterBindingResult.Value.Extent, "ImportDscResourceNeedParams", string.Format(CultureInfo.CurrentCulture, ParserStrings.ImportDscResourceNeedParams))); - } - } - - if (errorList.Count == 0 && moduleNameBindingResult == null && resourceNameBindingResult == null) - { - errorList.Add(new ParseError(ast.Extent, "ImportDscResourceNeedParams", string.Format(CultureInfo.CurrentCulture, ParserStrings.ImportDscResourceNeedParams))); - } - - // Check here if Version is specified but modulename is not specified - if (moduleVersionBindingResult != null && moduleNameBindingResult == null) - { - // only add this error again to the error list if resources is not null - // if resources and modules are both null we have already added this error in collection - // we do not want to do this twice. since we are giving same error ImportDscResourceNeedParams in both cases - // once we have different error messages for 2 scenarios we can remove this check - if (resourceNameBindingResult is not null) - { - errorList.Add(new ParseError(ast.Extent, "ImportDscResourceNeedModuleNameWithModuleVersion", string.Format(CultureInfo.CurrentCulture, ParserStrings.ImportDscResourceNeedParams))); - } - } - - string[] resourceNames = null; - if (resourceNameBindingResult is not null) - { - object resourceName = null; - if (!IsConstantValueVisitor.IsConstant(resourceNameBindingResult.Value, out resourceName, true, true) || - !LanguagePrimitives.TryConvertTo(resourceName, out resourceNames)) - { - errorList.Add(new ParseError(resourceNameBindingResult.Value.Extent, "RequiresInvalidStringArgument", string.Format(CultureInfo.CurrentCulture, ParserStrings.RequiresInvalidStringArgument, NameParam))); - } - } - - System.Version moduleVersion = null; - if (moduleVersionBindingResult is not null) - { - object moduleVer = null; - if (!IsConstantValueVisitor.IsConstant(moduleVersionBindingResult.Value, out moduleVer, true, true)) - { - errorList.Add(new ParseError(moduleVersionBindingResult.Value.Extent, "RequiresArgumentMustBeConstant", ParserStrings.RequiresArgumentMustBeConstant)); - } - - if (moduleVer is double) - { - // this happens in case -ModuleVersion 1.0, then use extent text for that. - // The better way to do it would be define static binding API against CommandInfo, that holds information about parameter types. - // This way, we can avoid this ugly special-casing and say that -ModuleVersion has type [System.Version]. - moduleVer = moduleVersionBindingResult.Value.Extent.Text; - } - - if (!LanguagePrimitives.TryConvertTo(moduleVer, out moduleVersion)) - { - errorList.Add(new ParseError(moduleVersionBindingResult.Value.Extent, "RequiresVersionInvalid", ParserStrings.RequiresVersionInvalid)); - } - } - - ModuleSpecification[] moduleSpecifications = null; - if (moduleNameBindingResult is not null) - { - object moduleName = null; - if (!IsConstantValueVisitor.IsConstant(moduleNameBindingResult.Value, out moduleName, true, true)) - { - errorList.Add(new ParseError(moduleNameBindingResult.Value.Extent, "RequiresArgumentMustBeConstant", ParserStrings.RequiresArgumentMustBeConstant)); - } - - if (LanguagePrimitives.TryConvertTo(moduleName, out moduleSpecifications)) - { - // if resourceNames are specified then we can not specify multiple modules name - if (moduleSpecifications is not null && moduleSpecifications.Length > 1 && resourceNames is not null) - { - errorList.Add(new ParseError(moduleNameBindingResult.Value.Extent, "ImportDscResourceMultipleModulesNotSupportedWithName", string.Format(CultureInfo.CurrentCulture, ParserStrings.ImportDscResourceMultipleModulesNotSupportedWithName))); - } - - // if moduleversion is specified then we can not specify multiple modules name - if (moduleSpecifications is not null && moduleSpecifications.Length > 1 && moduleVersion is not null) - { - errorList.Add(new ParseError(moduleNameBindingResult.Value.Extent, "ImportDscResourceMultipleModulesNotSupportedWithVersion", string.Format(CultureInfo.CurrentCulture, ParserStrings.ImportDscResourceNeedParams))); - } - - // if moduleversion is specified then we can not specify another version in modulespecification object of ModuleName - if (moduleSpecifications is not null && (moduleSpecifications[0].Version is not null || moduleSpecifications[0].MaximumVersion is not null) && moduleVersion is not null) - { - errorList.Add(new ParseError(moduleNameBindingResult.Value.Extent, "ImportDscResourceMultipleModuleVersionsNotSupported", string.Format(CultureInfo.CurrentCulture, ParserStrings.ImportDscResourceNeedParams))); - } - - // If moduleVersion is specified we have only one module Name in valid scenario - // So update it's version property in module specification object that will be used to load modules - if (moduleSpecifications is not null && moduleSpecifications[0].Version is null && moduleSpecifications[0].MaximumVersion is null && moduleVersion is not null) - { - moduleSpecifications[0].Version = moduleVersion; - } - } - else - { - errorList.Add(new ParseError(moduleNameBindingResult.Value.Extent, "RequiresInvalidStringArgument", string.Format(CultureInfo.CurrentCulture, ParserStrings.RequiresInvalidStringArgument, ModuleNameParam))); - } - } - - if (errorList.Count == 0) - { - // No errors, try to load the resources - LoadResourcesFromModuleInImportResourcePostParse(ast.Extent, moduleSpecifications, resourceNames, errorList); - } - - return errorList.ToArray(); - } - - // This function performs semantic checks for Import-DscResource - private static ParseError[] ImportResourceCheckSemantics(DynamicKeywordStatementAst ast) - { - List errorList = null; - - var keywordAst = Ast.GetAncestorAst(ast.Parent); - while (keywordAst is not null) - { - if (keywordAst.Keyword.Keyword.Equals("Node")) - { - if (errorList is null) - { - errorList = new List(); - } - - errorList.Add(new ParseError(ast.Extent, "ImportDscResourceInsideNode", string.Format(CultureInfo.CurrentCulture, ParserStrings.ImportDscResourceInsideNode))); - break; - } - - keywordAst = Ast.GetAncestorAst(keywordAst.Parent); - } - - if (errorList is not null) - { - return errorList.ToArray(); - } - else - { - return null; - } - } - - // This function performs semantic checks for all DSC Resources keywords. - private static ParseError[] CheckMandatoryPropertiesPresent(DynamicKeywordStatementAst ast) - { - HashSet mandatoryPropertiesNames = new HashSet(StringComparer.OrdinalIgnoreCase); - foreach (var pair in ast.Keyword.Properties) - { - if (pair.Value.Mandatory) - { - mandatoryPropertiesNames.Add(pair.Key); - } - } - - // by design mandatoryPropertiesNames are not empty at this point: - // every resource must have at least one Key property. - HashtableAst hashtableAst = null; - foreach (var commandElementsAst in ast.CommandElements) - { - hashtableAst = commandElementsAst as HashtableAst; - if (hashtableAst != null) - { - break; - } - } - - if (hashtableAst is null) - { - // nothing to validate - return null; - } - - foreach (var pair in hashtableAst.KeyValuePairs) - { - object evalResultObject; - if (IsConstantValueVisitor.IsConstant(pair.Item1, out evalResultObject, forAttribute: false, forRequires: false)) - { - var presentName = evalResultObject as string; - if (presentName is not null) - { - if (mandatoryPropertiesNames.Remove(presentName) && mandatoryPropertiesNames.Count == 0) - { - // optimization, once all mandatory properties are specified, we can safely exit. - return null; - } - } - } - } - - if (mandatoryPropertiesNames.Count > 0) - { - ParseError[] errors = new ParseError[mandatoryPropertiesNames.Count]; - var extent = ast.CommandElements[0].Extent; - int i = 0; - foreach (string name in mandatoryPropertiesNames) - { - errors[i] = new ParseError( - extent, - "MissingValueForMandatoryProperty", - string.Format( - CultureInfo.CurrentCulture, - ParserStrings.MissingValueForMandatoryProperty, - ast.Keyword.Keyword, - ast.Keyword.Properties.First(p => StringComparer.OrdinalIgnoreCase.Equals(p.Value.Name, name)).Value.TypeConstraint, - name)); - i++; - } - - return errors; - } - - return null; - } - - /// - /// Load DSC resources from specified module. - /// - /// Script statement loading the module, can be null. - /// Module information, can be null. - /// Name of the resource to be loaded from module. - /// List of errors reported by the method. - internal static void LoadResourcesFromModuleInImportResourcePostParse( - IScriptExtent scriptExtent, - ModuleSpecification[] moduleSpecifications, - string[] resourceNames, - List errorList) - { - // get all required modules - var modules = new Collection(); - if (moduleSpecifications is not null) - { - foreach (var moduleToImport in moduleSpecifications) - { - bool foundModule = false; - var moduleInfos = ModuleCmdletBase.GetModuleIfAvailable(moduleToImport); - - if (moduleInfos.Count >= 1 && (moduleToImport.Version is not null || moduleToImport.Guid is not null)) - { - foreach (var psModuleInfo in moduleInfos) - { - if ((moduleToImport.Guid.HasValue && moduleToImport.Guid.Equals(psModuleInfo.Guid)) || - (moduleToImport.Version is not null && - moduleToImport.Version.Equals(psModuleInfo.Version))) - { - modules.Add(psModuleInfo); - foundModule = true; - break; - } - } - } - else if (moduleInfos.Count == 1) - { - modules.Add(moduleInfos[0]); - foundModule = true; - } - - if (!foundModule) - { - if (moduleInfos.Count > 1) - { - errorList.Add( - new ParseError( - scriptExtent, - "MultipleModuleEntriesFoundDuringParse", - string.Format(CultureInfo.CurrentCulture, ParserStrings.MultipleModuleEntriesFoundDuringParse, moduleToImport.Name))); - } - else - { - string moduleString = moduleToImport.Version == null - ? moduleToImport.Name - : string.Format(CultureInfo.CurrentCulture, "<{0}, {1}>", moduleToImport.Name, moduleToImport.Version); - - errorList.Add(new ParseError(scriptExtent, "ModuleNotFoundDuringParse", string.Format(CultureInfo.CurrentCulture, ParserStrings.ModuleNotFoundDuringParse, moduleString))); - } - - return; - } - } - } - else if (resourceNames is not null) - { - // Lookup the required resources under available PowerShell modules when modulename is not specified - // Make sure that this is not a circular import/parsing - var callLocation = string.Join(':', scriptExtent.File, scriptExtent.StartLineNumber, scriptExtent.StartColumnNumber, scriptExtent.Text); - if (!t_currentImportDscResourceInvocations.Contains(callLocation)) - { - t_currentImportDscResourceInvocations.Add(callLocation); - using (var powerShell = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace)) - { - powerShell.AddCommand("Get-Module"); - powerShell.AddParameter("ListAvailable"); - modules = powerShell.Invoke(); - } - } - } - - // When ModuleName only specified, we need to import all resources from that module - var resourcesToImport = new List(); - if (resourceNames is null || resourceNames.Length == 0) - { - resourcesToImport.Add("*"); - } - else - { - resourcesToImport.AddRange(resourceNames); - } - - foreach (var moduleInfo in modules) - { - var resourcesFound = new List(); - var exceptionList = new System.Collections.ObjectModel.Collection(); - LoadPowerShellClassResourcesFromModule(primaryModuleInfo: moduleInfo, moduleInfo: moduleInfo, resourcesToImport: resourcesToImport, resourcesFound: resourcesFound, errorList: exceptionList, functionsToDefine: null, recurse: true, extent: scriptExtent); - foreach (Exception ex in exceptionList) - { - errorList.Add(new ParseError(scriptExtent, "ClassResourcesLoadingFailed", ex.Message)); - } - - foreach (var resource in resourcesFound) - { - resourcesToImport.Remove(resource); - } - - if (resourcesToImport.Count == 0) - { - break; - } - } - - if (resourcesToImport.Count > 0) - { - foreach (var resourceNameToImport in resourcesToImport) - { - if (!resourceNameToImport.Contains('*')) - { - errorList.Add(new ParseError(scriptExtent, "DscResourcesNotFoundDuringParsing", string.Format(CultureInfo.CurrentCulture, ParserStrings.DscResourcesNotFoundDuringParsing, resourceNameToImport))); - } - } - } - } - - private static void LoadPowerShellClassResourcesFromModule( - PSModuleInfo primaryModuleInfo, - PSModuleInfo moduleInfo, - ICollection resourcesToImport, - ICollection resourcesFound, - Collection errorList, - Dictionary functionsToDefine = null, - bool recurse = true, - IScriptExtent extent = null) - { - if (primaryModuleInfo._declaredDscResourceExports is null || primaryModuleInfo._declaredDscResourceExports.Count == 0) - { - return; - } - - if (moduleInfo.ModuleType == ModuleType.Binary) - { - throw PSTraceSource.NewArgumentException("isConfiguration", ParserStrings.ConfigurationNotSupportedInPowerShellCore); - } - else - { - string scriptPath = null; - if (moduleInfo.RootModule is not null) - { - scriptPath = Path.Join(moduleInfo.ModuleBase, moduleInfo.RootModule); - } - else if (moduleInfo.Path is not null) - { - scriptPath = moduleInfo.Path; - } - - LoadPowerShellClassResourcesFromModule(scriptPath, primaryModuleInfo, resourcesToImport, resourcesFound, functionsToDefine, errorList, extent); - } - - if (moduleInfo.NestedModules is not null && recurse) - { - foreach (var nestedModule in moduleInfo.NestedModules) - { - LoadPowerShellClassResourcesFromModule(primaryModuleInfo, nestedModule, resourcesToImport, resourcesFound, errorList, functionsToDefine, recurse: false, extent: extent); - } - } - } - - /// - /// Import class resources from module. - /// - /// Module information. - /// Collection of resources to import. - /// Functions to define. - /// List of errors to return. - /// The list of resources imported from this module. - public static List ImportClassResourcesFromModule(PSModuleInfo moduleInfo, ICollection resourcesToImport, Dictionary functionsToDefine, Collection errors) - { - if (!ExperimentalFeature.IsEnabled(DscExperimentalFeatureName)) - { - throw new InvalidOperationException(ParserStrings.PS7DscSupportDisabled); - } - - var resourcesImported = new List(); - LoadPowerShellClassResourcesFromModule(moduleInfo, moduleInfo, resourcesToImport, resourcesImported, errors, functionsToDefine); - return resourcesImported; - } - - internal static PSObject[] GenerateJsonClassesForAst(TypeDefinitionAst typeAst, PSModuleInfo module, DSCResourceRunAsCredential runAsBehavior) - { - var embeddedInstanceTypes = new List(); - - var result = GenerateJsonClassesForAst(typeAst, embeddedInstanceTypes); - var visitedInstances = new List(); - visitedInstances.Add(typeAst); - var classes = ProcessEmbeddedInstanceTypes(embeddedInstanceTypes, visitedInstances); - AddEmbeddedInstanceTypesToCaches(classes, module, runAsBehavior); - - return result; - } - - private static List ProcessEmbeddedInstanceTypes(List embeddedInstanceTypes, List visitedInstances) - { - var result = new List(); - while (embeddedInstanceTypes.Count > 0) - { - var batchedTypes = embeddedInstanceTypes.Where(x => !visitedInstances.Contains(x)).ToArray(); - embeddedInstanceTypes.Clear(); - - for (int i = batchedTypes.Length - 1; i >= 0; i--) - { - visitedInstances.Add(batchedTypes[i]); - var typeAst = batchedTypes[i] as TypeDefinitionAst; - if (typeAst is not null) - { - var classes = GenerateJsonClassesForAst(typeAst, embeddedInstanceTypes); - result.AddRange(classes); - } - } - } - - return result; - } - - private static void AddEmbeddedInstanceTypesToCaches(IEnumerable classes, PSModuleInfo module, DSCResourceRunAsCredential runAsBehavior) - { - foreach (dynamic c in classes) - { - var className = c.ClassName; - string alias = GetFriendlyName(c); - var friendlyName = string.IsNullOrEmpty(alias) ? className : alias; - var moduleQualifiedResourceName = GetModuleQualifiedResourceName(module.Name, module.Version.ToString(), className, friendlyName); - var classCacheEntry = new DscClassCacheEntry(runAsBehavior, false, c, module.Path); - ClassCache[moduleQualifiedResourceName] = classCacheEntry; - GuestConfigClassCache[moduleQualifiedResourceName] = classCacheEntry; - ByClassModuleCache[className] = new Tuple(module.Name, module.Version); - } - } - - internal static string MapTypeNameToMofType(ITypeName typeName, string memberName, string className, out bool isArrayType, out string embeddedInstanceType, List embeddedInstanceTypes, ref string[] enumNames) - { - TypeName propTypeName; - var arrayTypeName = typeName as ArrayTypeName; - if (arrayTypeName is not null) - { - isArrayType = true; - propTypeName = arrayTypeName.ElementType as TypeName; - } - else - { - isArrayType = false; - propTypeName = typeName as TypeName; - } - - if (propTypeName is null || propTypeName._typeDefinitionAst is null) - { - throw new NotSupportedException(string.Format( - CultureInfo.InvariantCulture, - ParserStrings.UnsupportedPropertyTypeOfDSCResourceClass, - memberName, - typeName.FullName, - typeName)); - } - - if (propTypeName._typeDefinitionAst.IsEnum) - { - enumNames = propTypeName._typeDefinitionAst.Members.Select(m => m.Name).ToArray(); - isArrayType = false; - embeddedInstanceType = null; - return "string"; - } - - if (!embeddedInstanceTypes.Contains(propTypeName._typeDefinitionAst)) - { - embeddedInstanceTypes.Add(propTypeName._typeDefinitionAst); - } - - embeddedInstanceType = propTypeName.Name.Replace('.', '_'); - return "Instance"; - } - - private static PSObject[] GenerateJsonClassesForAst(TypeDefinitionAst typeAst, List embeddedInstanceTypes) - { - // MOF-based implementation of this used to generate MOF string representing classes/typeAst and pass it to MMI/MOF deserializer to get CimClass array - // Here we are avoiding that roundtrip by constructing the resulting PSObjects directly - var className = typeAst.Name; - - string cimSuperClassName = null; - if (typeAst.Attributes.Any(a => a.TypeName.GetReflectionAttributeType() == typeof(DscResourceAttribute))) - { - cimSuperClassName = "OMI_BaseResource"; - } - - var cimClassProperties = ProcessMembers(embeddedInstanceTypes, typeAst, className).ToArray(); - - Queue bases = new Queue(); - foreach (var b in typeAst.BaseTypes) - { - bases.Enqueue(b); - } - - while (bases.Count > 0) - { - var b = bases.Dequeue(); - var tc = b as TypeConstraintAst; - - if (tc is not null) - { - b = tc.TypeName.GetReflectionType(); - if (b is null) - { - var td = tc.TypeName as TypeName; - if (td is not null && td._typeDefinitionAst is not null) - { - ProcessMembers(embeddedInstanceTypes, td._typeDefinitionAst, className); - foreach (var b1 in td._typeDefinitionAst.BaseTypes) - { - bases.Enqueue(b1); - } - } - - continue; - } - } - } - - var result = new PSObject(); - result.Properties.Add(new PSNoteProperty("ClassName", className)); - result.Properties.Add(new PSNoteProperty("FriendlyName", className)); - result.Properties.Add(new PSNoteProperty("SuperClassName", cimSuperClassName)); - result.Properties.Add(new PSNoteProperty("ClassProperties", cimClassProperties)); - - return new[] { result }; - } - - private static List ProcessMembers(List embeddedInstanceTypes, TypeDefinitionAst typeDefinitionAst, string className) - { - List result = new List(); - - foreach (var member in typeDefinitionAst.Members) - { - var property = member as PropertyMemberAst; - - if (property == null || property.IsStatic || - property.Attributes.All(a => a.TypeName.GetReflectionAttributeType() != typeof(DscPropertyAttribute))) - { - continue; - } - - var memberType = property.PropertyType is null - ? typeof(object) - : property.PropertyType.TypeName.GetReflectionType(); - - var attributes = new List(); - for (int i = 0; i < property.Attributes.Count; i++) - { - attributes.Add(property.Attributes[i].GetAttribute()); - } - - string mofType; - bool isArrayType; - string embeddedInstanceType; - string[] enumNames = null; - - if (memberType != null) - { - mofType = MapTypeToMofType(memberType, member.Name, className, out isArrayType, out embeddedInstanceType, embeddedInstanceTypes); - if (memberType.IsEnum) - { - enumNames = Enum.GetNames(memberType); - } - } - else - { - // PropertyType can't be null, we used typeof(object) above in that case so we don't get here. - mofType = MapTypeNameToMofType(property.PropertyType.TypeName, member.Name, className, out isArrayType, out embeddedInstanceType, embeddedInstanceTypes, ref enumNames); - } - - var propertyObject = new PSObject(); - propertyObject.Properties.Add(new PSNoteProperty(@"Name", member.Name)); - propertyObject.Properties.Add(new PSNoteProperty(@"CimType", mofType + (isArrayType ? "Array" : string.Empty))); - if (!string.IsNullOrEmpty(embeddedInstanceType)) - { - propertyObject.Properties.Add(new PSNoteProperty(@"ReferenceClassName", embeddedInstanceType)); - } - - PSObject attributesPSObject = null; - foreach (var attr in attributes) - { - var dscProperty = attr as DscPropertyAttribute; - if (dscProperty is not null) - { - if (attributesPSObject is null) - { - attributesPSObject = new PSObject(); - } - - if (dscProperty.Key) - { - attributesPSObject.Properties.Add(new PSNoteProperty("Key", true)); - } - - if (dscProperty.Mandatory) - { - attributesPSObject.Properties.Add(new PSNoteProperty("Required", true)); - } - - if (dscProperty.NotConfigurable) - { - attributesPSObject.Properties.Add(new PSNoteProperty("Read", true)); - } - - continue; - } - - var validateSet = attr as ValidateSetAttribute; - if (validateSet is not null) - { - if (attributesPSObject is null) - { - attributesPSObject = new PSObject(); - } - - List valueMap = new List(validateSet.ValidValues); - List values = new List(validateSet.ValidValues); - attributesPSObject.Properties.Add(new PSNoteProperty("ValueMap", valueMap)); - attributesPSObject.Properties.Add(new PSNoteProperty("Values", values)); - } - } - - if (attributesPSObject is not null) - { - propertyObject.Properties.Add(new PSNoteProperty(@"Qualifiers", attributesPSObject)); - } - - result.Add(propertyObject); - } - - return result; - } - - private static bool GetResourceDefinitionsFromModule(string fileName, out IEnumerable resourceDefinitions, Collection errorList, IScriptExtent extent) - { - resourceDefinitions = null; - - if (string.IsNullOrEmpty(fileName)) - { - return false; - } - - if (!".psm1".Equals(Path.GetExtension(fileName), StringComparison.OrdinalIgnoreCase) && - !".ps1".Equals(Path.GetExtension(fileName), StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - Token[] tokens; - ParseError[] errors; - var ast = Parser.ParseFile(fileName, out tokens, out errors); - - if (errors is not null && errors.Length > 0) - { - if (errorList is not null && extent is not null) - { - List errorMessages = new List(); - foreach (var error in errors) - { - errorMessages.Add(error.ToString()); - } - - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.FailToParseModuleScriptFile, fileName, string.Join(Environment.NewLine, errorMessages)); - e.SetErrorId("FailToParseModuleScriptFile"); - errorList.Add(e); - } - - return false; - } - - resourceDefinitions = ast.FindAll( - n => - { - var typeAst = n as TypeDefinitionAst; - if (typeAst is not null) - { - for (int i = 0; i < typeAst.Attributes.Count; i++) - { - var a = typeAst.Attributes[i]; - if (a.TypeName.GetReflectionAttributeType() == typeof(DscResourceAttribute)) - { - return true; - } - } - } - - return false; - }, - false); - - return true; - } - - private static bool LoadPowerShellClassResourcesFromModule(string fileName, PSModuleInfo module, ICollection resourcesToImport, ICollection resourcesFound, Dictionary functionsToDefine, Collection errorList, IScriptExtent extent) - { - IEnumerable resourceDefinitions; - if (!GetResourceDefinitionsFromModule(fileName, out resourceDefinitions, errorList, extent)) - { - return false; - } - - var result = false; - - const WildcardOptions options = WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant; - IEnumerable patternList = SessionStateUtilities.CreateWildcardsFromStrings(module._declaredDscResourceExports, options); - - foreach (var r in resourceDefinitions) - { - result = true; - var resourceDefnAst = (TypeDefinitionAst)r; - - if (!SessionStateUtilities.MatchesAnyWildcardPattern(resourceDefnAst.Name, patternList, true)) - { - continue; - } - - bool skip = true; - foreach (var toImport in resourcesToImport) - { - if (WildcardPattern.Get(toImport, WildcardOptions.IgnoreCase).IsMatch(resourceDefnAst.Name)) - { - skip = false; - break; - } - } - - if (skip) - { - continue; - } - - // Parse the Resource Attribute to see if RunAs behavior is specified for the resource. - DSCResourceRunAsCredential runAsBehavior = DSCResourceRunAsCredential.Default; - foreach (var attr in resourceDefnAst.Attributes) - { - if (attr.TypeName.GetReflectionAttributeType() == typeof(DscResourceAttribute)) - { - foreach (var na in attr.NamedArguments) - { - if (na.ArgumentName.Equals("RunAsCredential", StringComparison.OrdinalIgnoreCase)) - { - var dscResourceAttribute = attr.GetAttribute() as DscResourceAttribute; - if (dscResourceAttribute != null) - { - runAsBehavior = dscResourceAttribute.RunAsCredential; - } - } - } - } - } - - var classes = GenerateJsonClassesForAst(resourceDefnAst, module, runAsBehavior); - - ProcessJsonForDynamicKeywords(module, resourcesFound, functionsToDefine, classes, runAsBehavior, errorList); - } - - return result; - } - - private static readonly Dictionary s_mapPrimitiveDotNetTypeToMof = new Dictionary() - { - { typeof(sbyte), "sint8" }, - { typeof(byte), "uint8" }, - { typeof(short), "sint16" }, - { typeof(ushort), "uint16" }, - { typeof(int), "sint32" }, - { typeof(uint), "uint32" }, - { typeof(long), "sint64" }, - { typeof(ulong), "uint64" }, - { typeof(float), "real32" }, - { typeof(double), "real64" }, - { typeof(bool), "boolean" }, - { typeof(string), "string" }, - { typeof(DateTime), "datetime" }, - { typeof(PSCredential), "string" }, - { typeof(char), "char16" }, - }; - - internal static string MapTypeToMofType(Type type, string memberName, string className, out bool isArrayType, out string embeddedInstanceType, List embeddedInstanceTypes) - { - isArrayType = false; - if (type.IsValueType) - { - type = Nullable.GetUnderlyingType(type) ?? type; - } - - if (type.IsEnum) - { - embeddedInstanceType = null; - return "string"; - } - - if (type == typeof(Hashtable)) - { - // Hashtable is obviously not an array, but in the mof, we represent - // it as string[] (really, embeddedinstance of MSFT_KeyValuePair), but - // we need an array to hold each entry in the hashtable. - isArrayType = true; - embeddedInstanceType = "MSFT_KeyValuePair"; - return "string"; - } - - if (type == typeof(PSCredential)) - { - embeddedInstanceType = "MSFT_Credential"; - return "string"; - } - - if (type.IsArray) - { - isArrayType = true; - bool temp; - var elementType = type.GetElementType(); - if (!elementType.IsArray) - { - return MapTypeToMofType(type.GetElementType(), memberName, className, out temp, out embeddedInstanceType, embeddedInstanceTypes); - } - } - else - { - string cimType; - if (s_mapPrimitiveDotNetTypeToMof.TryGetValue(type, out cimType)) - { - embeddedInstanceType = null; - return cimType; - } - } - - bool supported = false; - bool missingDefaultConstructor = false; - if (type.IsValueType) - { - if (s_mapPrimitiveDotNetTypeToMof.ContainsKey(type)) - { - supported = true; - } - } - else if (!type.IsAbstract) - { - // Must have default constructor, at least 1 public property/field, and no base classes - if (type.GetConstructor(Type.EmptyTypes) is null) - { - missingDefaultConstructor = true; - } - else if (type.BaseType == typeof(object) && - (type.GetProperties(BindingFlags.Instance | BindingFlags.Public).Length > 0 || - type.GetFields(BindingFlags.Instance | BindingFlags.Public).Length > 0)) - { - supported = true; - } - } - - if (supported) - { - if (!embeddedInstanceTypes.Contains(type)) - { - embeddedInstanceTypes.Add(type); - } - - // The type is obviously not a string, but in the mof, we represent - // it as string (really, embeddedinstance of the class type) - embeddedInstanceType = type.FullName.Replace('.', '_'); - return "string"; - } - - if (missingDefaultConstructor) - { - throw new NotSupportedException(string.Format( - CultureInfo.InvariantCulture, - ParserStrings.DscResourceMissingDefaultConstructor, - type.Name)); - } - else - { - throw new NotSupportedException(string.Format( - CultureInfo.InvariantCulture, - ParserStrings.UnsupportedPropertyTypeOfDSCResourceClass, - memberName, - type.Name, - className)); - } - } - - private static void ProcessJsonForDynamicKeywords( - PSModuleInfo module, - ICollection resourcesFound, - Dictionary functionsToDefine, - PSObject[] classes, - DSCResourceRunAsCredential runAsBehavior, - Collection errors) - { - foreach (dynamic c in classes) - { - var className = c.ClassName; - string alias = GetFriendlyName(c); - var friendlyName = string.IsNullOrEmpty(alias) ? className : alias; - if (!CacheResourcesFromMultipleModuleVersions) - { - // Find & remove the previous version of the resource. - List> resourceList = FindResourceInCache(module.Name, className, friendlyName); - - if (resourceList.Count > 0 && !string.IsNullOrEmpty(resourceList[0].Key)) - { - ClassCache.Remove(resourceList[0].Key); - - // keyword is already defined and it is a Inbox resource, remove it - if (DynamicKeyword.ContainsKeyword(friendlyName) && resourceList[0].Value.IsImportedImplicitly) - { - DynamicKeyword.RemoveKeyword(friendlyName); - } - } - } - - var moduleQualifiedResourceName = GetModuleQualifiedResourceName(module.Name, module.Version.ToString(), className, friendlyName); - DscClassCacheEntry existingCacheEntry = null; - if (ClassCache.TryGetValue(moduleQualifiedResourceName, out existingCacheEntry)) - { - if (errors is not null) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.DuplicateCimClassDefinition, className, module.Path, existingCacheEntry.ModulePath); - e.SetErrorId("DuplicateCimClassDefinition"); - errors.Add(e); - } - } - else - { - var classCacheEntry = new DscClassCacheEntry(runAsBehavior, false, c, module.Path); - ClassCache[moduleQualifiedResourceName] = classCacheEntry; - GuestConfigClassCache[moduleQualifiedResourceName] = classCacheEntry; - ByClassModuleCache[className] = new Tuple(module.Name, module.Version); - resourcesFound.Add(className); - CreateAndRegisterKeywordFromCimClass(module.Name, module.Version, c, functionsToDefine, runAsBehavior); - } - } - } - - /// - /// Returns an error record to use in the case of a malformed resource reference in the DependsOn list. - /// - /// The malformed resource. - /// The referencing resource instance. - /// Generated error record. - public static ErrorRecord GetBadlyFormedRequiredResourceIdErrorRecord(string badDependsOnReference, string definingResource) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.GetBadlyFormedRequiredResourceId, badDependsOnReference, definingResource); - e.SetErrorId("GetBadlyFormedRequiredResourceId"); - return e.ErrorRecord; - } - - /// - /// Returns an error record to use in the case of a malformed resource reference in the exclusive resources list. - /// - /// The malformed resource. - /// The referencing resource instance. - /// Generated error record. - public static ErrorRecord GetBadlyFormedExclusiveResourceIdErrorRecord(string badExclusiveResourcereference, string definingResource) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.GetBadlyFormedExclusiveResourceId, badExclusiveResourcereference, definingResource); - e.SetErrorId("GetBadlyFormedExclusiveResourceId"); - return e.ErrorRecord; - } - - /// - /// If a partial configuration is in 'Pull' Mode, it needs a configuration source. - /// - /// Resource id. - /// Generated error record. - public static ErrorRecord GetPullModeNeedConfigurationSource(string resourceId) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.GetPullModeNeedConfigurationSource, resourceId); - e.SetErrorId("GetPullModeNeedConfigurationSource"); - return e.ErrorRecord; - } - - /// - /// Refresh Mode can not be Disabled for the Partial Configurations. - /// - /// Resource id. - /// Generated error record. - public static ErrorRecord DisabledRefreshModeNotValidForPartialConfig(string resourceId) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.DisabledRefreshModeNotValidForPartialConfig, resourceId); - e.SetErrorId("DisabledRefreshModeNotValidForPartialConfig"); - return e.ErrorRecord; - } - - /// - /// Returns an error record to use in the case of a malformed resource reference in the DependsOn list. - /// - /// The duplicate resource identifier. - /// The node being defined. - /// The error record to use. - public static ErrorRecord DuplicateResourceIdInNodeStatementErrorRecord(string duplicateResourceId, string nodeName) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.DuplicateResourceIdInNodeStatement, duplicateResourceId, nodeName); - e.SetErrorId("DuplicateResourceIdInNodeStatement"); - return e.ErrorRecord; - } - - /// - /// Returns an error record to use in the case of a configuration name is invalid. - /// - /// Configuration name. - /// Generated error record. - public static ErrorRecord InvalidConfigurationNameErrorRecord(string configurationName) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.InvalidConfigurationName, configurationName); - e.SetErrorId("InvalidConfigurationName"); - return e.ErrorRecord; - } - - /// - /// Returns an error record to use in the case of the given value for a property is invalid. - /// - /// Property name. - /// Property value. - /// Keyword name. - /// Valid property values. - /// Generated error record. - public static ErrorRecord InvalidValueForPropertyErrorRecord(string propertyName, string value, string keywordName, string validValues) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.InvalidValueForProperty, value, propertyName, keywordName, validValues); - e.SetErrorId("InvalidValueForProperty"); - return e.ErrorRecord; - } - - /// - /// Returns an error record to use in case the given property is not valid LocalConfigurationManager property. - /// - /// Property name. - /// Valid properties. - /// Generated error record. - public static ErrorRecord InvalidLocalConfigurationManagerPropertyErrorRecord(string propertyName, string validProperties) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.InvalidLocalConfigurationManagerProperty, propertyName, validProperties); - e.SetErrorId("InvalidLocalConfigurationManagerProperty"); - return e.ErrorRecord; - } - - /// - /// Returns an error record to use in the case of the given value for a property is not supported. - /// - /// Property name. - /// Property value. - /// Keyword name. - /// Valid property values. - /// Generated error record. - public static ErrorRecord UnsupportedValueForPropertyErrorRecord(string propertyName, string value, string keywordName, string validValues) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.UnsupportedValueForProperty, value, propertyName, keywordName, validValues); - e.SetErrorId("UnsupportedValueForProperty"); - return e.ErrorRecord; - } - - /// - /// Returns an error record to use in the case of no value is provided for a mandatory property. - /// - /// Keyword name. - /// Type name. - /// Property name. - /// Generated error record. - public static ErrorRecord MissingValueForMandatoryPropertyErrorRecord(string keywordName, string typeName, string propertyName) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.MissingValueForMandatoryProperty, keywordName, typeName, propertyName); - e.SetErrorId("MissingValueForMandatoryProperty"); - return e.ErrorRecord; - } - - /// - /// Returns an error record to use in the case of more than one values are provided for DebugMode property. - /// - /// Generated error record. - public static ErrorRecord DebugModeShouldHaveOneValue() - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.DebugModeShouldHaveOneValue); - e.SetErrorId("DebugModeShouldHaveOneValue"); - return e.ErrorRecord; - } - - /// - /// Return an error to indicate a value is out of range for a dynamic keyword property. - /// - /// Rroperty name. - /// Resource name. - /// Provided value. - /// Valid range lower bound. - /// Valid range upper bound. - /// Generated error record. - public static ErrorRecord ValueNotInRangeErrorRecord(string property, string name, int providedValue, int lower, int upper) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.ValueNotInRange, property, name, providedValue, lower, upper); - e.SetErrorId("ValueNotInRange"); - return e.ErrorRecord; - } - - /// - /// Returns an error record to use when composite resource and its resource instances both has PsDscRunAsCredentials value. - /// - /// ResourceId of resource. - /// Generated error record. - public static ErrorRecord PsDscRunAsCredentialMergeErrorForCompositeResources(string resourceId) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.PsDscRunAsCredentialMergeErrorForCompositeResources, resourceId); - e.SetErrorId("PsDscRunAsCredentialMergeErrorForCompositeResources"); - return e.ErrorRecord; - } - - /// - /// Routine to format a usage string from keyword. The resulting string should look like: - /// User [string] #ResourceName - /// { - /// UserName = [string] - /// [ Description = [string] ] - /// [ Disabled = [bool] ] - /// [ Ensure = [string] { Absent | Present } ] - /// [ Force = [bool] ] - /// [ FullName = [string] ] - /// [ Password = [PSCredential] ] - /// [ PasswordChangeNotAllowed = [bool] ] - /// [ PasswordChangeRequired = [bool] ] - /// [ PasswordNeverExpires = [bool] ] - /// [ DependsOn = [string[]] ] - /// } - /// - /// Dynamic keyword. - /// Usage string. - public static string GetDSCResourceUsageString(DynamicKeyword keyword) - { - StringBuilder usageString; - switch (keyword.NameMode) - { - // Name must be present and simple non-empty bare word - case DynamicKeywordNameMode.SimpleNameRequired: - usageString = new StringBuilder(keyword.Keyword + " [string] # Resource Name"); - break; - - // Name must be present but can also be an expression - case DynamicKeywordNameMode.NameRequired: - usageString = new StringBuilder(keyword.Keyword + " [string[]] # Name List"); - break; - - // Name may be optionally present, but if it is present, it must be a non-empty bare word. - case DynamicKeywordNameMode.SimpleOptionalName: - usageString = new StringBuilder(keyword.Keyword + " [ [string] ] # Optional Name"); - break; - - // Name may be optionally present, expression or bare word - case DynamicKeywordNameMode.OptionalName: - usageString = new StringBuilder(keyword.Keyword + " [ [string[]] ] # Optional NameList"); - break; - - // Does not take a name - default: - usageString = new StringBuilder(keyword.Keyword); - break; - } - - usageString.Append("\n{\n"); - - bool listKeyProperties = true; - while (true) - { - foreach (var prop in keyword.Properties.OrderBy(ob => ob.Key)) - { - if (string.Equals(prop.Key, "ResourceId", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - var propVal = prop.Value; - if ((listKeyProperties && propVal.IsKey) || (!listKeyProperties && !propVal.IsKey)) - { - usageString.Append(propVal.Mandatory ? " " : " [ "); - usageString.Append(prop.Key); - usageString.Append(" = "); - usageString.Append(FormatCimPropertyType(propVal, !propVal.Mandatory)); - } - } - - if (listKeyProperties) - { - listKeyProperties = false; - } - else - { - break; - } - } - - usageString.Append('}'); - - return usageString.ToString(); - } - - /// - /// Format the type name of a CIM property in a presentable way. - /// - /// Dynamic keyword property. - /// If this is optional property or not. - /// CIM property type string. - private static StringBuilder FormatCimPropertyType(DynamicKeywordProperty prop, bool isOptionalProperty) - { - string cimTypeName = prop.TypeConstraint; - StringBuilder formattedTypeString = new StringBuilder(); - - if (string.Equals(cimTypeName, "MSFT_Credential", StringComparison.OrdinalIgnoreCase)) - { - formattedTypeString.Append("[PSCredential]"); - } - else if (string.Equals(cimTypeName, "MSFT_KeyValuePair", StringComparison.OrdinalIgnoreCase) || string.Equals(cimTypeName, "MSFT_KeyValuePair[]", StringComparison.OrdinalIgnoreCase)) - { - formattedTypeString.Append("[Hashtable]"); - } - else - { - string convertedTypeString = System.Management.Automation.LanguagePrimitives.ConvertTypeNameToPSTypeName(cimTypeName); - if (!string.IsNullOrEmpty(convertedTypeString) && !string.Equals(convertedTypeString, "[]", StringComparison.OrdinalIgnoreCase)) - { - formattedTypeString.Append(convertedTypeString); - } - else - { - formattedTypeString.Append("[" + cimTypeName + "]"); - } - } - - // Do the property values map - if (prop.ValueMap is not null && prop.ValueMap.Count > 0) - { - formattedTypeString.Append(" { " + string.Join(" | ", prop.ValueMap.Keys.OrderBy(x => x)) + " }"); - } - - // We prepend optional property with "[" so close out it here. This way it is shown with [ ] to indication optional - if (isOptionalProperty) - { - formattedTypeString.Append(']'); - } - - formattedTypeString.Append('\n'); - - return formattedTypeString; - } - - /// - /// Gets the scriptblock that implements the CIM keyword functionality. - /// - private static ScriptBlock CimKeywordImplementationFunction - { - get - { - // The scriptblock cache will handle mutual exclusion - return s_cimKeywordImplementationFunction ??= ScriptBlock.Create(CimKeywordImplementationFunctionText); - } - } - - private static ScriptBlock s_cimKeywordImplementationFunction; - - private const string CimKeywordImplementationFunctionText = @" - param ( - [Parameter(Mandatory)] - $KeywordData, - [Parameter(Mandatory)] - $Name, - [Parameter(Mandatory)] - [Hashtable] - $Value, - [Parameter(Mandatory)] - $SourceMetadata - ) - -# walk the call stack to get at all of the enclosing configuration resource IDs - $stackedConfigs = @(Get-PSCallStack | - where { ($null -ne $_.InvocationInfo.MyCommand) -and ($_.InvocationInfo.MyCommand.CommandType -eq 'Configuration') }) -# keep all but the top-most - $stackedConfigs = $stackedConfigs[0..(@($stackedConfigs).Length - 2)] -# and build the complex resource ID suffix. - $complexResourceQualifier = ( $stackedConfigs | ForEach-Object { '[' + $_.Command + ']' + $_.InvocationInfo.BoundParameters['InstanceName'] } ) -join '::' - -# -# Utility function used to validate that the DependsOn arguments are well-formed. -# The function also adds them to the define nodes resource collection. -# in the case of resources generated inside a script resource, this routine -# will also fix up the DependsOn references to '[Type]Instance::[OuterType]::OuterInstance -# - function Test-DependsOn - { - -# make sure the references are well-formed - $updatedDependsOn = foreach ($DependsOnVar in $value['DependsOn']) { -# match [ResourceType]ResourceName. ResourceName should starts with [a-z_0-9] followed by [a-z_0-9\p{Zs}\.\\-]* - if ($DependsOnVar -notmatch '^\[[a-z]\w*\][a-z_0-9][a-z_0-9\p{Zs}\.\\-]*$') - { - Update-ConfigurationErrorCount - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::GetBadlyFormedRequiredResourceIdErrorRecord($DependsOnVar, $resourceId)) - } - -# Fix up DependsOn for nested names - if ($MyTypeName -and $typeName -ne $MyTypeName -and $InstanceName) - { - ""$DependsOnVar::$complexResourceQualifier"" - } - else - { - $DependsOnVar - } - } - - $value['DependsOn']= $updatedDependsOn - - if($null -ne $DependsOn) - { -# -# Combine DependsOn with dependson from outer composite resource -# which is set as local variable $DependsOn at the composite resource context -# - $value['DependsOn']= @($value['DependsOn']) + $DependsOn - } - -# Save the resource id in a per-node dictionary to do cross validation at the end - Set-NodeResources $resourceId @( $value['DependsOn']) - -# Remove depends on because it need to be fixed up for composite resources -# We do it in ValidateNodeResource and Update-Depends on in configuration/Node function - $value.Remove('DependsOn') - } - -# A copy of the value object with correctly-cased property names - $canonicalizedValue = @{} - - $typeName = $keywordData.ResourceName # CIM type - $keywordName = $keywordData.Keyword # user-friendly alias that is used in scripts - $keyValues = '' - $debugPrefix = "" ${TypeName}:"" # set up a debug prefix string that makes it easier to track what's happening. - - Write-Debug ""${debugPrefix} RESOURCE PROCESSING STARTED [KeywordName='$keywordName'] Function='$($myinvocation.Invocationname)']"" - -# Check whether it's an old style metaconfig - $OldMetaConfig = $false - if ((-not $IsMetaConfig) -and ($keywordName -ieq 'LocalConfigurationManager')) { - $OldMetaConfig = $true - } - -# Check to see if it's a resource keyword. If so add the meta-properties to the canonical property collection. - $resourceId = $null -# todo: need to include configuration managers and partial configuration - if (($keywordData.Properties.Keys -contains 'DependsOn') -or (($KeywordData.ImplementingModule -ieq 'PSDesiredStateConfigurationEngine') -and ($KeywordData.NameMode -eq [System.Management.Automation.Language.DynamicKeywordNameMode]::NameRequired))) - { - - $resourceId = ""[$keywordName]$name"" - if ($MyTypeName -and $keywordName -ne $MyTypeName -and $InstanceName) - { - $resourceId += ""::$complexResourceQualifier"" - } - - Write-Debug ""${debugPrefix} ResourceID = $resourceId"" - -# copy the meta-properties - $canonicalizedValue['ResourceID'] = $resourceId - $canonicalizedValue['SourceInfo'] = $SourceMetadata - if(-not $IsMetaConfig) - { - $canonicalizedValue['ModuleName'] = $keywordData.ImplementingModule - $canonicalizedValue['ModuleVersion'] = $keywordData.ImplementingModuleVersion -as [string] - } - -# see if there is already a resource with this ID. - if (Test-NodeResources $resourceId) - { - Update-ConfigurationErrorCount - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::DuplicateResourceIdInNodeStatementErrorRecord($resourceId, (Get-PSCurrentConfigurationNode))) - } - else - { -# If there are prerequisite resources, validate that the references are well-formed strings -# This routine also adds the resource to the global node resources table. - Test-DependsOn - -# Check if PsDscRunCredential is being specified as Arguments to Configuration - if($null -ne $PsDscRunAsCredential) - { -# Check if resource is also trying to set the value for RunAsCred -# In that case we will generate error during compilation, this is merge error - if($null -ne $value['PsDscRunAsCredential']) - { - Update-ConfigurationErrorCount - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::PsDscRunAsCredentialMergeErrorForCompositeResources($resourceId)) - } -# Set the Value of RunAsCred to that of outer configuration - else - { - $value['PsDscRunAsCredential'] = $PsDscRunAsCredential - } - } - -# Save the resource id in a per-node dictionary to do cross validation at the end - if($keywordData.ImplementingModule -ieq ""PSDesiredStateConfigurationEngine"") - { -#$keywordName is PartialConfiguration - if($keywordName -eq 'PartialConfiguration') - { -# RefreshMode is 'Pull' and .ConfigurationSource is empty - if($value['RefreshMode'] -eq 'Pull' -and -not $value['ConfigurationSource']) - { - Update-ConfigurationErrorCount - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::GetPullModeNeedConfigurationSource($resourceId)) - } - -# Verify that RefreshMode is not Disabled for Partial configuration - if($value['RefreshMode'] -eq 'Disabled') - { - Update-ConfigurationErrorCount - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::DisabledRefreshModeNotValidForPartialConfig($resourceId)) - } - - if($null -ne $value['ConfigurationSource']) - { - Set-NodeManager $resourceId $value['ConfigurationSource'] - } - - if($null -ne $value['ResourceModuleSource']) - { - Set-NodeResourceSource $resourceId $value['ResourceModuleSource'] - } - } - - if($null -ne $value['ExclusiveResources']) - { -# make sure the references are well-formed - foreach ($ExclusiveResource in $value['ExclusiveResources']) { - if (($ExclusiveResource -notmatch '^[a-z][a-z_0-9]*\\[a-z][a-z_0-9]*$') -and ($ExclusiveResource -notmatch '^[a-z][a-z_0-9]*$') -and ($ExclusiveResource -notmatch '^[a-z][a-z_0-9]*\\\*$')) - { - Update-ConfigurationErrorCount - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::GetBadlyFormedExclusiveResourceIdErrorRecord($ExclusiveResource, $resourceId)) - } - } - -# Save the resource id in a per-node dictionary to do cross validation at the end -# Validate resource exist -# Also update the resource reference from module\friendlyname to module\name - $value['ExclusiveResources'] = @(Set-NodeExclusiveResources $resourceId @( $value['ExclusiveResources'] )) - } - } - } - } - else - { - Write-Debug ""${debugPrefix} TYPE IS NOT AS DSC RESOURCE"" - } - -# -# Copy the user-supplied values into a new collection with canonicalized property names -# - foreach ($key in $keywordData.Properties.Keys) - { - Write-Debug ""${debugPrefix} Processing property '$key' ["" - - if ($value.Contains($key)) - { - if ($OldMetaConfig -and (-not ($V1MetaConfigPropertyList -contains $key))) - { - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::InvalidLocalConfigurationManagerPropertyErrorRecord($key, ($V1MetaConfigPropertyList -join ', '))) - Update-ConfigurationErrorCount - } -# see if there is a list of allowed values for this property (similar to an enum) - $allowedValues = $keywordData.Properties[$key].Values -# If there is and user-provided value is not in that list, write an error. - if ($allowedValues) - { - if(($null -eq $value[$key]) -and ($allowedValues -notcontains $value[$key])) - { - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::InvalidValueForPropertyErrorRecord($key, ""$($value[$key])"", $keywordData.Keyword, ($allowedValues -join ', '))) - Update-ConfigurationErrorCount - } - else - { - $notAllowedValue=$null - foreach($v in $value[$key]) - { - if($allowedValues -notcontains $v) - { - $notAllowedValue +=$v.ToString() + ', ' - } - } - - if($notAllowedValue) - { - $notAllowedValue = $notAllowedValue.Substring(0, $notAllowedValue.Length -2) - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::UnsupportedValueForPropertyErrorRecord($key, $notAllowedValue, $keywordData.Keyword, ($allowedValues -join ', '))) - Update-ConfigurationErrorCount - } - } - } - -# see if a value range is defined for this property - $allowedRange = $keywordData.Properties[$key].Range - if($allowedRange) - { - $castedValue = $value[$key] -as [int] - if((($castedValue -is [int]) -and (($castedValue -lt $keywordData.Properties[$key].Range.Item1) -or ($castedValue -gt $keywordData.Properties[$key].Range.Item2))) -or ($null -eq $castedValue)) - { - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::ValueNotInRangeErrorRecord($key, $keywordName, $value[$key], $keywordData.Properties[$key].Range.Item1, $keywordData.Properties[$key].Range.Item2)) - Update-ConfigurationErrorCount - } - } - - Write-Debug ""${debugPrefix} Canonicalized property '$key' = '$($value[$key])'"" - - if ($keywordData.Properties[$key].IsKey) - { - if($null -eq $value[$key]) - { - $keyValues += ""::__NULL__"" - } - else - { - $keyValues += ""::"" + $value[$key] - } - } - -# see if ValueMap is also defined for this property (actual values) - $allowedValueMap = $keywordData.Properties[$key].ValueMap -#if it is and the ValueMap contains the user-provided value as a key, use the actual value - if ($allowedValueMap -and $allowedValueMap.ContainsKey($value[$key])) - { - $canonicalizedValue[$key] = $allowedValueMap[$value[$key]] - } - else - { - $canonicalizedValue[$key] = $value[$key] - } - } - elseif ($keywordData.Properties[$key].Mandatory) - { -# If the property was mandatory but the user didn't provide a value, write and error. - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::MissingValueForMandatoryPropertyErrorRecord($keywordData.Keyword, $keywordData.Properties[$key].TypeConstraint, $Key)) - Update-ConfigurationErrorCount - } - - Write-Debug ""${debugPrefix} Processing completed '$key' ]"" - } - - if($keyValues) - { - $keyValues = $keyValues.Substring(2) # Remove the leading '::' - Add-NodeKeys $keyValues $keywordName - Test-ConflictingResources $keywordName $canonicalizedValue $keywordData - } - -# update OMI_ConfigurationDocument - if($IsMetaConfig) - { - if($keywordData.ResourceName -eq 'OMI_ConfigurationDocument') - { - if($(Get-PSMetaConfigurationProcessed)) - { - $PSMetaConfigDocumentInstVersionInfo = Get-PSMetaConfigDocumentInstVersionInfo - $canonicalizedValue['MinimumCompatibleVersion']=$PSMetaConfigDocumentInstVersionInfo['MinimumCompatibleVersion'] - } - else - { - Set-PSMetaConfigDocInsProcessedBeforeMeta - $canonicalizedValue['MinimumCompatibleVersion']='1.0.0' - } - } - - if(($keywordData.ResourceName -eq 'MSFT_WebDownloadManager') ` - -or ($keywordData.ResourceName -eq 'MSFT_FileDownloadManager') ` - -or ($keywordData.ResourceName -eq 'MSFT_WebResourceManager') ` - -or ($keywordData.ResourceName -eq 'MSFT_FileResourceManager') ` - -or ($keywordData.ResourceName -eq 'MSFT_WebReportManager') ` - -or ($keywordData.ResourceName -eq 'MSFT_SignatureValidation') ` - -or ($keywordData.ResourceName -eq 'MSFT_PartialConfiguration')) - { - Set-PSMetaConfigVersionInfoV2 - } - } - elseif($keywordData.ResourceName -eq 'OMI_ConfigurationDocument') - { - $canonicalizedValue['MinimumCompatibleVersion']='1.0.0' - $canonicalizedValue['CompatibleVersionAdditionalProperties']=@('Omi_BaseResource:ConfigurationName') - } - - if(($keywordData.ResourceName -eq 'MSFT_DSCMetaConfiguration') -or ($keywordData.ResourceName -eq 'MSFT_DSCMetaConfigurationV2')) - { - if($canonicalizedValue['DebugMode'] -and @($canonicalizedValue['DebugMode']).Length -gt 1) - { -# we only allow one value for debug mode now. - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::DebugModeShouldHaveOneValue()) - Update-ConfigurationErrorCount - } - } - -# Generate the MOF text for this resource instance. -# when generate mof text for OMI_ConfigurationDocument we handle below two cases: -# 1. we will add versioning related property based on meta configuration instance already process -# 2. we update the existing OMI_ConfigurationDocument instance if it already exists when process meta configuration instance - $aliasId = ConvertTo-MOFInstance $keywordName $canonicalizedValue - -# If a OMI_ConfigurationDocument is executed outside of a node statement, it becomes the default -# for all nodes that don't have an explicit OMI_ConfigurationDocument declaration - if ($keywordData.ResourceName -eq 'OMI_ConfigurationDocument' -and -not (Get-PSCurrentConfigurationNode)) - { - $data = Get-MoFInstanceText $aliasId - Write-Debug ""${debugPrefix} DEFINING DEFAULT CONFIGURATION DOCUMENT: $data"" - Set-PSDefaultConfigurationDocument $data - } - - Write-Debug ""${debugPrefix} MOF alias for this resource is '$aliasId'"" - -# always return the aliasId so the generated file will be well-formed if not valid - $aliasId - - Write-Debug ""${debugPrefix} RESOURCE PROCESSING COMPLETED. TOTAL ERROR COUNT: $(Get-ConfigurationErrorCount)"" - - "; - } -} 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 374cadf31d8..0400f99b899 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs @@ -42,27 +42,24 @@ internal static IEnumerable GetFormatData() private static IEnumerable ViewsOf_FileSystemTypes(CustomControl[] sharedControls) { #if UNIX - if (ExperimentalFeature.IsEnabled("PSUnixFileStat")) - { - yield return new FormatViewDefinition("childrenWithUnixStat", - TableControl.Create() - .GroupByProperty("PSParentPath", customControl: sharedControls[0]) - .AddHeader(Alignment.Left, label: "UnixMode", width: 10) - .AddHeader(Alignment.Left, label: "User", width: 16) - .AddHeader(Alignment.Left, label: "Group", width: 16) - .AddHeader(Alignment.Right, label: "LastWriteTime", width: 18) - .AddHeader(Alignment.Right, label: "Size", width: 14) - .AddHeader(Alignment.Left, label: "Name") - .StartRowDefinition(wrap: true) - .AddPropertyColumn("UnixMode") - .AddPropertyColumn("User") - .AddPropertyColumn("Group") - .AddScriptBlockColumn(scriptBlock: @"'{0:d} {0:HH}:{0:mm}' -f $_.LastWriteTime") - .AddPropertyColumn("Size") - .AddPropertyColumn("NameString") - .EndRowDefinition() - .EndTable()); - } + yield return new FormatViewDefinition("childrenWithUnixStat", + TableControl.Create() + .GroupByProperty("PSParentPath", customControl: sharedControls[0]) + .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: "Size", width: 12) + .AddHeader(Alignment.Left, label: "Name") + .StartRowDefinition(wrap: true) + .AddPropertyColumn("UnixMode") + .AddPropertyColumn("User") + .AddPropertyColumn("Group") + .AddScriptBlockColumn(scriptBlock: @"'{0:d} {0:HH}:{0:mm}' -f $_.LastWriteTime") + .AddPropertyColumn("Size") + .AddPropertyColumn("NameString") + .EndRowDefinition() + .EndTable()); #endif yield return new FormatViewDefinition("children", diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/HelpV3_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/HelpV3_format_ps1xml.cs index d04ea1bdedf..fc5ddc5ab9a 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/HelpV3_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/HelpV3_format_ps1xml.cs @@ -175,7 +175,7 @@ internal static IEnumerable GetFormatData() .EndControl(); var sharedControls = new CustomControl[] { - null,//MamlParameterValueGroupControl, + null, //MamlParameterValueGroupControl, MamlParameterControl, MamlTypeControl, MamlParameterValueControl, 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 d18de013397..05f57286026 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Help_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Help_format_ps1xml.cs @@ -350,7 +350,7 @@ internal static IEnumerable GetFormatData() .AddNewline() .AddText(HelpDisplayStrings.ParameterPosition) .AddScriptBlockExpressionBinding(@" ", selectedByScript: @"($_.position -eq $()) -or ($_.position -eq '')", customControl: control7) - .AddScriptBlockExpressionBinding(@"$_.position", selectedByScript: "$_.position -ne $()") + .AddScriptBlockExpressionBinding(@"$_.position", selectedByScript: "$_.position -ne $()") .AddNewline() .AddText(HelpDisplayStrings.ParameterDefaultValue) .AddPropertyExpressionBinding(@"defaultValue") @@ -358,6 +358,9 @@ internal static IEnumerable GetFormatData() .AddText(HelpDisplayStrings.AcceptsPipelineInput) .AddPropertyExpressionBinding(@"pipelineInput") .AddNewline() + .AddText(HelpDisplayStrings.ParameterAliases) + .AddPropertyExpressionBinding(@"aliases") + .AddNewline() .AddText(HelpDisplayStrings.AcceptsWildCardCharacters) .AddPropertyExpressionBinding(@"globbing", customControl: MamlTrueFalseShortControl) .AddNewline() 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 b29fe0bb5de..ef0d506d122 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs @@ -137,6 +137,10 @@ internal static IEnumerable GetFormatData() "System.Management.Automation.Subsystem.SubsystemInfo", ViewsOf_System_Management_Automation_Subsystem_SubsystemInfo()); + yield return new ExtendedTypeDefinition( + "System.Management.Automation.Subsystem.SubsystemInfo+ImplementationInfo", + ViewsOf_System_Management_Automation_Subsystem_SubsystemInfo_ImplementationInfo()); + yield return new ExtendedTypeDefinition( "System.Management.Automation.ShellVariable", ViewsOf_System_Management_Automation_ShellVariable()); @@ -248,6 +252,10 @@ internal static IEnumerable GetFormatData() "Microsoft.PowerShell.MarkdownRender.PSMarkdownOptionInfo", ViewsOf_Microsoft_PowerShell_MarkdownRender_MarkdownOptionInfo()); + yield return new ExtendedTypeDefinition( + "Microsoft.PowerShell.Commands.TestConnectionCommand+TcpPortStatus", + ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_TcpPortStatus()); + yield return new ExtendedTypeDefinition( "Microsoft.PowerShell.Commands.TestConnectionCommand+PingStatus", ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_PingStatus()); @@ -276,6 +284,10 @@ internal static IEnumerable GetFormatData() "System.Management.Automation.PSStyle+ProgressConfiguration", ViewsOf_System_Management_Automation_PSStyleProgressConfiguration()); + yield return new ExtendedTypeDefinition( + "System.Management.Automation.PSStyle+FileInfoFormatting", + ViewsOf_System_Management_Automation_PSStyleFileInfoFormat()); + yield return new ExtendedTypeDefinition( "System.Management.Automation.PSStyle+ForegroundColor", ViewsOf_System_Management_Automation_PSStyleForegroundColor()); @@ -770,6 +782,21 @@ private static IEnumerable ViewsOf_System_Management_Autom .EndTable()); } + private static IEnumerable ViewsOf_System_Management_Automation_Subsystem_SubsystemInfo_ImplementationInfo() + { + yield return new FormatViewDefinition( + "System.Management.Automation.Subsystem.SubsystemInfo+ImplementationInfo", + ListControl.Create() + .StartEntry() + .AddItemProperty(@"Id") + .AddItemProperty(@"Kind") + .AddItemProperty(@"Name") + .AddItemProperty(@"Description") + .AddItemProperty(@"ImplementationType") + .EndEntry() + .EndList()); + } + private static IEnumerable ViewsOf_System_Management_Automation_ShellVariable() { yield return new FormatViewDefinition("ShellVariable", @@ -806,29 +833,19 @@ private static IEnumerable ViewsOf_System_Management_Autom $maxDepth = 10 $ellipsis = ""`u{2026}"" $resetColor = '' - if ($Host.UI.SupportsVirtualTerminal -and ([string]::IsNullOrEmpty($env:__SuppressAnsiEscapeSequences))) { - $resetColor = [System.Management.Automation.VTUtility]::GetEscapeSequence( - [System.Management.Automation.VTUtility+VT]::Reset - ) - } - - function Get-VT100Color([ConsoleColor] $color) { - if (!$Host.UI.SupportsVirtualTerminal -or !([string]::IsNullOrEmpty($env:__SuppressAnsiEscapeSequences))) { - return '' - } + $errorColor = '' + $accentColor = '' - return [System.Management.Automation.VTUtility]::GetEscapeSequence($color) + if ($Host.UI.SupportsVirtualTerminal -and ([string]::IsNullOrEmpty($env:__SuppressAnsiEscapeSequences))) { + $resetColor = $PSStyle.Reset + $errorColor = $psstyle.Formatting.Error + $accentColor = $PSStyle.Formatting.FormatAccent } function Show-ErrorRecord($obj, [int]$indent = 0, [int]$depth = 1) { $newline = [Environment]::Newline $output = [System.Text.StringBuilder]::new() $prefix = ' ' * $indent - $accentColor = '' - - if ($null -ne $Host.PrivateData) { - $accentColor = Get-VT100Color ($Host.PrivateData.FormatAccentColor ?? $Host.PrivateData.ErrorForegroundColor) - } $expandTypes = @( 'Microsoft.Rest.HttpRequestMessageWrapper' @@ -901,9 +918,9 @@ private static IEnumerable ViewsOf_System_Management_Autom $null = $output.Append($prop.Value) } # Dictionary and Hashtable we want to show as Key/Value pairs, we don't do the extra whitespace alignment here - elseif ($prop.Value.GetType().Name.StartsWith('Dictionary') -or $prop.Value.GetType().Name -eq 'Hashtable') { + elseif ($prop.Value -is [System.Collections.IDictionary]) { $isFirstElement = $true - foreach ($key in $prop.Value.Keys) { + foreach ($key in ($prop.Value.Keys | Sort-Object)) { if ($isFirstElement) { $null = $output.Append($newline) } @@ -929,20 +946,42 @@ private static IEnumerable ViewsOf_System_Management_Autom $isFirstElement = $true foreach ($value in $prop.Value) { $null = $output.Append($newline) - if (!$isFirstElement) { - $null = $output.Append($newline) + $valueIndent = ' ' * ($newIndent + 2) + + if ($value -is [Type]) { + # Just show the typename instead of it as an object + $null = $output.Append(""${prefix}${valueIndent}[$($value.ToString())]"") + } + elseif ($value -is [string] -or $value.GetType().IsPrimitive) { + $null = $output.Append(""${prefix}${valueIndent}${value}"") + } + else { + if (!$isFirstElement) { + $null = $output.Append($newline) + } + $null = $output.Append((Show-ErrorRecord $value $newIndent ($depth + 1))) } - $null = $output.Append((Show-ErrorRecord $value $newIndent ($depth + 1))) $isFirstElement = $false } } } + elseif ($prop.Value -is [Type]) { + # Just show the typename instead of it as an object + $null = $output.Append(""[$($prop.Value.ToString())]"") + } # Anything else, we convert to string. # ToString() can throw so we use LanguagePrimitives.TryConvertTo() to hide a convert error else { $value = $null if ([System.Management.Automation.LanguagePrimitives]::TryConvertTo($prop.Value, [string], [ref]$value) -and $value -ne $null) { + if ($prop.Name -eq 'PositionMessage') { + $value = $value.Insert($value.IndexOf('~'), $errorColor) + } + elseif ($prop.Name -eq 'Message') { + $value = $errorColor + $value + } + $isFirstLine = $true if ($value.Contains($newline)) { # the 3 is to account for ' : ' @@ -995,12 +1034,18 @@ private static IEnumerable ViewsOf_System_Management_Autom yield return new FormatViewDefinition("ErrorInstance", CustomControl.Create(outOfBand: true) .StartEntry() - .AddScriptBlockExpressionBinding(@" - if (@('NativeCommandErrorMessage','NativeCommandError') -notcontains $_.FullyQualifiedErrorId -and @('CategoryView','ConciseView') -notcontains $ErrorView) + .AddScriptBlockExpressionBinding( + """ + $errorColor = '' + $commandPrefix = '' + if (@('NativeCommandErrorMessage','NativeCommandError') -notcontains $_.FullyQualifiedErrorId -and @('CategoryView','ConciseView','DetailedView') -notcontains $ErrorView) { $myinv = $_.InvocationInfo - if ($myinv -and $myinv.MyCommand) - { + if ($Host.UI.SupportsVirtualTerminal) { + $errorColor = $PSStyle.Formatting.Error + } + + $commandPrefix = if ($myinv -and $myinv.MyCommand) { switch -regex ( $myinv.MyCommand.CommandType ) { ([System.Management.Automation.CommandTypes]::ExternalScript) @@ -1045,41 +1090,27 @@ private static IEnumerable ViewsOf_System_Management_Autom $myinv.InvocationName + ' : ' } } - ") - .AddScriptBlockExpressionBinding(@" + + $errorColor + $commandPrefix + """) + .AddScriptBlockExpressionBinding( + """ Set-StrictMode -Off + $ErrorActionPreference = 'Stop' + trap { 'Error found in error view definition: ' + $_.Exception.Message } $newline = [Environment]::Newline - function Get-ConciseViewPositionMessage { - - $resetColor = '' - if ($Host.UI.SupportsVirtualTerminal -and ([string]::IsNullOrEmpty($env:__SuppressAnsiEscapeSequences))) { - $resetColor = [System.Management.Automation.VTUtility]::GetEscapeSequence( - [System.Management.Automation.VTUtility+VT]::Reset - ) - } - - function Get-VT100Color([ConsoleColor] $color) { - if (!$Host.UI.SupportsVirtualTerminal -or !([string]::IsNullOrEmpty($env:__SuppressAnsiEscapeSequences))) { - return '' - } - - return [System.Management.Automation.VTUtility]::GetEscapeSequence($color) - } - - # return length of string sans VT100 codes - function Get-RawStringLength($string) { - $vtCodes = ""`e[0m"", ""`e[2;30m"", ""`e[2;31m"", ""`e[2;32m"", ""`e[2;33m"", ""`e[2;34m"", - ""`e[2;35m"", ""`e[2;36m"", ""`e[2;37m"", ""`e[1;30m"", ""`e[1;31m"", ""`e[1;32m"", - ""`e[1;33m"", ""`e[1;34m"", ""`e[1;35m"", ""`e[1;36m"", ""`e[1;37m"" + $resetColor = '' + $errorColor = '' + $accentColor = '' - $newString = $string - foreach ($vtCode in $vtCodes) { - $newString = $newString.Replace($vtCode, '') - } + if ($Host.UI.SupportsVirtualTerminal -and ([string]::IsNullOrEmpty($env:__SuppressAnsiEscapeSequences))) { + $resetColor = $PSStyle.Reset + $errorColor = $PSStyle.Formatting.Error + $accentColor = $PSStyle.Formatting.ErrorAccent + } - return $newString.Length - } + function Get-ConciseViewPositionMessage { # returns a string cut to last whitespace function Get-TruncatedString($string, [int]$length) { @@ -1091,45 +1122,53 @@ function Get-ConciseViewPositionMessage { return ($string.Substring(0,$length) -split '\s',-2)[0] } - $errorColor = '' - $accentColor = '' - - if ($null -ne $Host.PrivateData) { - $errorColor = Get-VT100Color $Host.PrivateData.ErrorForegroundColor - $accentColor = Get-VT100Color ($Host.PrivateData.ErrorAccentColor ?? $errorColor) - } - $posmsg = '' $headerWhitespace = '' $offsetWhitespace = '' $message = '' $prefix = '' - # Don't show line information if script module - if (($myinv -and $myinv.ScriptName -or $myinv.ScriptLineNumber -gt 1 -or $err.CategoryInfo.Category -eq 'ParserError') -and !($myinv.ScriptName.EndsWith('.psm1', [System.StringComparison]::OrdinalIgnoreCase))) { - $useTargetObject = $false + # Handle case where there is a TargetObject from a Pester `Should` assertion failure and we can show the error at the target rather than the script source + # Note that in some versions, this is a Dictionary<,> and in others it's a hashtable. So we explicitly cast to a shared interface in the method invocation + # to force using `IDictionary.Contains`. Hashtable does have it's own `ContainKeys` as well, but if they ever opt to use a custom `IDictionary`, that may not. + $useTargetObject = $null -ne $err.TargetObject -and + $err.TargetObject -is [System.Collections.IDictionary] -and + ([System.Collections.IDictionary]$err.TargetObject).Contains('Line') -and + ([System.Collections.IDictionary]$err.TargetObject).Contains('LineText') + + # The checks here determine if we show line detailed error information: + # - check if `ParserError` and comes from PowerShell which eventually results in a ParseException, but during this execution it's an ErrorRecord + $isParseError = $err.CategoryInfo.Category -eq 'ParserError' -and + $err.Exception -is [System.Management.Automation.ParentContainsErrorRecordException] + + # - check if invocation is a script or multiple lines in the console + $isMultiLineOrExternal = $myinv.ScriptName -or $myinv.ScriptLineNumber -gt 1 - # Handle case where there is a TargetObject and we can show the error at the target rather than the script source - if ($_.TargetObject.Line -and $_.TargetObject.LineText) { - $posmsg = ""${resetcolor}$($_.TargetObject.File)${newline}"" - $useTargetObject = $true + # - check that it's not a script module as expectation is that users don't want to see the line of error within a module + $shouldShowLineDetail = ($isParseError -or $isMultiLineOrExternal) -and + $myinv.ScriptName -notmatch '\.psm1$' + + if ($useTargetObject -or $shouldShowLineDetail) { + + if ($useTargetObject) { + $posmsg = "${resetcolor}$($err.TargetObject.File)${newline}" } elseif ($myinv.ScriptName) { if ($env:TERM_PROGRAM -eq 'vscode') { # If we are running in vscode, we know the file:line:col links are clickable so we use this format - $posmsg = ""${resetcolor}$($myinv.ScriptName):$($myinv.ScriptLineNumber):$($myinv.OffsetInLine)${newline}"" + $posmsg = "${resetcolor}$($myinv.ScriptName):$($myinv.ScriptLineNumber):$($myinv.OffsetInLine)${newline}" } else { - $posmsg = ""${resetcolor}$($myinv.ScriptName):$($myinv.ScriptLineNumber)${newline}"" + $posmsg = "${resetcolor}$($myinv.ScriptName):$($myinv.ScriptLineNumber)${newline}" } } else { - $posmsg = ""${newline}"" + $posmsg = "${newline}" } if ($useTargetObject) { - $scriptLineNumber = $_.TargetObject.Line - $scriptLineNumberLength = $_.TargetObject.Line.ToString().Length + $scriptLineNumber = $err.TargetObject.Line + $scriptLineNumberLength = $err.TargetObject.Line.ToString().Length } else { $scriptLineNumber = $myinv.ScriptLineNumber @@ -1146,7 +1185,7 @@ function Get-ConciseViewPositionMessage { } $verticalBar = '|' - $posmsg += ""${accentColor}${headerWhitespace}Line ${verticalBar}${newline}"" + $posmsg += "${accentColor}${headerWhitespace}Line ${verticalBar}${newline}" $highlightLine = '' if ($useTargetObject) { @@ -1171,19 +1210,19 @@ function Get-ConciseViewPositionMessage { $line = $line.Insert($offsetInLine + $offsetLength, $resetColor).Insert($offsetInLine, $accentColor) } - $posmsg += ""${accentColor}${lineWhitespace}${ScriptLineNumber} ${verticalBar} ${resetcolor}${line}"" + $posmsg += "${accentColor}${lineWhitespace}${ScriptLineNumber} ${verticalBar} ${resetcolor}${line}" $offsetWhitespace = ' ' * $offsetInLine - $prefix = ""${accentColor}${headerWhitespace} ${verticalBar} ${errorColor}"" + $prefix = "${accentColor}${headerWhitespace} ${verticalBar} ${errorColor}" if ($highlightLine -ne '') { - $posMsg += ""${prefix}${highlightLine}${newline}"" + $posMsg += "${prefix}${highlightLine}${newline}" } - $message = ""${prefix}"" + $message = "${prefix}" } if (! $err.ErrorDetails -or ! $err.ErrorDetails.Message) { - if ($err.CategoryInfo.Category -eq 'ParserError' -and $err.Exception.Message.Contains(""~$newline"")) { + if ($err.CategoryInfo.Category -eq 'ParserError' -and $err.Exception.Message.Contains("~$newline")) { # need to parse out the relevant part of the pre-rendered positionmessage - $message += $err.Exception.Message.split(""~$newline"")[1].split(""${newline}${newline}"")[0] + $message += $err.Exception.Message.split("~$newline")[1].split("${newline}${newline}")[0] } elseif ($err.Exception) { $message += $err.Exception.Message @@ -1201,11 +1240,11 @@ function Get-ConciseViewPositionMessage { # if rendering line information, break up the message if it's wider than the console if ($myinv -and $myinv.ScriptName -or $err.CategoryInfo.Category -eq 'ParserError') { - $prefixLength = Get-RawStringLength -string $prefix + $prefixLength = [System.Management.Automation.Internal.StringDecorated]::new($prefix).ContentLength $prefixVtLength = $prefix.Length - $prefixLength # replace newlines in message so it lines up correct - $message = $message.Replace($newline, ' ').Replace(""`n"", ' ').Replace(""`t"", ' ') + $message = $message.Replace($newline, ' ').Replace("`n", ' ').Replace("`t", ' ') $windowWidth = 120 if ($Host.UI.RawUI -ne $null) { @@ -1240,7 +1279,7 @@ function Get-ConciseViewPositionMessage { $message += $newline } - $posmsg += ""${errorColor}"" + $message + $posmsg += "${errorColor}" + $message $reason = 'Error' if ($err.Exception -and $err.Exception.WasThrownFromThrowStatement) { @@ -1252,8 +1291,8 @@ function Get-ConciseViewPositionMessage { $reason = $myinv.MyCommand } # If it's a scriptblock, better to show the command in the scriptblock that had the error - elseif ($_.CategoryInfo.Activity) { - $reason = $_.CategoryInfo.Activity + elseif ($err.CategoryInfo.Activity) { + $reason = $err.CategoryInfo.Activity } elseif ($myinv.MyCommand) { $reason = $myinv.MyCommand @@ -1270,7 +1309,7 @@ function Get-ConciseViewPositionMessage { $errorMsg = 'Error' - ""${errorColor}${reason}: ${posmsg}${resetcolor}"" + "${errorColor}${reason}: ${posmsg}${resetcolor}" } $myinv = $_.InvocationInfo @@ -1281,69 +1320,84 @@ function Get-ConciseViewPositionMessage { } if ($err.FullyQualifiedErrorId -eq 'NativeCommandErrorMessage' -or $err.FullyQualifiedErrorId -eq 'NativeCommandError') { - $err.Exception.Message + return "${errorColor}$($err.Exception.Message)${resetcolor}" } - else - { - $myinv = $err.InvocationInfo - if ($ErrorView -eq 'ConciseView') { - $posmsg = Get-ConciseViewPositionMessage - } - elseif ($myinv -and ($myinv.MyCommand -or ($err.CategoryInfo.Category -ne 'ParserError'))) { - $posmsg = $myinv.PositionMessage - } else { - $posmsg = '' - } - if ($posmsg -ne '') - { + if ($ErrorView -eq 'DetailedView') { + $message = Get-Error | Out-String + return "${errorColor}${message}${resetcolor}" + } + + if ($ErrorView -eq 'CategoryView') { + $message = $err.CategoryInfo.GetMessage() + return "${errorColor}${message}${resetcolor}" + } + + $posmsg = '' + if ($ErrorView -eq 'ConciseView') { + $posmsg = Get-ConciseViewPositionMessage + } + elseif ($myinv -and ($myinv.MyCommand -or ($err.CategoryInfo.Category -ne 'ParserError'))) { + $posmsg = $myinv.PositionMessage + if ($posmsg -ne '') { $posmsg = $newline + $posmsg } + } - if ($err.PSMessageDetails) { - $posmsg = ' : ' + $err.PSMessageDetails + $posmsg + if ($err.PSMessageDetails) { + $posmsg = ' : ' + $err.PSMessageDetails + $posmsg + } + + if ($ErrorView -eq 'ConciseView') { + $recommendedAction = $_.ErrorDetails.RecommendedAction + if (-not [String]::IsNullOrWhiteSpace($recommendedAction)) { + $recommendedAction = $newline + + ${errorColor} + + ' Recommendation: ' + + $recommendedAction + + ${resetcolor} } - if ($ErrorView -eq 'ConciseView') { - return $posmsg + if ($err.PSMessageDetails) { + $posmsg = "${errorColor}${posmsg}" } + return $posmsg + $recommendedAction + } - $indent = 4 + $indent = 4 - $errorCategoryMsg = $err.ErrorCategory_Message + $errorCategoryMsg = $err.ErrorCategory_Message - if ($null -ne $errorCategoryMsg) - { - $indentString = '+ CategoryInfo : ' + $err.ErrorCategory_Message - } - else - { - $indentString = '+ CategoryInfo : ' + $err.CategoryInfo - } + if ($null -ne $errorCategoryMsg) + { + $indentString = '+ CategoryInfo : ' + $err.ErrorCategory_Message + } + else + { + $indentString = '+ CategoryInfo : ' + $err.CategoryInfo + } - $posmsg += $newline + $indentString + $posmsg += $newline + $indentString - $indentString = ""+ FullyQualifiedErrorId : "" + $err.FullyQualifiedErrorId - $posmsg += $newline + $indentString + $indentString = "+ FullyQualifiedErrorId : " + $err.FullyQualifiedErrorId + $posmsg += $newline + $indentString - $originInfo = $err.OriginInfo + $originInfo = $err.OriginInfo - if (($null -ne $originInfo) -and ($null -ne $originInfo.PSComputerName)) - { - $indentString = ""+ PSComputerName : "" + $originInfo.PSComputerName - $posmsg += $newline + $indentString - } + if (($null -ne $originInfo) -and ($null -ne $originInfo.PSComputerName)) + { + $indentString = "+ PSComputerName : " + $originInfo.PSComputerName + $posmsg += $newline + $indentString + } - if ($ErrorView -eq 'CategoryView') { - $err.CategoryInfo.GetMessage() - } - elseif (! $err.ErrorDetails -or ! $err.ErrorDetails.Message) { - $err.Exception.Message + $posmsg - } else { - $err.ErrorDetails.Message + $posmsg - } + $finalMsg = if ($err.ErrorDetails.Message) { + $err.ErrorDetails.Message + $posmsg + } else { + $err.Exception.Message + $posmsg } - ") + + "${errorColor}${finalMsg}${resetcolor}" + """) .EndEntry() .EndControl()); } @@ -1905,6 +1959,31 @@ private static IEnumerable ViewsOf_Microsoft_PowerShell_Ma .EndList()); } + private static IEnumerable ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_TcpPortStatus() + { + yield return new FormatViewDefinition( + "Microsoft.PowerShell.Commands.TestConnectionCommand+TcpPortStatus", + TableControl.Create() + .AddHeader(Alignment.Right, label: "Id", width: 4) + .AddHeader(Alignment.Left, label: "Source", width: 16) + .AddHeader(Alignment.Left, label: "Address", width: 25) + .AddHeader(Alignment.Right, label: "Port", width: 7) + .AddHeader(Alignment.Right, label: "Latency(ms)", width: 7) + .AddHeader(Alignment.Left, label: "Connected", width: 10) + .AddHeader(Alignment.Left, label: "Status", width: 24) + .StartRowDefinition() + .AddPropertyColumn("Id") + .AddPropertyColumn("Source") + .AddPropertyColumn("TargetAddress") + .AddPropertyColumn("Port") + .AddPropertyColumn("Latency") + .AddPropertyColumn("Connected") + .AddPropertyColumn("Status") + .EndRowDefinition() + .GroupByProperty("Target") + .EndTable()); + } + private static IEnumerable ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_PingStatus() { yield return new FormatViewDefinition( @@ -2036,6 +2115,8 @@ private static IEnumerable ViewsOf_System_Management_Autom .AddItemScriptBlock(@"""$($_.Blink)$($_.Blink.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "Blink") .AddItemScriptBlock(@"""$($_.BoldOff)$($_.BoldOff.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "BoldOff") .AddItemScriptBlock(@"""$($_.Bold)$($_.Bold.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "Bold") + .AddItemScriptBlock(@"""$($_.DimOff)$($_.DimOff.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "DimOff") + .AddItemScriptBlock(@"""$($_.Dim)$($_.Dim.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "Dim") .AddItemScriptBlock(@"""$($_.Hidden)$($_.Hidden.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "Hidden") .AddItemScriptBlock(@"""$($_.HiddenOff)$($_.HiddenOff.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "HiddenOff") .AddItemScriptBlock(@"""$($_.Reverse)$($_.Reverse.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "Reverse") @@ -2053,42 +2134,51 @@ private static IEnumerable ViewsOf_System_Management_Autom .AddItemScriptBlock(@"""$($_.Formatting.Warning)$($_.Formatting.Warning.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.Warning") .AddItemScriptBlock(@"""$($_.Formatting.Verbose)$($_.Formatting.Verbose.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.Verbose") .AddItemScriptBlock(@"""$($_.Formatting.Debug)$($_.Formatting.Debug.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.Debug") + .AddItemScriptBlock(@"""$($_.Formatting.TableHeader)$($_.Formatting.TableHeader.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.TableHeader") + .AddItemScriptBlock(@"""$($_.Formatting.CustomTableHeaderLabel)$($_.Formatting.CustomTableHeaderLabel.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.CustomTableHeaderLabel") + .AddItemScriptBlock(@"""$($_.Formatting.FeedbackName)$($_.Formatting.FeedbackName.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.FeedbackName") + .AddItemScriptBlock(@"""$($_.Formatting.FeedbackText)$($_.Formatting.FeedbackText.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.FeedbackText") + .AddItemScriptBlock(@"""$($_.Formatting.FeedbackAction)$($_.Formatting.FeedbackAction.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.FeedbackAction") .AddItemScriptBlock(@"""$($_.Progress.Style)$($_.Progress.Style.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Progress.Style") .AddItemScriptBlock(@"""$($_.Progress.MaxWidth)""", label: "Progress.MaxWidth") .AddItemScriptBlock(@"""$($_.Progress.View)""", label: "Progress.View") .AddItemScriptBlock(@"""$($_.Progress.UseOSCIndicator)""", label: "Progress.UseOSCIndicator") + .AddItemScriptBlock(@"""$($_.FileInfo.Directory)$($_.FileInfo.Directory.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "FileInfo.Directory") + .AddItemScriptBlock(@"""$($_.FileInfo.SymbolicLink)$($_.FileInfo.SymbolicLink.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "FileInfo.SymbolicLink") + .AddItemScriptBlock(@"""$($_.FileInfo.Executable)$($_.FileInfo.Executable.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "FileInfo.Executable") + .AddItemScriptBlock(@"""$([string]::Join(',',$_.FileInfo.Extension.Keys))""", label: "FileInfo.Extension") .AddItemScriptBlock(@"""$($_.Foreground.Black)$($_.Foreground.Black.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.Black") + .AddItemScriptBlock(@"""$($_.Foreground.BrightBlack)$($_.Foreground.BrightBlack.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.BrightBlack") .AddItemScriptBlock(@"""$($_.Foreground.White)$($_.Foreground.White.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.White") - .AddItemScriptBlock(@"""$($_.Foreground.DarkGray)$($_.Foreground.DarkGray.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.DarkGray") - .AddItemScriptBlock(@"""$($_.Foreground.LightGray)$($_.Foreground.LightGray.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.LightGray") + .AddItemScriptBlock(@"""$($_.Foreground.BrightWhite)$($_.Foreground.BrightWhite.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.BrightWhite") .AddItemScriptBlock(@"""$($_.Foreground.Red)$($_.Foreground.Red.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.Red") - .AddItemScriptBlock(@"""$($_.Foreground.LightRed)$($_.Foreground.LightRed.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.LightRed") + .AddItemScriptBlock(@"""$($_.Foreground.BrightRed)$($_.Foreground.BrightRed.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.BrightRed") .AddItemScriptBlock(@"""$($_.Foreground.Magenta)$($_.Foreground.Magenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.Magenta") - .AddItemScriptBlock(@"""$($_.Foreground.LightMagenta)$($_.Foreground.LightMagenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.LightMagenta") + .AddItemScriptBlock(@"""$($_.Foreground.BrightMagenta)$($_.Foreground.BrightMagenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.BrightMagenta") .AddItemScriptBlock(@"""$($_.Foreground.Blue)$($_.Foreground.Blue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.Blue") - .AddItemScriptBlock(@"""$($_.Foreground.LightBlue)$($_.Foreground.LightBlue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.LightBlue") + .AddItemScriptBlock(@"""$($_.Foreground.BrightBlue)$($_.Foreground.BrightBlue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.BrightBlue") .AddItemScriptBlock(@"""$($_.Foreground.Cyan)$($_.Foreground.Cyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.Cyan") - .AddItemScriptBlock(@"""$($_.Foreground.LightCyan)$($_.Foreground.LightCyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.LightCyan") + .AddItemScriptBlock(@"""$($_.Foreground.BrightCyan)$($_.Foreground.BrightCyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.BrightCyan") .AddItemScriptBlock(@"""$($_.Foreground.Green)$($_.Foreground.Green.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.Green") - .AddItemScriptBlock(@"""$($_.Foreground.LightGreen)$($_.Foreground.LightGreen.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.LightGreen") + .AddItemScriptBlock(@"""$($_.Foreground.BrightGreen)$($_.Foreground.BrightGreen.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.BrightGreen") .AddItemScriptBlock(@"""$($_.Foreground.Yellow)$($_.Foreground.Yellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.Yellow") - .AddItemScriptBlock(@"""$($_.Foreground.LightYellow)$($_.Foreground.LightYellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.LightYellow") + .AddItemScriptBlock(@"""$($_.Foreground.BrightYellow)$($_.Foreground.BrightYellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.BrightYellow") .AddItemScriptBlock(@"""$($_.Background.Black)$($_.Background.Black.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.Black") + .AddItemScriptBlock(@"""$($_.Background.BrightBlack)$($_.Background.BrightBlack.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.BrightBlack") .AddItemScriptBlock(@"""$($_.Background.White)$($_.Background.White.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.White") - .AddItemScriptBlock(@"""$($_.Background.DarkGray)$($_.Background.DarkGray.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.DarkGray") - .AddItemScriptBlock(@"""$($_.Background.LightGray)$($_.Background.LightGray.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.LightGray") + .AddItemScriptBlock(@"""$($_.Background.BrightWhite)$($_.Background.BrightWhite.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.BrightWhite") .AddItemScriptBlock(@"""$($_.Background.Red)$($_.Background.Red.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.Red") - .AddItemScriptBlock(@"""$($_.Background.LightRed)$($_.Background.LightRed.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.LightRed") + .AddItemScriptBlock(@"""$($_.Background.BrightRed)$($_.Background.BrightRed.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.BrightRed") .AddItemScriptBlock(@"""$($_.Background.Magenta)$($_.Background.Magenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.Magenta") - .AddItemScriptBlock(@"""$($_.Background.LightMagenta)$($_.Background.LightMagenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.LightMagenta") + .AddItemScriptBlock(@"""$($_.Background.BrightMagenta)$($_.Background.BrightMagenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.BrightMagenta") .AddItemScriptBlock(@"""$($_.Background.Blue)$($_.Background.Blue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.Blue") - .AddItemScriptBlock(@"""$($_.Background.LightBlue)$($_.Background.LightBlue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.LightBlue") + .AddItemScriptBlock(@"""$($_.Background.BrightBlue)$($_.Background.BrightBlue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.BrightBlue") .AddItemScriptBlock(@"""$($_.Background.Cyan)$($_.Background.Cyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.Cyan") - .AddItemScriptBlock(@"""$($_.Background.LightCyan)$($_.Background.LightCyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.LightCyan") + .AddItemScriptBlock(@"""$($_.Background.BrightCyan)$($_.Background.BrightCyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.BrightCyan") .AddItemScriptBlock(@"""$($_.Background.Green)$($_.Background.Green.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.Green") - .AddItemScriptBlock(@"""$($_.Background.LightGreen)$($_.Background.LightGreen.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.LightGreen") + .AddItemScriptBlock(@"""$($_.Background.BrightGreen)$($_.Background.BrightGreen.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.BrightGreen") .AddItemScriptBlock(@"""$($_.Background.Yellow)$($_.Background.Yellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.Yellow") - .AddItemScriptBlock(@"""$($_.Background.LightYellow)$($_.Background.LightYellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.LightYellow") + .AddItemScriptBlock(@"""$($_.Background.BrightYellow)$($_.Background.BrightYellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.BrightYellow") .EndEntry() .EndList()); } @@ -2102,8 +2192,13 @@ private static IEnumerable ViewsOf_System_Management_Autom .AddItemScriptBlock(@"""$($_.ErrorAccent)$($_.ErrorAccent.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "ErrorAccent") .AddItemScriptBlock(@"""$($_.Error)$($_.Error.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Error") .AddItemScriptBlock(@"""$($_.Warning)$($_.Warning.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Warning") - .AddItemScriptBlock(@"""$($_.Verbose)$($_.Verbose.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.Verbose") + .AddItemScriptBlock(@"""$($_.Verbose)$($_.Verbose.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Verbose") .AddItemScriptBlock(@"""$($_.Debug)$($_.Debug.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Debug") + .AddItemScriptBlock(@"""$($_.TableHeader)$($_.TableHeader.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "TableHeader") + .AddItemScriptBlock(@"""$($_.CustomTableHeaderLabel)$($_.CustomTableHeaderLabel.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "CustomTableHeaderLabel") + .AddItemScriptBlock(@"""$($_.FeedbackName)$($_.FeedbackName.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "FeedbackName") + .AddItemScriptBlock(@"""$($_.FeedbackText)$($_.FeedbackText.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "FeedbackText") + .AddItemScriptBlock(@"""$($_.FeedbackAction)$($_.FeedbackAction.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "FeedbackAction") .EndEntry() .EndList()); } @@ -2121,52 +2216,85 @@ private static IEnumerable ViewsOf_System_Management_Autom .EndList()); } + private static IEnumerable ViewsOf_System_Management_Automation_PSStyleFileInfoFormat() + { + yield return new FormatViewDefinition("System.Management.Automation.PSStyle+FileInfoFormatting", + ListControl.Create() + .StartEntry() + .AddItemScriptBlock(@"""$($_.Directory)$($_.Directory.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Directory") + .AddItemScriptBlock(@"""$($_.SymbolicLink)$($_.SymbolicLink.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "SymbolicLink") + .AddItemScriptBlock(@"""$($_.Executable)$($_.Executable.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Executable") + .AddItemScriptBlock(@" + $sb = [System.Text.StringBuilder]::new() + $maxKeyLength = 0 + foreach ($key in $_.Extension.Keys) { + if ($key.Length -gt $maxKeyLength) { + $maxKeyLength = $key.Length + } + } + + foreach ($key in $_.Extension.Keys) { + $null = $sb.Append($key.PadRight($maxKeyLength)) + $null = $sb.Append(' = ""') + $null = $sb.Append($_.Extension[$key]) + $null = $sb.Append($_.Extension[$key].Replace(""`e"",'`e')) + $null = $sb.Append($PSStyle.Reset) + $null = $sb.Append('""') + $null = $sb.Append([Environment]::NewLine) + } + + $sb.ToString()", + label: "Extension") + .EndEntry() + .EndList()); + } + private static IEnumerable ViewsOf_System_Management_Automation_PSStyleForegroundColor() { yield return new FormatViewDefinition("System.Management.Automation.PSStyle+ForegroundColor", ListControl.Create() .StartEntry() .AddItemScriptBlock(@"""$($_.Black)$($_.Black.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Black") + .AddItemScriptBlock(@"""$($_.BrightBlack)$($_.BrightBlack.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightBlack") .AddItemScriptBlock(@"""$($_.White)$($_.White.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "White") - .AddItemScriptBlock(@"""$($_.DarkGray)$($_.DarkGray.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "DarkGray") - .AddItemScriptBlock(@"""$($_.LightGray)$($_.LightGray.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightGray") + .AddItemScriptBlock(@"""$($_.BrightWhite)$($_.BrightWhite.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightWhite") .AddItemScriptBlock(@"""$($_.Red)$($_.Red.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Red") - .AddItemScriptBlock(@"""$($_.LightRed)$($_.LightRed.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightRed") + .AddItemScriptBlock(@"""$($_.BrightRed)$($_.BrightRed.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightRed") .AddItemScriptBlock(@"""$($_.Magenta)$($_.Magenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Magenta") - .AddItemScriptBlock(@"""$($_.LightMagenta)$($_.LightMagenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightMagenta") + .AddItemScriptBlock(@"""$($_.BrightMagenta)$($_.BrightMagenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightMagenta") .AddItemScriptBlock(@"""$($_.Blue)$($_.Blue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Blue") - .AddItemScriptBlock(@"""$($_.LightBlue)$($_.LightBlue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightBlue") + .AddItemScriptBlock(@"""$($_.BrightBlue)$($_.BrightBlue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightBlue") .AddItemScriptBlock(@"""$($_.Cyan)$($_.Cyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Cyan") - .AddItemScriptBlock(@"""$($_.LightCyan)$($_.LightCyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightCyan") + .AddItemScriptBlock(@"""$($_.BrightCyan)$($_.BrightCyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightCyan") .AddItemScriptBlock(@"""$($_.Green)$($_.Green.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Green") - .AddItemScriptBlock(@"""$($_.LightGreen)$($_.LightGreen.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightGreen") + .AddItemScriptBlock(@"""$($_.BrightGreen)$($_.BrightGreen.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightGreen") .AddItemScriptBlock(@"""$($_.Yellow)$($_.Yellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Yellow") - .AddItemScriptBlock(@"""$($_.LightYellow)$($_.LightYellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightYellow") + .AddItemScriptBlock(@"""$($_.BrightYellow)$($_.BrightYellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightYellow") .EndEntry() .EndList()); } private static IEnumerable ViewsOf_System_Management_Automation_PSStyleBackgroundColor() { - yield return new FormatViewDefinition("System.Management.Automation.PSStyle+ForegroundColor", + yield return new FormatViewDefinition("System.Management.Automation.PSStyle+BackgroundColor", ListControl.Create() .StartEntry() .AddItemScriptBlock(@"""$($_.Black)$($_.Black.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Black") + .AddItemScriptBlock(@"""$($_.BrightBlack)$($_.BrightBlack.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightBlack") .AddItemScriptBlock(@"""$($_.White)$($_.White.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "White") - .AddItemScriptBlock(@"""$($_.DarkGray)$($_.DarkGray.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "DarkGray") - .AddItemScriptBlock(@"""$($_.LightGray)$($_.LightGray.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightGray") + .AddItemScriptBlock(@"""$($_.BrightWhite)$($_.BrightWhite.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightWhite") .AddItemScriptBlock(@"""$($_.Red)$($_.Red.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Red") - .AddItemScriptBlock(@"""$($_.LightRed)$($_.LightRed.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightRed") + .AddItemScriptBlock(@"""$($_.BrightRed)$($_.BrightRed.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightRed") .AddItemScriptBlock(@"""$($_.Magenta)$($_.Magenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Magenta") - .AddItemScriptBlock(@"""$($_.LightMagenta)$($_.LightMagenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightMagenta") + .AddItemScriptBlock(@"""$($_.BrightMagenta)$($_.BrightMagenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightMagenta") .AddItemScriptBlock(@"""$($_.Blue)$($_.Blue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Blue") - .AddItemScriptBlock(@"""$($_.LightBlue)$($_.LightBlue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightBlue") + .AddItemScriptBlock(@"""$($_.BrightBlue)$($_.BrightBlue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightBlue") .AddItemScriptBlock(@"""$($_.Cyan)$($_.Cyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Cyan") - .AddItemScriptBlock(@"""$($_.LightCyan)$($_.LightCyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightCyan") + .AddItemScriptBlock(@"""$($_.BrightCyan)$($_.BrightCyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightCyan") .AddItemScriptBlock(@"""$($_.Green)$($_.Green.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Green") - .AddItemScriptBlock(@"""$($_.LightGreen)$($_.LightGreen.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightGreen") + .AddItemScriptBlock(@"""$($_.BrightGreen)$($_.BrightGreen.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightGreen") .AddItemScriptBlock(@"""$($_.Yellow)$($_.Yellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Yellow") - .AddItemScriptBlock(@"""$($_.LightYellow)$($_.LightYellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightYellow") + .AddItemScriptBlock(@"""$($_.BrightYellow)$($_.BrightYellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightYellow") .EndEntry() .EndList()); } diff --git a/src/System.Management.Automation/FormatAndOutput/common/BaseCommand.cs b/src/System.Management.Automation/FormatAndOutput/common/BaseCommand.cs index dcfb79d4aed..e59cae40146 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/BaseCommand.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/BaseCommand.cs @@ -22,6 +22,7 @@ internal TerminatingErrorContext(PSCmdlet command) _command = command; } + [System.Diagnostics.CodeAnalysis.DoesNotReturn] internal void ThrowTerminatingError(ErrorRecord errorRecord) { _command.ThrowTerminatingError(errorRecord); diff --git a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs index 95c58353457..7f88727ace7 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs @@ -176,15 +176,13 @@ internal override object Verify(object val, // need to check the type: // it can be a string or a script block - ScriptBlock sb = val as ScriptBlock; - if (sb != null) + if (val is ScriptBlock sb) { PSPropertyExpression ex = new PSPropertyExpression(sb); return ex; } - string s = val as string; - if (s != null) + if (val is string s) { if (string.IsNullOrEmpty(s)) { diff --git a/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs b/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs index 48ab1307681..113acf3fa6a 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Management.Automation; using System.Management.Automation.Internal; @@ -129,15 +128,10 @@ private bool ProcessObject(PSObject so) } // instantiate the cache if not done yet - if (_cache == null) - { - _cache = new FormattedObjectsCache(this.LineOutput.RequiresBuffering); - } + _cache ??= new FormattedObjectsCache(this.LineOutput.RequiresBuffering); // no need for formatting, just process the object - FormatStartData formatStart = o as FormatStartData; - - if (formatStart != null) + if (o is FormatStartData formatStart) { // get autosize flag from object // turn on group caching @@ -149,8 +143,7 @@ private bool ProcessObject(PSObject so) else { // If the format info doesn't define column widths, then auto-size based on the first ten elements - TableHeaderInfo headerInfo = formatStart.shapeInfo as TableHeaderInfo; - if ((headerInfo != null) && + if ((formatStart.shapeInfo is TableHeaderInfo headerInfo) && (headerInfo.tableColumnInfoList.Count > 0) && (headerInfo.tableColumnInfoList[0].width == 0)) { @@ -262,8 +255,7 @@ private enum PreprocessingState { raw, processed, error } /// Whether the object needs to be shunted to preprocessing. private bool NeedsPreprocessing(object o) { - FormatEntryData fed = o as FormatEntryData; - if (fed != null) + if (o is FormatEntryData fed) { // we got an already pre-processed object if (!fed.outOfBand) @@ -326,8 +318,7 @@ private void ValidateCurrentFormattingState(FormattingState expectedFormattingSt // need to abort the command string violatingCommand = "format-*"; - StartData sdObj = obj as StartData; - if (sdObj != null) + if (obj is StartData sdObj) { if (sdObj.shapeInfo is WideViewHeaderInfo) { @@ -387,18 +378,16 @@ private FormatMessagesContextManager.OutputContext CreateOutputContext( FormatMessagesContextManager.OutputContext parentContext, FormatInfoData formatInfoData) { - FormatStartData formatStartData = formatInfoData as FormatStartData; // initialize the format context - if (formatStartData != null) + if (formatInfoData is FormatStartData formatStartData) { FormatOutputContext foc = new FormatOutputContext(parentContext, formatStartData); return foc; } - GroupStartData gsd = formatInfoData as GroupStartData; // we are starting a group, initialize the group context - if (gsd != null) + if (formatInfoData is GroupStartData gsd) { GroupOutputContext goc = null; @@ -460,9 +449,16 @@ private void ProcessFormatStart(FormatMessagesContextManager.OutputContext c) /// Current context, with Fs in it. private void ProcessFormatEnd(FormatEndData fe, FormatMessagesContextManager.OutputContext c) { - // Console.WriteLine("ProcessFormatEnd"); - // we just add an empty line to the display - this.LineOutput.WriteLine(string.Empty); + if (c is FormatOutputContext foContext + && foContext.Data.shapeInfo is ListViewHeaderInfo) + { + // Skip writing out a new line for List view, because we already wrote out + // an extra new line after displaying the last list entry. + return; + } + + // We just add an empty line to the display. + LineOutput.WriteLine(string.Empty); } /// @@ -541,8 +537,7 @@ private void ProcessPayload(FormatEntryData fed, FormatMessagesContextManager.Ou private void ProcessOutOfBandPayload(FormatEntryData fed) { // try if it is raw text - RawTextFormatEntry rte = fed.formatEntryInfo as RawTextFormatEntry; - if (rte != null) + if (fed.formatEntryInfo is RawTextFormatEntry rte) { if (fed.isHelpObject) { @@ -553,15 +548,15 @@ private void ProcessOutOfBandPayload(FormatEntryData fed) } else { - _lo.WriteLine(rte.text); + // Write out raw text without any changes to it. + _lo.WriteRawText(rte.text); } return; } // try if it is a complex entry - ComplexViewEntry cve = fed.formatEntryInfo as ComplexViewEntry; - if (cve != null && cve.formatValueList != null) + if (fed.formatEntryInfo is ComplexViewEntry cve && cve.formatValueList != null) { ComplexWriter complexWriter = new ComplexWriter(); @@ -571,8 +566,7 @@ private void ProcessOutOfBandPayload(FormatEntryData fed) return; } // try if it is a list view - ListViewEntry lve = fed.formatEntryInfo as ListViewEntry; - if (lve != null && lve.listViewFieldList != null) + if (fed.formatEntryInfo is ListViewEntry lve && lve.listViewFieldList != null) { ListWriter listWriter = new ListWriter(); @@ -624,9 +618,7 @@ private FormatOutputContext FormatContext { for (FormatMessagesContextManager.OutputContext oc = _ctxManager.ActiveOutputContext; oc != null; oc = oc.ParentContext) { - FormatOutputContext foc = oc as FormatOutputContext; - - if (foc != null) + if (oc is FormatOutputContext foc) return foc; } @@ -651,17 +643,13 @@ private void ProcessCachedGroup(FormatStartData formatStartData, List /// Context for the outer scope of the format sequence. /// - private class FormatOutputContext : FormatMessagesContextManager.OutputContext + private sealed class FormatOutputContext : FormatMessagesContextManager.OutputContext { /// /// Construct a context to push on the stack. @@ -978,11 +966,10 @@ internal TableOutputContext(OutCommandInner cmd, /// internal override void Initialize() { - TableFormattingHint tableHint = this.InnerCommand.RetrieveFormattingHint() as TableFormattingHint; int[] columnWidthsHint = null; - // We expect that console width is less then 120. + // We expect that console width is less than 120. - if (tableHint != null) + if (this.InnerCommand.RetrieveFormattingHint() is TableFormattingHint tableHint) { columnWidthsHint = tableHint.columnWidths; } @@ -999,16 +986,18 @@ internal override void Initialize() // create arrays for widths and alignment Span columnWidths = columns <= StackAllocThreshold ? stackalloc int[columns] : new int[columns]; Span alignment = columns <= StackAllocThreshold ? stackalloc int[columns] : new int[columns]; + Span headerMatchesProperty = columns <= StackAllocThreshold ? stackalloc bool[columns] : new bool[columns]; int k = 0; foreach (TableColumnInfo tci in this.CurrentTableHeaderInfo.tableColumnInfoList) { columnWidths[k] = (columnWidthsHint != null) ? columnWidthsHint[k] : tci.width; alignment[k] = tci.alignment; + headerMatchesProperty[k] = tci.HeaderMatchesProperty; k++; } - this.Writer.Initialize(0, _consoleWidth, columnWidths, alignment, this.CurrentTableHeaderInfo.hideHeader); + this.Writer.Initialize(0, _consoleWidth, columnWidths, alignment, headerMatchesProperty, this.CurrentTableHeaderInfo.hideHeader); } /// @@ -1117,33 +1106,40 @@ private void InternalInitialize(ListViewEntry lve) internal static string[] GetProperties(ListViewEntry lve) { - StringCollection props = new StringCollection(); - foreach (ListViewField lvf in lve.listViewFieldList) + int count = lve.listViewFieldList.Count; + + if (count == 0) { - props.Add(lvf.label ?? lvf.propertyName); + return null; } - if (props.Count == 0) - return null; - string[] retVal = new string[props.Count]; - props.CopyTo(retVal, 0); - return retVal; + string[] result = new string[count]; + for (int index = 0; index < result.Length; ++index) + { + ListViewField lvf = lve.listViewFieldList[index]; + result[index] = lvf.label ?? lvf.propertyName; + } + + return result; } internal static string[] GetValues(ListViewEntry lve) { - StringCollection vals = new StringCollection(); + int count = lve.listViewFieldList.Count; - foreach (ListViewField lvf in lve.listViewFieldList) + if (count == 0) { - vals.Add(lvf.formatPropertyField.propertyValue); + return null; } - if (vals.Count == 0) - return null; - string[] retVal = new string[vals.Count]; - vals.CopyTo(retVal, 0); - return retVal; + string[] result = new string[count]; + for (int index = 0; index < result.Length; ++index) + { + ListViewField lvf = lve.listViewFieldList[index]; + result[index] = lvf.formatPropertyField.propertyValue; + } + + return result; } /// @@ -1202,13 +1198,11 @@ internal override void Initialize() // set the hard wider default, to be used if no other info is available int itemsPerRow = 2; - // get the header info and the view hint - WideFormattingHint hint = this.InnerCommand.RetrieveFormattingHint() as WideFormattingHint; - + // get the header info int columnsOnTheScreen = GetConsoleWindowWidth(this.InnerCommand._lo.ColumnNumber); // give a preference to the hint, if there - if (hint != null && hint.maxWidth > 0) + if (this.InnerCommand.RetrieveFormattingHint() is WideFormattingHint hint && hint.maxWidth > 0) { itemsPerRow = TableWriter.ComputeWideViewBestItemsPerRowFit(hint.maxWidth, columnsOnTheScreen); } @@ -1230,7 +1224,7 @@ internal override void Initialize() alignment[k] = TextAlignment.Left; } - this.Writer.Initialize(0, columnsOnTheScreen, columnWidths, alignment, false, GetConsoleWindowHeight(this.InnerCommand._lo.RowNumber)); + this.Writer.Initialize(leftMarginIndent: 0, columnsOnTheScreen, columnWidths, alignment, headerMatchesProperty: null, suppressHeader: false, screenRows: GetConsoleWindowHeight(this.InnerCommand._lo.RowNumber)); } /// @@ -1296,7 +1290,7 @@ private void WriteStringBuffer() /// Helper class to accumulate the display values so that when the end /// of a line is reached, a full line can be composed. /// - private class StringValuesBuffer + private sealed class StringValuesBuffer { /// /// Construct the buffer. @@ -1389,10 +1383,10 @@ internal override void Initialize() /// FormatEntryData to process. internal override void ProcessPayload(FormatEntryData fed) { - ComplexViewEntry cve = fed.formatEntryInfo as ComplexViewEntry; - if (cve == null || cve.formatValueList == null) - return; - _writer.WriteObject(cve.formatValueList); + if (fed.formatEntryInfo is ComplexViewEntry cve && cve.formatValueList is not null) + { + _writer.WriteObject(cve.formatValueList); + } } private readonly ComplexWriter _writer = new ComplexWriter(); diff --git a/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs b/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs index e96115b78da..61a3f962daf 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs @@ -6,8 +6,10 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Globalization; +using System.Management.Automation; using System.Management.Automation.Internal; using System.Text; +using System.Text.RegularExpressions; namespace Microsoft.PowerShell.Commands.Internal.Format { @@ -68,8 +70,7 @@ private void GenerateFormatEntryDisplay(FormatEntry fe, int currentDepth) { foreach (object obj in fe.formatValueList) { - FormatEntry feChild = obj as FormatEntry; - if (feChild != null) + if (obj is FormatEntry feChild) { if (currentDepth < maxRecursionDepth) { @@ -98,15 +99,13 @@ private void GenerateFormatEntryDisplay(FormatEntry fe, int currentDepth) continue; } - FormatTextField ftf = obj as FormatTextField; - if (ftf != null) + if (obj is FormatTextField ftf) { this.AddToBuffer(ftf.text); continue; } - FormatPropertyField fpf = obj as FormatPropertyField; - if (fpf != null) + if (obj is FormatPropertyField fpf) { this.AddToBuffer(fpf.propertyValue); } @@ -146,7 +145,7 @@ private void WriteToScreen() int indentationAbsoluteValue = (firstLineIndentation > 0) ? firstLineIndentation : -firstLineIndentation; if (indentationAbsoluteValue >= usefulWidth) { - // valu too big, we reset it to zero + // value too big, we reset it to zero firstLineIndentation = 0; } @@ -237,10 +236,7 @@ internal IndentationStackFrame(IndentationManager mgr) public void Dispose() { - if (_mgr != null) - { - _mgr.RemoveStackFrame(); - } + _mgr?.RemoveStackFrame(); } private readonly IndentationManager _mgr; @@ -328,9 +324,10 @@ internal struct GetWordsResult /// internal sealed class StringManipulationHelper { - private static readonly char s_softHyphen = '\u00AD'; - private static readonly char s_hardHyphen = '\u2011'; - private static readonly char s_nonBreakingSpace = '\u00A0'; + private const char SoftHyphen = '\u00AD'; + private const char HardHyphen = '\u2011'; + private const char NonBreakingSpace = '\u00A0'; + private static readonly Collection s_cultureCollection = new Collection(); static StringManipulationHelper() @@ -353,27 +350,58 @@ static StringManipulationHelper() private static IEnumerable GetWords(string s) { StringBuilder sb = new StringBuilder(); - GetWordsResult result = new GetWordsResult(); + StringBuilder vtSeqs = null; + Dictionary vtRanges = null; + + var valueStrDec = new ValueStringDecorated(s); + if (valueStrDec.IsDecorated) + { + vtSeqs = new StringBuilder(); + vtRanges = valueStrDec.EscapeSequenceRanges; + } + bool wordHasVtSeqs = false; for (int i = 0; i < s.Length; i++) { - // Soft hyphen = \u00AD - Should break, and add a hyphen if needed. If not needed for a break, hyphen should be absent - if (s[i] == ' ' || s[i] == '\t' || s[i] == s_softHyphen) + if (vtRanges?.TryGetValue(i, out int len) == true) { - result.Word = sb.ToString(); - sb.Clear(); - result.Delim = new string(s[i], 1); + var vtSpan = s.AsSpan(i, len); + sb.Append(vtSpan); + vtSeqs.Append(vtSpan); - yield return result; + wordHasVtSeqs = true; + i += len - 1; + continue; } - // Non-breaking space = \u00A0 - ideally shouldn't wrap - // Hard hyphen = \u2011 - Should not break - else if (s[i] == s_hardHyphen || s[i] == s_nonBreakingSpace) + + string delimiter = null; + if (s[i] is ' ' or '\t' or SoftHyphen) { - result.Word = sb.ToString(); - sb.Clear(); - result.Delim = string.Empty; + // Soft hyphen = \u00AD - Should break, and add a hyphen if needed. + // If not needed for a break, hyphen should be absent. + delimiter = new string(s[i], 1); + } + else if (s[i] is HardHyphen or NonBreakingSpace) + { + // Non-breaking space = \u00A0 - ideally shouldn't wrap. + // Hard hyphen = \u2011 - Should not break. + delimiter = string.Empty; + } + + if (delimiter is not null) + { + if (wordHasVtSeqs && !sb.EndsWith(PSStyle.Instance.Reset)) + { + sb.Append(PSStyle.Instance.Reset); + } + var result = new GetWordsResult() + { + Word = sb.ToString(), + Delim = delimiter + }; + + sb.Clear().Append(vtSeqs); yield return result; } else @@ -382,10 +410,23 @@ private static IEnumerable GetWords(string s) } } - result.Word = sb.ToString(); - result.Delim = string.Empty; + if (wordHasVtSeqs) + { + if (sb.Length == vtSeqs.Length) + { + // This indicates 'sb' only contains all VT sequences, which may happen when the string ends with a word delimiter. + // For a word that contains VT sequence only, it's the same as an empty string to the formatting system, + // because nothing will actually be rendered. + // So, we use an empty string in this case to avoid unneeded string allocations. + sb.Clear(); + } + else if (!sb.EndsWith(PSStyle.Instance.Reset)) + { + sb.Append(PSStyle.Instance.Reset); + } + } - yield return result; + yield return new GetWordsResult() { Word = sb.ToString(), Delim = string.Empty }; } internal static StringCollection GenerateLines(DisplayCells displayCells, string val, int firstLineLen, int followingLinesLen) @@ -412,14 +453,16 @@ private static StringCollection GenerateLinesWithoutWordWrap(DisplayCells displa } // break string on newlines and process each line separately - string[] lines = SplitLines(val); + List lines = SplitLines(val); - for (int k = 0; k < lines.Length; k++) + for (int k = 0; k < lines.Count; k++) { - if (lines[k] == null || displayCells.Length(lines[k]) <= firstLineLen) + string currentLine = lines[k]; + + if (currentLine == null || displayCells.Length(currentLine) <= firstLineLen) { // we do not need to split further, just add - retVal.Add(lines[k]); + retVal.Add(currentLine); continue; } @@ -432,7 +475,7 @@ private static StringCollection GenerateLinesWithoutWordWrap(DisplayCells displa int offset = 0; // offset into the line we are splitting - while (true) + while (offset < currentLine.Length) { // acquire the current active display line length (it can very from call to call) int currentDisplayLen = accumulator.ActiveLen; @@ -440,7 +483,7 @@ private static StringCollection GenerateLinesWithoutWordWrap(DisplayCells displa // determine if the current tail would fit or not // for the remaining part of the string, determine its display cell count - int currentCellsToFit = displayCells.Length(lines[k], offset); + int currentCellsToFit = displayCells.Length(currentLine, offset); // determine if we fit into the line int excessCells = currentCellsToFit - currentDisplayLen; @@ -449,7 +492,7 @@ private static StringCollection GenerateLinesWithoutWordWrap(DisplayCells displa { // we are not at the end of the string, select a sub string // that would fit in the remaining display length - int charactersToAdd = displayCells.GetHeadSplitLength(lines[k], offset, currentDisplayLen); + int charactersToAdd = displayCells.TruncateTail(currentLine, offset, currentDisplayLen); if (charactersToAdd <= 0) { @@ -463,7 +506,7 @@ private static StringCollection GenerateLinesWithoutWordWrap(DisplayCells displa else { // of the given length, add it to the accumulator - accumulator.AddLine(lines[k].Substring(offset, charactersToAdd)); + accumulator.AddLine(currentLine.VtSubstring(offset, charactersToAdd)); } // increase the offset by the # of characters added @@ -472,7 +515,7 @@ private static StringCollection GenerateLinesWithoutWordWrap(DisplayCells displa else { // we reached the last (partial) line, we add it all - accumulator.AddLine(lines[k].Substring(offset)); + accumulator.AddLine(currentLine.VtSubstring(offset)); break; } } @@ -528,9 +571,9 @@ private static StringCollection GenerateLinesWithWordWrap(DisplayCells displayCe } // break string on newlines and process each line separately - string[] lines = SplitLines(val); + List lines = SplitLines(val); - for (int k = 0; k < lines.Length; k++) + for (int k = 0; k < lines.Count; k++) { if (lines[k] == null || displayCells.Length(lines[k]) <= firstLineLen) { @@ -543,28 +586,34 @@ private static StringCollection GenerateLinesWithWordWrap(DisplayCells displayCe int lineWidth = firstLineLen; bool firstLine = true; StringBuilder singleLine = new StringBuilder(); + string resetStr = PSStyle.Instance.Reset; foreach (GetWordsResult word in GetWords(lines[k])) { string wordToAdd = word.Word; + string suffix = null; // Handle soft hyphen - if (word.Delim == s_softHyphen.ToString()) + if (word.Delim.Length == 1 && word.Delim[0] is SoftHyphen) { - int wordWidthWithHyphen = displayCells.Length(wordToAdd) + displayCells.Length(s_softHyphen.ToString()); + int wordWidthWithHyphen = displayCells.Length(wordToAdd) + displayCells.Length(SoftHyphen); // Add hyphen only if necessary if (wordWidthWithHyphen == spacesLeft) { - wordToAdd += "-"; + suffix = "-"; } } - else + else if (!string.IsNullOrEmpty(word.Delim)) { - if (!string.IsNullOrEmpty(word.Delim)) - { - wordToAdd += word.Delim; - } + suffix = word.Delim; + } + + if (suffix is not null) + { + wordToAdd = wordToAdd.EndsWith(resetStr) + ? wordToAdd.Insert(wordToAdd.Length - resetStr.Length, suffix) + : wordToAdd + suffix; } int wordWidth = displayCells.Length(wordToAdd); @@ -589,15 +638,35 @@ private static StringCollection GenerateLinesWithWordWrap(DisplayCells displayCe // Word is wider than a single line if (wordWidth > lineWidth) { - foreach (char c in wordToAdd) + Dictionary vtRanges = null; + StringBuilder vtSeqs = null; + + var valueStrDec = new ValueStringDecorated(wordToAdd); + if (valueStrDec.IsDecorated) { - char charToAdd = c; - int charWidth = displayCells.Length(c); + vtSeqs = new StringBuilder(); + vtRanges = valueStrDec.EscapeSequenceRanges; + } - // corner case: we have a two cell character and the current - // display length is one. - // add a single cell arbitrary character instead of the original - // one and keep going + bool hasEscSeqs = false; + for (int i = 0; i < wordToAdd.Length; i++) + { + if (vtRanges?.TryGetValue(i, out int len) == true) + { + var vtSpan = wordToAdd.AsSpan(i, len); + singleLine.Append(vtSpan); + vtSeqs.Append(vtSpan); + + hasEscSeqs = true; + i += len - 1; + continue; + } + + char charToAdd = wordToAdd[i]; + int charWidth = displayCells.Length(charToAdd); + + // Corner case: we have a two cell character and the current display length is one. + // Add a single cell arbitrary character instead of the original one and keep going. if (charWidth > lineWidth) { charToAdd = '?'; @@ -606,9 +675,13 @@ private static StringCollection GenerateLinesWithWordWrap(DisplayCells displayCe if (charWidth > spacesLeft) { + if (hasEscSeqs && !singleLine.EndsWith(resetStr)) + { + singleLine.Append(resetStr); + } + retVal.Add(singleLine.ToString()); - singleLine.Clear(); - singleLine.Append(charToAdd); + singleLine.Clear().Append(vtSeqs).Append(charToAdd); if (firstLine) { @@ -630,8 +703,7 @@ private static StringCollection GenerateLinesWithWordWrap(DisplayCells displayCe if (wordWidth > spacesLeft) { retVal.Add(singleLine.ToString()); - singleLine.Clear(); - singleLine.Append(wordToAdd); + singleLine.Clear().Append(wordToAdd); if (firstLine) { @@ -661,49 +733,77 @@ private static StringCollection GenerateLinesWithWordWrap(DisplayCells displayCe /// /// String to split. /// String array with the values. - internal static string[] SplitLines(string s) + internal static List SplitLines(string s) { - if (string.IsNullOrEmpty(s)) - return new string[1] { s }; + if (string.IsNullOrEmpty(s) || !s.Contains('\n')) + { + return new List(capacity: 1) { s?.Replace("\r", string.Empty) }; + } StringBuilder sb = new StringBuilder(); + List list = new List(); - foreach (char c in s) + StringBuilder vtSeqs = null; + Dictionary vtRanges = null; + + var valueStrDec = new ValueStringDecorated(s); + if (valueStrDec.IsDecorated) { - if (c != '\r') - sb.Append(c); + vtSeqs = new StringBuilder(); + vtRanges = valueStrDec.EscapeSequenceRanges; } - return sb.ToString().Split(s_newLineChar); - } - -#if false - internal static string StripNewLines (string s) - { - if (string.IsNullOrEmpty (s)) - return s; - - string[] lines = SplitLines (s); + bool hasVtSeqs = false; + for (int i = 0; i < s.Length; i++) + { + if (vtRanges?.TryGetValue(i, out int len) == true) + { + var vtSpan = s.AsSpan(i, len); + sb.Append(vtSpan); + vtSeqs.Append(vtSpan); - if (lines.Length == 0) - return null; + hasVtSeqs = true; + i += len - 1; + continue; + } - if (lines.Length == 1) - return lines[0]; + char c = s[i]; + if (c == '\n') + { + if (hasVtSeqs && !sb.EndsWith(PSStyle.Instance.Reset)) + { + sb.Append(PSStyle.Instance.Reset); + } - StringBuilder sb = new StringBuilder (); + list.Add(sb.ToString()); + sb.Clear().Append(vtSeqs); + } + else if (c != '\r') + { + sb.Append(c); + } + } - for (int k = 0; k < lines.Length; k++) + if (hasVtSeqs) { - if (k == 0) - sb.Append (lines[k]); - else - sb.Append (" " + lines[k]); + if (sb.Length == vtSeqs.Length) + { + // This indicates 'sb' only contains all VT sequences, which may happen when the string ends with '\n'. + // For a sub-string that contains VT sequence only, it's the same as an empty string to the formatting + // system, because nothing will actually be rendered. + // So, we use an empty string in this case to avoid unneeded string allocations. + sb.Clear(); + } + else if (!sb.EndsWith(PSStyle.Instance.Reset)) + { + sb.Append(PSStyle.Instance.Reset); + } } - return sb.ToString (); + list.Add(sb.ToString()); + return list; } -#endif + internal static string TruncateAtNewLine(string s) { if (string.IsNullOrEmpty(s)) @@ -711,7 +811,7 @@ internal static string TruncateAtNewLine(string s) return string.Empty; } - int lineBreak = s.IndexOfAny(s_lineBreakChars); + int lineBreak = s.AsSpan().IndexOfAny('\n', '\r'); if (lineBreak < 0) { @@ -725,8 +825,5 @@ internal static string PadLeft(string val, int count) { return StringUtil.Padding(count) + val; } - - private static readonly char[] s_newLineChar = new char[] { '\n' }; - private static readonly char[] s_lineBreakChars = new char[] { '\n', '\r' }; } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/FormatTable.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/FormatTable.cs index 43cab48d003..a1e34424950 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/FormatTable.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/FormatTable.cs @@ -19,9 +19,8 @@ namespace System.Management.Automation.Runspaces { /// /// This exception is used by Formattable constructor to indicate errors - /// occured during construction time. - /// - [Serializable] + /// occurred during construction time. + /// [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "FormatTable")] public class FormatTableLoadException : RuntimeException { @@ -70,7 +69,7 @@ public FormatTableLoadException(string message, Exception innerException) /// time. /// /// - /// The errors that occured + /// The errors that occurred. /// internal FormatTableLoadException(ConcurrentBag loadErrors) : base(StringUtil.Format(FormatAndOutXmlLoadingStrings.FormatTableLoadErrors)) @@ -84,55 +83,14 @@ internal FormatTableLoadException(ConcurrentBag loadErrors) /// /// /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected FormatTableLoadException(SerializationInfo info, StreamingContext context) - : base(info, context) { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - int errorCount = info.GetInt32("ErrorCount"); - if (errorCount > 0) - { - _errors = new Collection(); - for (int index = 0; index < errorCount; index++) - { - string key = string.Format(CultureInfo.InvariantCulture, "Error{0}", index); - _errors.Add(info.GetString(key)); - } - } + throw new NotSupportedException(); } #endregion Constructors - /// - /// Serializes the exception data. - /// - /// Serialization information. - /// Streaming context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - // If there are simple fields, serialize them with info.AddValue - if (_errors != null) - { - int errorCount = _errors.Count; - info.AddValue("ErrorCount", errorCount); - - for (int index = 0; index < errorCount; index++) - { - string key = string.Format(CultureInfo.InvariantCulture, "Error{0}", index); - info.AddValue(key, _errors[index]); - } - } - } - /// /// Set the default ErrorRecord. /// diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/XmlLoaderBase.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/XmlLoaderBase.cs index a7e7df0a5d7..9ebc79a762a 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/XmlLoaderBase.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/XmlLoaderBase.cs @@ -384,14 +384,10 @@ private bool MatchNodeNameHelper(XmlNode n, string s, bool allowAttributes) match = true; } - if (match && !allowAttributes) + if (match && !allowAttributes && n is XmlElement e && e.Attributes.Count > 0) { - XmlElement e = n as XmlElement; - if (e != null && e.Attributes.Count > 0) - { - // Error at XPath {0} in file {1}: The XML Element {2} does not allow attributes. - ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.AttributesNotAllowed, ComputeCurrentXPath(), FilePath, n.Name)); - } + // Error at XPath {0} in file {1}: The XML Element {2} does not allow attributes. + ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.AttributesNotAllowed, ComputeCurrentXPath(), FilePath, n.Name)); } return match; @@ -600,8 +596,7 @@ protected string ComputeCurrentXPath() path.Insert(0, "/"); if (sf.index != -1) { - path.Insert(1, string.Format(CultureInfo.InvariantCulture, - "{0}[{1}]", sf.node.Name, sf.index + 1)); + path.Insert(1, string.Create(CultureInfo.InvariantCulture, $"{sf.node.Name}[{sf.index + 1}]")); } else { diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData.cs index f126dddcadc..0190a31ddc9 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData.cs @@ -361,6 +361,7 @@ internal sealed class FieldPropertyToken : PropertyTokenBase internal sealed class FieldFormattingDirective { internal string formatString = null; // optional + internal bool isTable = false; } #endregion Elementary Tokens @@ -886,7 +887,7 @@ internal static EntrySelectedBy Get(List references) { if (tr.conditionToken != null) { - if (result.SelectionCondition == null) result.SelectionCondition = new List(); + result.SelectionCondition ??= new List(); result.SelectionCondition.Add(new DisplayEntry(tr.conditionToken)); continue; @@ -895,7 +896,7 @@ internal static EntrySelectedBy Get(List references) if (tr is TypeGroupReference) continue; - if (result.TypeNames == null) result.TypeNames = new List(); + result.TypeNames ??= new List(); result.TypeNames.Add(tr.name); } diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Complex.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Complex.cs index d3290c18752..822703fde21 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Complex.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Complex.cs @@ -179,14 +179,12 @@ internal static CustomItemBase Create(FormatToken token) return new CustomItemNewline(); } - var textToken = token as TextToken; - if (textToken != null) + if (token is TextToken textToken) { return new CustomItemText { Text = textToken.text }; } - var frameToken = token as FrameToken; - if (frameToken != null) + if (token is FrameToken frameToken) { var frame = new CustomItemFrame { @@ -211,8 +209,7 @@ internal static CustomItemBase Create(FormatToken token) return frame; } - var cpt = token as CompoundPropertyToken; - if (cpt != null) + if (token is CompoundPropertyToken cpt) { var cie = new CustomItemExpression { EnumerateCollection = cpt.enumerateCollection }; @@ -226,19 +223,14 @@ internal static CustomItemBase Create(FormatToken token) cie.Expression = new DisplayEntry(cpt.expression); } - if (cpt.control != null) + if (cpt.control is ComplexControlBody complexControlBody) { - cie.CustomControl = new CustomControl((ComplexControlBody)cpt.control, null); + cie.CustomControl = new CustomControl(complexControlBody, null); } return cie; } - var fpt = token as FieldPropertyToken; - if (fpt != null) - { - } - Diagnostics.Assert(false, "Unexpected formatting token kind"); return null; diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_List.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_List.cs index 6025acfff44..293b4c5b82e 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_List.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_List.cs @@ -208,8 +208,7 @@ public List SelectedBy { get { - if (EntrySelectedBy == null) - EntrySelectedBy = new EntrySelectedBy { TypeNames = new List() }; + EntrySelectedBy ??= new EntrySelectedBy { TypeNames = new List() }; return EntrySelectedBy.TypeNames; } } @@ -319,8 +318,7 @@ internal ListControlEntryItem(ListControlItemDefinition definition) Label = definition.label.text; } - FieldPropertyToken fpt = definition.formatTokenList[0] as FieldPropertyToken; - if (fpt != null) + if (definition.formatTokenList[0] is FieldPropertyToken fpt) { if (fpt.fieldFormattingDirective.formatString != null) { diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Table.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Table.cs index e509530538d..026b9b82f73 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Table.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Table.cs @@ -446,10 +446,9 @@ internal TableControlRow(TableRowDefinition rowdefinition) : this() foreach (TableRowItemDefinition itemdef in rowdefinition.rowItemDefinitionList) { - FieldPropertyToken fpt = itemdef.formatTokenList[0] as FieldPropertyToken; TableControlColumn column; - if (fpt != null) + if (itemdef.formatTokenList[0] is FieldPropertyToken fpt) { column = new TableControlColumn(fpt.expression.expressionValue, itemdef.alignment, fpt.expression.isScriptBlock, fpt.fieldFormattingDirective.formatString); diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Wide.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Wide.cs index 4f87998fd1e..1f5b42fd262 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Wide.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Wide.cs @@ -187,8 +187,7 @@ public List SelectedBy { get { - if (EntrySelectedBy == null) - EntrySelectedBy = new EntrySelectedBy { TypeNames = new List() }; + EntrySelectedBy ??= new EntrySelectedBy { TypeNames = new List() }; return EntrySelectedBy.TypeNames; } } @@ -205,8 +204,7 @@ internal WideControlEntryItem() internal WideControlEntryItem(WideControlEntryDefinition definition) : this() { - FieldPropertyToken fpt = definition.formatTokenList[0] as FieldPropertyToken; - if (fpt != null) + if (definition.formatTokenList[0] is FieldPropertyToken fpt) { DisplayEntry = new DisplayEntry(fpt.expression); FormatString = fpt.fieldFormattingDirective.formatString; diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayResourceManagerCache.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayResourceManagerCache.cs index fbdd1287a02..b5eb4d456e8 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayResourceManagerCache.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayResourceManagerCache.cs @@ -142,7 +142,7 @@ private sealed class AssemblyLoadResult /// Helper class to resolve an assembly name to an assembly reference /// The class caches previous results for faster lookup. /// - private class AssemblyNameResolver + private sealed class AssemblyNameResolver { /// /// Resolve the assembly name against the set of loaded assemblies. diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataManager.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataManager.cs index c42bd21adc7..629e419d5a2 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataManager.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataManager.cs @@ -409,7 +409,10 @@ private static TypeInfoDataBase LoadFromFileHelper( continue; } - if (etwEnabled) RunspaceEventSource.Log.ProcessFormatFileStart(file.FullPath); + if (etwEnabled) + { + RunspaceEventSource.Log.ProcessFormatFileStart(file.FullPath); + } if (!ProcessBuiltin(file, db, expressionFactory, logEntries, ref success)) { @@ -428,7 +431,10 @@ private static TypeInfoDataBase LoadFromFileHelper( { string mshsnapinMessage = StringUtil.Format(FormatAndOutXmlLoadingStrings.MshSnapinQualifiedError, info.psSnapinName, entry.message); info.errors.Add(mshsnapinMessage); - if (entry.failToLoadFile) { file.FailToLoadFile = true; } + if (entry.failToLoadFile) + { + file.FailToLoadFile = true; + } } } // now aggregate the entries... @@ -436,7 +442,10 @@ private static TypeInfoDataBase LoadFromFileHelper( } } - if (etwEnabled) RunspaceEventSource.Log.ProcessFormatFileStop(file.FullPath); + if (etwEnabled) + { + RunspaceEventSource.Log.ProcessFormatFileStop(file.FullPath); + } } // add any sensible defaults to the database diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataQuery.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataQuery.cs index 60953d22c26..c759cabd2a1 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataQuery.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataQuery.cs @@ -143,9 +143,8 @@ private int ComputeBestMatch(AppliesTo appliesTo, PSObject currentObject) } int currentMatch = BestMatchIndexUndefined; - TypeReference tr = r as TypeReference; - if (tr != null) + if (r is TypeReference tr) { // we have a type currentMatch = MatchTypeIndex(tr.name, currentObject, ex); @@ -486,18 +485,25 @@ private static void TraceHelper(ViewDefinition vd, bool isMatched) foreach (TypeOrGroupReference togr in vd.appliesTo.referenceList) { StringBuilder sb = new StringBuilder(); - TypeReference tr = togr as TypeReference; sb.Append(isMatched ? "MATCH FOUND" : "NOT MATCH"); - if (tr != null) + if (togr is TypeReference tr) { - sb.AppendFormat(CultureInfo.InvariantCulture, " {0} NAME: {1} TYPE: {2}", - ControlBase.GetControlShapeName(vd.mainControl), vd.name, tr.name); + sb.AppendFormat( + CultureInfo.InvariantCulture, + " {0} NAME: {1} TYPE: {2}", + ControlBase.GetControlShapeName(vd.mainControl), + vd.name, + tr.name); } else { TypeGroupReference tgr = togr as TypeGroupReference; - sb.AppendFormat(CultureInfo.InvariantCulture, " {0} NAME: {1} GROUP: {2}", - ControlBase.GetControlShapeName(vd.mainControl), vd.name, tgr.name); + sb.AppendFormat( + CultureInfo.InvariantCulture, + " {0} NAME: {1} GROUP: {2}", + ControlBase.GetControlShapeName(vd.mainControl), + vd.name, + tgr.name); } ActiveTracer.WriteLine(sb.ToString()); @@ -593,11 +599,9 @@ internal static AppliesTo GetAllApplicableTypes(TypeInfoDataBase db, AppliesTo a foreach (TypeOrGroupReference r in appliesTo.referenceList) { // if it is a type reference, just add the type name - TypeReference tr = r as TypeReference; - if (tr != null) + if (r is TypeReference tr) { - if (!allTypes.Contains(tr.name)) - allTypes.Add(tr.name); + allTypes.Add(tr.name); } else { @@ -614,8 +618,7 @@ internal static AppliesTo GetAllApplicableTypes(TypeInfoDataBase db, AppliesTo a // we found the group, go over it foreach (TypeReference x in tgd.typeReferenceList) { - if (!allTypes.Contains(x.name)) - allTypes.Add(x.name); + allTypes.Add(x.name); } } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader.cs index 8c1c251f149..b8a25e5e207 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader.cs @@ -277,7 +277,7 @@ internal bool LoadXmlFile( /// The ExtendedTypeDefinition instance to load formatting data from. /// Database instance to load the formatting data into. /// Expression factory to validate the script block. - /// Do we implicitly trust the script blocks (so they should run in full langauge mode)? + /// Do we implicitly trust the script blocks (so they should run in full language mode)? /// True when the view is for help output. /// internal bool LoadFormattingData( @@ -436,10 +436,13 @@ private void LoadData(ExtendedTypeDefinition typeDefinition, TypeInfoDataBase db ViewDefinition view = LoadViewFromObjectModel(typeDefinition.TypeNames, formatView, viewIndex++); if (view != null) { - ReportTrace(string.Format(CultureInfo.InvariantCulture, + ReportTrace(string.Format( + CultureInfo.InvariantCulture, "{0} view {1} is loaded from the 'FormatViewDefinition' at index {2} in 'ExtendedTypeDefinition' with type name {3}", ControlBase.GetControlShapeName(view.mainControl), - view.name, viewIndex - 1, typeDefinition.TypeName)); + view.name, + viewIndex - 1, + typeDefinition.TypeName)); // we are fine, add the view to the list db.viewDefinitionsSection.viewDefinitionList.Add(view); @@ -1082,20 +1085,17 @@ private ComplexControlEntryDefinition LoadComplexControlEntryDefinitionFromObjec private FormatToken LoadFormatTokenFromObjectModel(CustomItemBase item, int viewIndex, string typeName) { - var newline = item as CustomItemNewline; - if (newline != null) + if (item is CustomItemNewline newline) { return new NewLineToken { count = newline.Count }; } - var text = item as CustomItemText; - if (text != null) + if (item is CustomItemText text) { return new TextToken { text = text.Text }; } - var expr = item as CustomItemExpression; - if (expr != null) + if (item is CustomItemExpression expr) { var cpt = new CompoundPropertyToken { enumerateCollection = expr.EnumerateCollection }; @@ -1763,9 +1763,8 @@ private TextToken LoadTextToken(XmlNode n) private bool LoadStringResourceReference(XmlNode n, out StringResourceReference resource) { resource = null; - XmlElement e = n as XmlElement; - if (e == null) + if (n is not XmlElement e) { // Error at XPath {0} in file {1}: Node should be an XmlElement. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.NonXmlElementNode, ComputeCurrentXPath(), FilePath)); diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader_Views.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader_Views.cs index 505c766b9d1..c6d191284e6 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader_Views.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader_Views.cs @@ -27,10 +27,12 @@ private void LoadViewDefinitions(TypeInfoDataBase db, XmlNode viewDefinitionsNod ViewDefinition view = LoadView(n, index++); if (view != null) { - ReportTrace(string.Format(CultureInfo.InvariantCulture, + ReportTrace(string.Format( + CultureInfo.InvariantCulture, "{0} view {1} is loaded from file {2}", ControlBase.GetControlShapeName(view.mainControl), - view.name, view.loadingInfo.filePath)); + view.name, + view.loadingInfo.filePath)); // we are fine, add the view to the list db.viewDefinitionsSection.viewDefinitionList.Add(view); } diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatMsgCtxManager.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatMsgCtxManager.cs index 3eabcc975a0..1018936d2c1 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatMsgCtxManager.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatMsgCtxManager.cs @@ -68,8 +68,7 @@ internal OutputContext(OutputContext parentContextInStack) internal void Process(object o) { PacketInfoData formatData = o as PacketInfoData; - FormatEntryData fed = formatData as FormatEntryData; - if (fed != null) + if (formatData is FormatEntryData fed) { OutputContext ctx = null; diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs index f29033e3c8d..db1bc0704aa 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs @@ -141,13 +141,10 @@ private void InitializeAutoSize() return; } // check if we have a view with autosize checked - if (this.dataBaseInfo.view != null && this.dataBaseInfo.view.mainControl != null) + if (this.dataBaseInfo.view != null && this.dataBaseInfo.view.mainControl != null + && this.dataBaseInfo.view.mainControl is ControlBody controlBody && controlBody.autosize.HasValue) { - ControlBody controlBody = this.dataBaseInfo.view.mainControl as ControlBody; - if (controlBody != null && controlBody.autosize.HasValue) - { - _autosize = controlBody.autosize.Value; - } + _autosize = controlBody.autosize.Value; } } @@ -219,7 +216,7 @@ internal GroupStartData GenerateGroupStartData(PSObject firstObjectInGroup, int if (formatErrorObject != null && formatErrorObject.exception != null) { - // if we did no thave any errors in the expression evaluation + // if we did not have any errors in the expression evaluation // we might have errors in the formatting, if present _errorManager.LogStringFormatError(formatErrorObject); if (_errorManager.DisplayFormatErrorString) @@ -387,7 +384,7 @@ protected string GetExpressionDisplayValue(PSObject so, int enumerationLimit, PS } else if (formatErrorObject != null && formatErrorObject.exception != null) { - // if we did no thave any errors in the expression evaluation + // if we did not have any errors in the expression evaluation // we might have errors in the formatting, if present _errorManager.LogStringFormatError(formatErrorObject); if (_errorManager.DisplayErrorStrings) @@ -439,17 +436,14 @@ protected FormatPropertyField GenerateFormatPropertyField(List form if (formatTokenList.Count != 0) { FormatToken token = formatTokenList[0]; - FieldPropertyToken fpt = token as FieldPropertyToken; - if (fpt != null) + if (token is FieldPropertyToken fpt) { PSPropertyExpression ex = this.expressionFactory.CreateFromExpressionToken(fpt.expression, this.dataBaseInfo.view.loadingInfo); fpf.propertyValue = this.GetExpressionDisplayValue(so, enumerationLimit, ex, fpt.fieldFormattingDirective, out result); } - else + else if (token is TextToken tt) { - TextToken tt = token as TextToken; - if (tt != null) - fpf.propertyValue = this.dataBaseInfo.db.displayResourceManagerCache.GetTextTokenString(tt); + fpf.propertyValue = this.dataBaseInfo.db.displayResourceManagerCache.GetTextTokenString(tt); } } else diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs index c1645b09ad5..b2bbd27c2b9 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs @@ -107,8 +107,7 @@ private bool ExecuteFormatControl(TraversalInfo level, ControlBase control, ComplexControlBody complexBody = null; // we might have a reference - ControlReference controlReference = control as ControlReference; - if (controlReference != null && controlReference.controlType == typeof(ComplexControlBody)) + if (control is ControlReference controlReference && controlReference.controlType == typeof(ComplexControlBody)) { // retrieve the reference complexBody = DisplayDataQuery.ResolveControlReference( @@ -205,8 +204,7 @@ private void ExecuteFormatTokenList(TraversalInfo level, #region foreach loop foreach (FormatToken t in formatTokenList) { - TextToken tt = t as TextToken; - if (tt != null) + if (t is TextToken tt) { FormatTextField ftf = new FormatTextField(); ftf.text = _db.displayResourceManagerCache.GetTextTokenString(tt); @@ -214,8 +212,7 @@ private void ExecuteFormatTokenList(TraversalInfo level, continue; } - var newline = t as NewLineToken; - if (newline != null) + if (t is NewLineToken newline) { for (int i = 0; i < newline.count; i++) { @@ -225,8 +222,7 @@ private void ExecuteFormatTokenList(TraversalInfo level, continue; } - FrameToken ft = t as FrameToken; - if (ft != null) + if (t is FrameToken ft) { // instantiate a new entry and attach a frame info object FormatEntry feFrame = new FormatEntry(); @@ -245,8 +241,7 @@ private void ExecuteFormatTokenList(TraversalInfo level, continue; } #region CompoundPropertyToken - CompoundPropertyToken cpt = t as CompoundPropertyToken; - if (cpt != null) + if (t is CompoundPropertyToken cpt) { if (!EvaluateDisplayCondition(so, cpt.conditionToken)) { @@ -283,10 +278,7 @@ private void ExecuteFormatTokenList(TraversalInfo level, { // Since it is a leaf node we just consider it an empty string and go // on with formatting - if (val == null) - { - val = string.Empty; - } + val ??= string.Empty; FieldFormattingDirective fieldFormattingDirective = null; StringFormatError formatErrorObject = null; @@ -494,7 +486,7 @@ private void DisplayRawObject(PSObject so, List formatValueList) if (formatErrorObject != null && formatErrorObject.exception != null) { - // if we did no thave any errors in the expression evaluation + // if we did not have any errors in the expression evaluation // we might have errors in the formatting, if present _errorManager.LogStringFormatError(formatErrorObject); if (_errorManager.DisplayFormatErrorString) @@ -717,7 +709,7 @@ private string GetObjectDisplayName(PSObject so) if (_complexSpecificParameters.classDisplay == ComplexSpecificParameters.ClassInfoDisplay.shortName) { // get the last token in the full name - string[] arr = typeNames[0].Split(Utils.Separators.Dot); + string[] arr = typeNames[0].Split('.'); if (arr.Length > 0) return arr[arr.Length - 1]; } diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs index 1cee3067011..f7acd9ed226 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs @@ -114,20 +114,17 @@ private ListViewEntry GenerateListViewEntryFromDataBaseInfo(PSObject so, int enu // we try to fall back and see if we have an un-resolved PSPropertyExpression FormatToken token = listItem.formatTokenList[0]; - FieldPropertyToken fpt = token as FieldPropertyToken; - if (fpt != null) + if (token is FieldPropertyToken fpt) { PSPropertyExpression ex = this.expressionFactory.CreateFromExpressionToken(fpt.expression, this.dataBaseInfo.view.loadingInfo); // use the un-resolved PSPropertyExpression string as a label lvf.label = ex.ToString(); } - else + else if (token is TextToken tt) { - TextToken tt = token as TextToken; - if (tt != null) - // we had a text token, use it as a label (last resort...) - lvf.label = this.dataBaseInfo.db.displayResourceManagerCache.GetTextTokenString(tt); + // we had a text token, use it as a label (last resort...) + lvf.label = this.dataBaseInfo.db.displayResourceManagerCache.GetTextTokenString(tt); } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs index bfd3291dd46..0ddc307b646 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs @@ -172,7 +172,14 @@ private TableHeaderInfo GenerateTableHeaderInfoFromDataBaseInfo(PSObject so) ci.width = colHeader.width; ci.alignment = colHeader.alignment; if (colHeader.label != null) + { + if (colHeader.label.text != string.Empty) + { + ci.HeaderMatchesProperty = so.Properties[colHeader.label.text] is not null; + } + ci.label = this.dataBaseInfo.db.displayResourceManagerCache.GetTextTokenString(colHeader.label); + } } if (ci.alignment == TextAlignment.Undefined) @@ -187,18 +194,13 @@ private TableHeaderInfo GenerateTableHeaderInfoFromDataBaseInfo(PSObject so) token = rowItem.formatTokenList[0]; if (token != null) { - FieldPropertyToken fpt = token as FieldPropertyToken; - if (fpt != null) + if (token is FieldPropertyToken fpt) { ci.label = fpt.expression.expressionValue; } - else + else if (token is TextToken tt) { - TextToken tt = token as TextToken; - if (tt != null) - { - ci.label = this.dataBaseInfo.db.displayResourceManagerCache.GetTextTokenString(tt); - } + ci.label = this.dataBaseInfo.db.displayResourceManagerCache.GetTextTokenString(tt); } } else @@ -219,6 +221,7 @@ private TableHeaderInfo GenerateTableHeaderInfoFromProperties(PSObject so) TableHeaderInfo thi = new TableHeaderInfo(); thi.hideHeader = this.HideHeaders; + thi.repeatHeader = this.RepeatHeader; for (int k = 0; k < this.activeAssociationList.Count; k++) { @@ -233,10 +236,7 @@ private TableHeaderInfo GenerateTableHeaderInfoFromProperties(PSObject so) ci.propertyName = (string)key; } - if (ci.propertyName == null) - { - ci.propertyName = this.activeAssociationList[k].ResolvedExpression.ToString(); - } + ci.propertyName ??= this.activeAssociationList[k].ResolvedExpression.ToString(); // set the width of the table if (a.OriginatingParameter != null) @@ -391,10 +391,7 @@ private List GetActiveTableRowDefinition(TableControlBod } } - if (matchingRowDefinition == null) - { - matchingRowDefinition = match.BestMatch as TableRowDefinition; - } + matchingRowDefinition ??= match.BestMatch as TableRowDefinition; if (matchingRowDefinition == null) { @@ -412,10 +409,7 @@ private List GetActiveTableRowDefinition(TableControlBod } } - if (matchingRowDefinition == null) - { - matchingRowDefinition = match.BestMatch as TableRowDefinition; - } + matchingRowDefinition ??= match.BestMatch as TableRowDefinition; } } @@ -483,6 +477,12 @@ private TableRowEntry GenerateTableRowEntryFromFromProperties(PSObject so, int e directive = activeAssociationList[k].OriginatingParameter.GetEntry(FormatParameterDefinitionKeys.FormatStringEntryKey) as FieldFormattingDirective; } + if (directive is null) + { + directive = new FieldFormattingDirective(); + directive.isTable = true; + } + fpf.propertyValue = this.GetExpressionDisplayValue(so, enumerationLimit, this.activeAssociationList[k].ResolvedExpression, directive); tre.formatPropertyFieldList.Add(fpf); } diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewManager.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewManager.cs index 0737e43476e..891dfce6829 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewManager.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewManager.cs @@ -646,8 +646,7 @@ private static ErrorRecord GenerateErrorRecord(FormattingError error) { ErrorRecord errorRecord = null; string msg = null; - PSPropertyExpressionError psPropertyExpressionError = error as PSPropertyExpressionError; - if (psPropertyExpressionError != null) + if (error is PSPropertyExpressionError psPropertyExpressionError) { errorRecord = new ErrorRecord( psPropertyExpressionError.result.Exception, @@ -660,8 +659,7 @@ private static ErrorRecord GenerateErrorRecord(FormattingError error) errorRecord.ErrorDetails = new ErrorDetails(msg); } - StringFormatError formattingError = error as StringFormatError; - if (formattingError != null) + if (error is StringFormatError formattingError) { errorRecord = new ErrorRecord( formattingError.exception, diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatXMLWriter.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatXMLWriter.cs index bb2c8ff93e7..c8b077918fe 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatXMLWriter.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatXMLWriter.cs @@ -13,7 +13,7 @@ namespace Microsoft.PowerShell.Commands /// /// Helper class for writing formatting directives to XML. /// - internal class FormatXmlWriter + internal sealed class FormatXmlWriter { private XmlWriter _writer; private bool _exportScriptBlock; @@ -385,8 +385,7 @@ internal void WriteCustomControl(CustomControl customControl) internal void WriteCustomItem(CustomItemBase item) { - var newline = item as CustomItemNewline; - if (newline != null) + if (item is CustomItemNewline newline) { for (int i = 0; i < newline.Count; i++) { @@ -396,15 +395,13 @@ internal void WriteCustomItem(CustomItemBase item) return; } - var text = item as CustomItemText; - if (text != null) + if (item is CustomItemText text) { _writer.WriteElementString("Text", text.Text); return; } - var expr = item as CustomItemExpression; - if (expr != null) + if (item is CustomItemExpression expr) { _writer.WriteStartElement("ExpressionBinding"); if (expr.EnumerateCollection) diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormattingObjects.cs b/src/System.Management.Automation/FormatAndOutput/common/FormattingObjects.cs index f4ac55d1feb..4af1ee54d81 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormattingObjects.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormattingObjects.cs @@ -18,7 +18,7 @@ // representation that mig have been introduced by serialization. // // There is also the need to preserve type information across serialization -// boundaries, therefore the objects provide a GUID based machanism to +// boundaries, therefore the objects provide a GUID based mechanism to // preserve the information. // @@ -207,6 +207,7 @@ internal sealed partial class TableColumnInfo : FormatInfoData public int alignment = TextAlignment.Left; public string label = null; public string propertyName = null; + public bool HeaderMatchesProperty = true; } internal sealed class ListViewHeaderInfo : ShapeInfo diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormattingObjectsDeserializer.cs b/src/System.Management.Automation/FormatAndOutput/common/FormattingObjectsDeserializer.cs index 285e4d4c02b..00923a103ec 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormattingObjectsDeserializer.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormattingObjectsDeserializer.cs @@ -30,8 +30,7 @@ internal FormatObjectDeserializer(TerminatingErrorContext errorContext) internal bool IsFormatInfoData(PSObject so) { - var fid = PSObject.Base(so) as FormatInfoData; - if (fid != null) + if (PSObject.Base(so) is FormatInfoData fid) { if (fid is FormatStartData || fid is FormatEndData || @@ -86,8 +85,7 @@ fid is GroupEndData || /// Deserialized object or null. internal object Deserialize(PSObject so) { - var fid = PSObject.Base(so) as FormatInfoData; - if (fid != null) + if (PSObject.Base(so) is FormatInfoData fid) { if (fid is FormatStartData || fid is FormatEndData || @@ -325,9 +323,7 @@ internal WriteStreamType DeserializeWriteStreamTypeMemberVariable(PSObject so) internal FormatInfoData DeserializeObject(PSObject so) { FormatInfoData fid = FormatInfoDataClassFactory.CreateInstance(so, this); - - if (fid != null) - fid.Deserialize(so, this); + fid?.Deserialize(so, this); return fid; } @@ -355,31 +351,31 @@ static FormatInfoDataClassFactory() { s_constructors = new Dictionary> { - {FormatStartData.CLSID, () => new FormatStartData()}, - {FormatEndData.CLSID, () => new FormatEndData()}, - {GroupStartData.CLSID, () => new GroupStartData()}, - {GroupEndData.CLSID, () => new GroupEndData()}, - {FormatEntryData.CLSID, () => new FormatEntryData()}, - {WideViewHeaderInfo.CLSID, () => new WideViewHeaderInfo()}, - {TableHeaderInfo.CLSID, () => new TableHeaderInfo()}, - {TableColumnInfo.CLSID, () => new TableColumnInfo()}, - {ListViewHeaderInfo.CLSID, () => new ListViewHeaderInfo()}, - {ListViewEntry.CLSID, () => new ListViewEntry()}, - {ListViewField.CLSID, () => new ListViewField()}, - {TableRowEntry.CLSID, () => new TableRowEntry()}, - {WideViewEntry.CLSID, () => new WideViewEntry()}, - {ComplexViewHeaderInfo.CLSID, () => new ComplexViewHeaderInfo()}, - {ComplexViewEntry.CLSID, () => new ComplexViewEntry()}, - {GroupingEntry.CLSID, () => new GroupingEntry()}, - {PageHeaderEntry.CLSID, () => new PageHeaderEntry()}, - {PageFooterEntry.CLSID, () => new PageFooterEntry()}, - {AutosizeInfo.CLSID, () => new AutosizeInfo()}, - {FormatNewLine.CLSID, () => new FormatNewLine()}, - {FrameInfo.CLSID, () => new FrameInfo()}, - {FormatTextField.CLSID, () => new FormatTextField()}, - {FormatPropertyField.CLSID, () => new FormatPropertyField()}, - {FormatEntry.CLSID, () => new FormatEntry()}, - {RawTextFormatEntry.CLSID, () => new RawTextFormatEntry()} + {FormatStartData.CLSID, static () => new FormatStartData()}, + {FormatEndData.CLSID, static () => new FormatEndData()}, + {GroupStartData.CLSID, static () => new GroupStartData()}, + {GroupEndData.CLSID, static () => new GroupEndData()}, + {FormatEntryData.CLSID, static () => new FormatEntryData()}, + {WideViewHeaderInfo.CLSID, static () => new WideViewHeaderInfo()}, + {TableHeaderInfo.CLSID, static () => new TableHeaderInfo()}, + {TableColumnInfo.CLSID, static () => new TableColumnInfo()}, + {ListViewHeaderInfo.CLSID, static () => new ListViewHeaderInfo()}, + {ListViewEntry.CLSID, static () => new ListViewEntry()}, + {ListViewField.CLSID, static () => new ListViewField()}, + {TableRowEntry.CLSID, static () => new TableRowEntry()}, + {WideViewEntry.CLSID, static () => new WideViewEntry()}, + {ComplexViewHeaderInfo.CLSID, static () => new ComplexViewHeaderInfo()}, + {ComplexViewEntry.CLSID, static () => new ComplexViewEntry()}, + {GroupingEntry.CLSID, static () => new GroupingEntry()}, + {PageHeaderEntry.CLSID, static () => new PageHeaderEntry()}, + {PageFooterEntry.CLSID, static () => new PageFooterEntry()}, + {AutosizeInfo.CLSID, static () => new AutosizeInfo()}, + {FormatNewLine.CLSID, static () => new FormatNewLine()}, + {FrameInfo.CLSID, static () => new FrameInfo()}, + {FormatTextField.CLSID, static () => new FormatTextField()}, + {FormatPropertyField.CLSID, static () => new FormatPropertyField()}, + {FormatEntry.CLSID, static () => new FormatEntry()}, + {RawTextFormatEntry.CLSID, static () => new RawTextFormatEntry()} }; } diff --git a/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs b/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs index 32d4565da5d..7bca25ea828 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs @@ -1,9 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Management.Automation; +using System.Management.Automation.Host; using System.Management.Automation.Internal; using System.Text; @@ -13,60 +15,112 @@ namespace Microsoft.PowerShell.Commands.Internal.Format { /// /// Base class providing support for string manipulation. - /// This class is a tear off class provided by the LineOutput class - /// - /// Assumptions (in addition to the assumptions made for LineOutput): - /// - characters map to one or more character cells - /// - /// NOTE: we provide a base class that is valid for devices that have a - /// 1:1 mapping between a UNICODE character and a display cell. + /// This class is a tear off class provided by the LineOutput class. /// internal class DisplayCells { - internal virtual int Length(string str) + /// + /// Calculate the buffer cell length of the given string. + /// + /// String that may contain VT escape sequences. + /// Number of buffer cells the string needs to take. + internal int Length(string str) { return Length(str, 0); } + /// + /// Calculate the buffer cell length of the given string. + /// + /// String that may contain VT escape sequences. + /// + /// When the string doesn't contain VT sequences, it's the starting index. + /// When the string contains VT sequences, it means starting from the 'n-th' char that doesn't belong to a escape sequence. + /// Number of buffer cells the string needs to take. internal virtual int Length(string str, int offset) { - int length = 0; + if (string.IsNullOrEmpty(str)) + { + return 0; + } - foreach (char c in str) + var valueStrDec = new ValueStringDecorated(str); + if (valueStrDec.IsDecorated) { - length += LengthInBufferCells(c); + str = valueStrDec.ToString(OutputRendering.PlainText); } - return length - offset; - } + int length = 0; + for (; offset < str.Length; offset++) + { + length += CharLengthInBufferCells(str[offset]); + } - internal virtual int Length(char character) { return 1; } + return length; + } - internal virtual int GetHeadSplitLength(string str, int displayCells) + /// + /// Calculate the buffer cell length of the given character. + /// + /// + /// Number of buffer cells the character needs to take. + internal virtual int Length(char character) { - return GetHeadSplitLength(str, 0, displayCells); + return CharLengthInBufferCells(character); } - internal virtual int GetHeadSplitLength(string str, int offset, int displayCells) + /// + /// Truncate from the tail of the string. + /// + /// String that may contain VT escape sequences. + /// Number of buffer cells to fit in. + /// Number of non-escape-sequence characters from head of the string that can fit in the space. + internal int TruncateTail(string str, int displayCells) { - int len = str.Length - offset; - return (len < displayCells) ? len : displayCells; + return TruncateTail(str, offset: 0, displayCells); } - internal virtual int GetTailSplitLength(string str, int displayCells) + /// + /// Truncate from the tail of the string. + /// + /// String that may contain VT escape sequences. + /// + /// When the string doesn't contain VT sequences, it's the starting index. + /// When the string contains VT sequences, it means starting from the 'n-th' char that doesn't belong to a escape sequence. + /// Number of buffer cells to fit in. + /// Number of non-escape-sequence characters from head of the string that can fit in the space. + internal int TruncateTail(string str, int offset, int displayCells) { - return GetTailSplitLength(str, 0, displayCells); + var valueStrDec = new ValueStringDecorated(str); + if (valueStrDec.IsDecorated) + { + str = valueStrDec.ToString(OutputRendering.PlainText); + } + + return GetFitLength(str, offset, displayCells, startFromHead: true); } - internal virtual int GetTailSplitLength(string str, int offset, int displayCells) + /// + /// Truncate from the head of the string. + /// + /// String that may contain VT escape sequences. + /// Number of buffer cells to fit in. + /// Number of non-escape-sequence characters from head of the string that should be skipped. + internal int TruncateHead(string str, int displayCells) { - int len = str.Length - offset; - return (len < displayCells) ? len : displayCells; + var valueStrDec = new ValueStringDecorated(str); + if (valueStrDec.IsDecorated) + { + str = valueStrDec.ToString(OutputRendering.PlainText); + } + + int tailCount = GetFitLength(str, offset: 0, displayCells, startFromHead: false); + return str.Length - tailCount; } #region Helpers - protected static int LengthInBufferCells(char c) + protected static int CharLengthInBufferCells(char c) { // The following is based on http://www.cl.cam.ac.uk/~mgk25/c/wcwidth.c // which is derived from https://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt @@ -83,7 +137,7 @@ protected static int LengthInBufferCells(char c) ((uint)(c - 0xffe0) <= (0xffe6 - 0xffe0))); // We can ignore these ranges because .Net strings use surrogate pairs - // for this range and we do not handle surrogage pairs. + // for this range and we do not handle surrogate pairs. // (c >= 0x20000 && c <= 0x2fffd) || // (c >= 0x30000 && c <= 0x3fffd) return 1 + (isWide ? 1 : 0); @@ -93,25 +147,26 @@ protected static int LengthInBufferCells(char c) /// Given a string and a number of display cells, it computes how many /// characters would fit starting from the beginning or end of the string. /// - /// String to be displayed. + /// String to be displayed, which doesn't contain any VT sequences. /// Offset inside the string. /// Number of display cells. - /// If true compute from the head (i.e. k++) else from the tail (i.e. k--). + /// If true compute from the head (i.e. k++) else from the tail (i.e. k--). /// Number of characters that would fit. - protected int GetSplitLengthInternalHelper(string str, int offset, int displayCells, bool head) + protected int GetFitLength(string str, int offset, int displayCells, bool startFromHead) { int filledDisplayCellsCount = 0; // number of cells that are filled in int charactersAdded = 0; // number of characters that fit int currCharDisplayLen; // scratch variable - int k = (head) ? offset : str.Length - 1; - int kFinal = (head) ? str.Length - 1 : offset; + int k = startFromHead ? offset : str.Length - 1; + int kFinal = startFromHead ? str.Length - 1 : offset; while (true) { - if ((head && (k > kFinal)) || ((!head) && (k < kFinal))) + if ((startFromHead && k > kFinal) || (!startFromHead && k < kFinal)) { break; } + // compute the cell number for the current character currCharDisplayLen = this.Length(str[k]); @@ -120,6 +175,7 @@ protected int GetSplitLengthInternalHelper(string str, int offset, int displayCe // if we added this character it would not fit, we cannot continue break; } + // keep adding, we fit filledDisplayCellsCount += currCharDisplayLen; charactersAdded++; @@ -131,13 +187,13 @@ protected int GetSplitLengthInternalHelper(string str, int offset, int displayCe break; } - k = (head) ? (k + 1) : (k - 1); + k = startFromHead ? (k + 1) : (k - 1); } return charactersAdded; } - #endregion + #endregion } /// @@ -194,6 +250,13 @@ internal virtual void ExecuteBufferPlayBack(DoPlayBackCall playback) { } /// internal abstract void WriteLine(string s); + /// + /// Write a line of string as raw text to the output device, with no change to the string. + /// For example, keeping VT escape sequences intact in it. + /// + /// The raw text to be written to the device. + internal virtual void WriteRawText(string s) => WriteLine(s); + internal WriteStreamType WriteStream { get; @@ -324,10 +387,10 @@ private void WriteLineInternal(string val, int cols) } // check for line breaks - string[] lines = StringManipulationHelper.SplitLines(val); + List lines = StringManipulationHelper.SplitLines(val); // process the substrings as separate lines - for (int k = 0; k < lines.Length; k++) + for (int k = 0; k < lines.Count; k++) { // compute the display length of the string int displayLength = _displayCells.Length(lines[k]); @@ -353,11 +416,11 @@ private void WriteLineInternal(string val, int cols) { // the string is still too long to fit, write the first cols characters // and go back for more wraparound - int splitLen = _displayCells.GetHeadSplitLength(s, cols); - WriteLineInternal(s.Substring(0, splitLen), cols); + int headCount = _displayCells.TruncateTail(s, cols); + WriteLineInternal(s.VtSubstring(0, headCount), cols); // chop off the first fieldWidth characters, already printed - s = s.Substring(splitLen); + s = s.VtSubstring(headCount); if (_displayCells.Length(s) <= cols) { // if we fit, print the tail of the string and we are done @@ -375,7 +438,7 @@ private void WriteLineInternal(string val, int cols) /// Implementation of the ILineOutput interface accepting an instance of a /// TextWriter abstract class. /// - internal class TextWriterLineOutput : LineOutput + internal sealed class TextWriterLineOutput : LineOutput { #region ILineOutput methods @@ -411,9 +474,17 @@ internal override int RowNumber /// internal override void WriteLine(string s) { - CheckStopProcessing(); + WriteRawText(PSHostUserInterface.GetOutputString(s, isHost: false)); + } - s = Utils.GetOutputString(s, isHost: false); + /// + /// Write a raw text by delegating to the writer underneath, with no change to the text. + /// For example, keeping VT escape sequences intact in it. + /// + /// The raw text to be written to the device. + internal override void WriteRawText(string s) + { + CheckStopProcessing(); if (_suppressNewline) { @@ -424,6 +495,7 @@ internal override void WriteLine(string s) _writer.WriteLine(s); } } + #endregion /// diff --git a/src/System.Management.Automation/FormatAndOutput/common/ListWriter.cs b/src/System.Management.Automation/FormatAndOutput/common/ListWriter.cs index 91a7b8698a6..7598c6b2797 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/ListWriter.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/ListWriter.cs @@ -2,9 +2,12 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; +using System.Management.Automation; using System.Management.Automation.Internal; +using System.Text; namespace Microsoft.PowerShell.Commands.Internal.Format { @@ -29,6 +32,11 @@ internal class ListWriter /// private int _columnWidth = 0; + /// + /// A cached string builder used within this type to reduce creation of temporary strings. + /// + private readonly StringBuilder _cachedBuilder = new(); + /// /// /// Names of the properties to display. @@ -59,6 +67,10 @@ internal void Initialize(string[] propertyNames, int screenColumnWidth, DisplayC // check if we have to truncate the labels int maxAllowableLabelLength = screenColumnWidth - Separator.Length - MinFieldWidth; + if (InternalTestHooks.ForceFormatListFixedLabelWidth) + { + maxAllowableLabelLength = 10; + } // find out the max display length (cell count) of the property names _propertyLabelsDisplayLength = 0; // reset max @@ -83,19 +95,20 @@ internal void Initialize(string[] propertyNames, int screenColumnWidth, DisplayC for (int k = 0; k < propertyNames.Length; k++) { + string propertyName = propertyNames[k]; if (propertyNameCellCounts[k] < _propertyLabelsDisplayLength) { // shorter than the max, add padding - _propertyLabels[k] = propertyNames[k] + StringUtil.Padding(_propertyLabelsDisplayLength - propertyNameCellCounts[k]); + _propertyLabels[k] = propertyName + StringUtil.Padding(_propertyLabelsDisplayLength - propertyNameCellCounts[k]); } else if (propertyNameCellCounts[k] > _propertyLabelsDisplayLength) { // longer than the max, clip - _propertyLabels[k] = propertyNames[k].Substring(0, dc.GetHeadSplitLength(propertyNames[k], _propertyLabelsDisplayLength)); + _propertyLabels[k] = propertyName.VtSubstring(0, dc.TruncateTail(propertyName, _propertyLabelsDisplayLength)); } else { - _propertyLabels[k] = propertyNames[k]; + _propertyLabels[k] = propertyName; } _propertyLabels[k] += Separator; @@ -164,16 +177,15 @@ internal void WriteProperties(string[] values, LineOutput lo) /// LineOutput interface to write to. private void WriteProperty(int k, string propertyValue, LineOutput lo) { - if (propertyValue == null) - propertyValue = string.Empty; + propertyValue ??= string.Empty; // make sure we honor embedded newlines - string[] lines = StringManipulationHelper.SplitLines(propertyValue); + List lines = StringManipulationHelper.SplitLines(propertyValue); // padding to use in the lines after the first string padding = null; - for (int i = 0; i < lines.Length; i++) + for (int i = 0; i < lines.Count; i++) { string prependString = null; @@ -181,8 +193,7 @@ private void WriteProperty(int k, string propertyValue, LineOutput lo) prependString = _propertyLabels[k]; else { - if (padding == null) - padding = StringUtil.Padding(_propertyLabelsDisplayLength); + padding ??= StringUtil.Padding(_propertyLabelsDisplayLength); prependString = padding; } @@ -197,11 +208,10 @@ private void WriteProperty(int k, string propertyValue, LineOutput lo) /// /// String to add to the left. /// Line to print. - /// LineOuput to write to. + /// LineOutput to write to. private void WriteSingleLineHelper(string prependString, string line, LineOutput lo) { - if (line == null) - line = string.Empty; + line ??= string.Empty; // compute the width of the field for the value string (in screen cells) int fieldCellCount = _columnWidth - _propertyLabelsDisplayLength; @@ -209,20 +219,52 @@ private void WriteSingleLineHelper(string prependString, string line, LineOutput // split the lines StringCollection sc = StringManipulationHelper.GenerateLines(lo.DisplayCells, line, fieldCellCount, fieldCellCount); - // padding to use in the lines after the first - string padding = StringUtil.Padding(_propertyLabelsDisplayLength); + // The padding to use in the lines after the first. + string headPadding = null; + + // The VT style used for the list label. + string style = PSStyle.Instance.Formatting.FormatAccent; + string reset = PSStyle.Instance.Reset; // display the string collection for (int k = 0; k < sc.Count; k++) { + string str = sc[k]; + _cachedBuilder.Clear(); + if (k == 0) { - lo.WriteLine(prependString + sc[k]); + if (string.IsNullOrWhiteSpace(prependString) || style == string.Empty) + { + // - Sometimes 'prependString' is just padding white spaces, and we don't + // need to add formatting escape sequences in such a case. + // - Otherwise, if the style is an empty string, then the user has chosen + // to not apply a style to the list label. + _cachedBuilder.Append(prependString).Append(str); + } + else + { + // Apply the style to the list label. + _cachedBuilder + .Append(style) + .Append(prependString) + .Append(reset) + .Append(str); + } } else { - lo.WriteLine(padding + sc[k]); + // Lazily calculate the padding to use for the subsequent lines as it's quite often that only the first line exists. + headPadding ??= StringUtil.Padding(_propertyLabelsDisplayLength); + _cachedBuilder.Append(headPadding).Append(str); + } + + if (str.Contains(ValueStringDecorated.ESC) && !str.EndsWith(reset)) + { + _cachedBuilder.Append(reset); } + + lo.WriteLine(_cachedBuilder.ToString()); } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/OutputManager.cs b/src/System.Management.Automation/FormatAndOutput/common/OutputManager.cs index 41ea611f198..a551ef89bed 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/OutputManager.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/OutputManager.cs @@ -92,19 +92,14 @@ internal override void ProcessRecord() internal override void EndProcessing() { // shut down only if we ever processed a pipeline object - if (_mgr != null) - _mgr.ShutDown(); + _mgr?.ShutDown(); } internal override void StopProcessing() { lock (_syncRoot) { - if (_lo != null) - { - _lo.StopProcessing(); - } - + _lo?.StopProcessing(); _isStopped = true; } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/OutputQueue.cs b/src/System.Management.Automation/FormatAndOutput/common/OutputQueue.cs index 1909b03939c..333b0b42689 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/OutputQueue.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/OutputQueue.cs @@ -43,8 +43,7 @@ internal OutputGroupQueue(FormattedObjectsCache.ProcessCachedGroupNotification c /// Objects the cache needs to return. It can be null. internal List Add(PacketInfoData o) { - FormatStartData fsd = o as FormatStartData; - if (fsd != null) + if (o is FormatStartData fsd) { // just cache the reference (used during the notification call) _formatStartData = fsd; @@ -120,12 +119,10 @@ private void UpdateObjectCount(PacketInfoData o) { // add only of it's not a control message // and it's not out of band - FormatEntryData fed = o as FormatEntryData; - - if (fed == null || fed.outOfBand) - return; - - _currentObjectCount++; + if (o is FormatEntryData fed && !fed.outOfBand) + { + _currentObjectCount++; + } } private void Notify() @@ -139,8 +136,7 @@ private void Notify() foreach (PacketInfoData x in _queue) { - FormatEntryData fed = x as FormatEntryData; - if (fed != null && fed.outOfBand) + if (x is FormatEntryData fed && fed.outOfBand) continue; validObjects.Add(x); diff --git a/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs b/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs index b8e1890b37f..3f927afd7d5 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs @@ -1,6 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Collections; +using System.Collections.Generic; +using System.Management.Automation.Internal; + namespace System.Management.Automation { #region OutputRendering @@ -9,17 +13,14 @@ namespace System.Management.Automation /// public enum OutputRendering { - /// Automatic by PowerShell. - Automatic = 0, + /// Render ANSI only to host. + Host = 0, /// Render as plaintext. PlainText = 1, /// Render as ANSI. Ansi = 2, - - /// Render ANSI only to host. - Host = 3, } #endregion OutputRendering @@ -52,79 +53,79 @@ public sealed class ForegroundColor public string Black { get; } = "\x1b[30m"; /// - /// Gets the color blue. + /// Gets the color red. /// - public string Blue { get; } = "\x1b[34m"; + public string Red { get; } = "\x1b[31m"; /// - /// Gets the color cyan. + /// Gets the color green. /// - public string Cyan { get; } = "\x1b[36m"; + public string Green { get; } = "\x1b[32m"; /// - /// Gets the color dark gray. + /// Gets the color yellow. /// - public string DarkGray { get; } = "\x1b[90m"; + public string Yellow { get; } = "\x1b[33m"; /// - /// Gets the color green. + /// Gets the color blue. /// - public string Green { get; } = "\x1b[32m"; + public string Blue { get; } = "\x1b[34m"; /// - /// Gets the color light blue. + /// Gets the color magenta. /// - public string LightBlue { get; } = "\x1b[94m"; + public string Magenta { get; } = "\x1b[35m"; /// - /// Gets the color light cyan. + /// Gets the color cyan. /// - public string LightCyan { get; } = "\x1b[96m"; + public string Cyan { get; } = "\x1b[36m"; /// - /// Gets the color light gray. + /// Gets the color white. /// - public string LightGray { get; } = "\x1b[97m"; + public string White { get; } = "\x1b[37m"; /// - /// Gets the color light green. + /// Gets the color bright black. /// - public string LightGreen { get; } = "\x1b[92m"; + public string BrightBlack { get; } = "\x1b[90m"; /// - /// Gets the color light magenta. + /// Gets the color bright red. /// - public string LightMagenta { get; } = "\x1b[95m"; + public string BrightRed { get; } = "\x1b[91m"; /// - /// Gets the color light red. + /// Gets the color bright green. /// - public string LightRed { get; } = "\x1b[91m"; + public string BrightGreen { get; } = "\x1b[92m"; /// - /// Gets the color light yellow. + /// Gets the color bright yellow. /// - public string LightYellow { get; } = "\x1b[93m"; + public string BrightYellow { get; } = "\x1b[93m"; /// - /// Gets the color magenta. + /// Gets the color bright blue. /// - public string Magenta { get; } = "\x1b[35m"; + public string BrightBlue { get; } = "\x1b[94m"; /// - /// Gets the color read. + /// Gets the color bright magenta. /// - public string Red { get; } = "\x1b[31m"; + public string BrightMagenta { get; } = "\x1b[95m"; /// - /// Gets the color white. + /// Gets the color bright cyan. /// - public string White { get; } = "\x1b[37m"; + public string BrightCyan { get; } = "\x1b[96m"; /// - /// Gets the color yellow. + /// Gets the color bright white. /// - public string Yellow { get; } = "\x1b[33m"; + public string BrightWhite { get; } = "\x1b[97m"; /// /// Set as RGB (Red, Green, Blue). @@ -154,6 +155,16 @@ public string FromRgb(int rgb) return FromRgb(red, green, blue); } + + /// + /// Return the VT escape sequence for a foreground color. + /// + /// The foreground color to be mapped from. + /// The VT escape sequence representing the foreground color. + public string FromConsoleColor(ConsoleColor color) + { + return MapForegroundColorToEscapeSequence(color); + } } /// @@ -167,79 +178,79 @@ public sealed class BackgroundColor public string Black { get; } = "\x1b[40m"; /// - /// Gets the color blue. + /// Gets the color red. /// - public string Blue { get; } = "\x1b[44m"; + public string Red { get; } = "\x1b[41m"; /// - /// Gets the color cyan. + /// Gets the color green. /// - public string Cyan { get; } = "\x1b[46m"; + public string Green { get; } = "\x1b[42m"; /// - /// Gets the color dark gray. + /// Gets the color yellow. /// - public string DarkGray { get; } = "\x1b[100m"; + public string Yellow { get; } = "\x1b[43m"; /// - /// Gets the color green. + /// Gets the color blue. /// - public string Green { get; } = "\x1b[42m"; + public string Blue { get; } = "\x1b[44m"; /// - /// Gets the color light blue. + /// Gets the color magenta. /// - public string LightBlue { get; } = "\x1b[104m"; + public string Magenta { get; } = "\x1b[45m"; /// - /// Gets the color light cyan. + /// Gets the color cyan. /// - public string LightCyan { get; } = "\x1b[106m"; + public string Cyan { get; } = "\x1b[46m"; /// - /// Gets the color light gray. + /// Gets the color white. /// - public string LightGray { get; } = "\x1b[107m"; + public string White { get; } = "\x1b[47m"; /// - /// Gets the color light green. + /// Gets the color bright black. /// - public string LightGreen { get; } = "\x1b[102m"; + public string BrightBlack { get; } = "\x1b[100m"; /// - /// Gets the color light magenta. + /// Gets the color bright red. /// - public string LightMagenta { get; } = "\x1b[105m"; + public string BrightRed { get; } = "\x1b[101m"; /// - /// Gets the color light red. + /// Gets the color bright green. /// - public string LightRed { get; } = "\x1b[101m"; + public string BrightGreen { get; } = "\x1b[102m"; /// - /// Gets the color light yellow. + /// Gets the color bright yellow. /// - public string LightYellow { get; } = "\x1b[103m"; + public string BrightYellow { get; } = "\x1b[103m"; /// - /// Gets the color magenta. + /// Gets the color bright blue. /// - public string Magenta { get; } = "\x1b[45m"; + public string BrightBlue { get; } = "\x1b[104m"; /// - /// Gets the color read. + /// Gets the color bright magenta. /// - public string Red { get; } = "\x1b[41m"; + public string BrightMagenta { get; } = "\x1b[105m"; /// - /// Gets the color white. + /// Gets the color bright cyan. /// - public string White { get; } = "\x1b[47m"; + public string BrightCyan { get; } = "\x1b[106m"; /// - /// Gets the color yellow. + /// Gets the color bright white. /// - public string Yellow { get; } = "\x1b[43m"; + public string BrightWhite { get; } = "\x1b[107m"; /// /// The color set as RGB (Red, Green, Blue). @@ -269,6 +280,16 @@ public string FromRgb(int rgb) return FromRgb(red, green, blue); } + + /// + /// Return the VT escape sequence for a background color. + /// + /// The background color to be mapped from. + /// The VT escape sequence representing the background color. + public string FromConsoleColor(ConsoleColor color) + { + return MapBackgroundColorToEscapeSequence(color); + } } /// @@ -279,12 +300,33 @@ public sealed class ProgressConfiguration /// /// Gets or sets the style for progress bar. /// - public string Style { get; set; } = "\x1b[33;1m"; + public string Style + { + get => _style; + set => _style = ValidateNoContent(value); + } + + private string _style = "\x1b[33;1m"; /// /// Gets or sets the max width of the progress bar. /// - public int MaxWidth { get; set; } = 120; + public int MaxWidth + { + get => _maxWidth; + set + { + // Width less than 18 does not render correctly due to the different parts of the progress bar. + if (value < 18) + { + throw new ArgumentOutOfRangeException(nameof(MaxWidth), PSStyleStrings.ProgressWidthTooSmall); + } + + _maxWidth = value; + } + } + + private int _maxWidth = 120; /// /// Gets or sets the view for progress bar. @@ -305,38 +347,299 @@ public sealed class FormattingData /// /// Gets or sets the accent style for formatting. /// - public string FormatAccent { get; set; } = "\x1b[32;1m"; + public string FormatAccent + { + get => _formatAccent; + set => _formatAccent = ValidateNoContent(value); + } + + private string _formatAccent = "\x1b[32;1m"; + + /// + /// Gets or sets the style for table headers. + /// + public string TableHeader + { + get => _tableHeader; + set => _tableHeader = ValidateNoContent(value); + } + + private string _tableHeader = "\x1b[32;1m"; + + /// + /// Gets or sets the style for custom table headers. + /// + public string CustomTableHeaderLabel + { + get => _customTableHeaderLabel; + set => _customTableHeaderLabel = ValidateNoContent(value); + } + + private string _customTableHeaderLabel = "\x1b[32;1;3m"; /// /// Gets or sets the accent style for errors. /// - public string ErrorAccent { get; set; } = "\x1b[36;1m"; + public string ErrorAccent + { + get => _errorAccent; + set => _errorAccent = ValidateNoContent(value); + } + + private string _errorAccent = "\x1b[36;1m"; /// /// Gets or sets the style for error messages. /// - public string Error { get; set; } = "\x1b[31;1m"; + public string Error + { + get => _error; + set => _error = ValidateNoContent(value); + } + + private string _error = "\x1b[31;1m"; /// /// Gets or sets the style for warning messages. /// - public string Warning { get; set; } = "\x1b[33;1m"; + public string Warning + { + get => _warning; + set => _warning = ValidateNoContent(value); + } + + private string _warning = "\x1b[33;1m"; /// /// Gets or sets the style for verbose messages. /// - public string Verbose { get; set; } = "\x1b[33;1m"; + public string Verbose + { + get => _verbose; + set => _verbose = ValidateNoContent(value); + } + + private string _verbose = "\x1b[33;1m"; /// /// Gets or sets the style for debug messages. /// - public string Debug { get; set; } = "\x1b[33;1m"; + public string Debug + { + get => _debug; + set => _debug = ValidateNoContent(value); + } + + private string _debug = "\x1b[33;1m"; + + /// + /// Gets or sets the style for rendering feedback provider names. + /// + public string FeedbackName + { + get => _feedbackName; + set => _feedbackName = ValidateNoContent(value); + } + + // Yellow by default. + private string _feedbackName = "\x1b[33m"; + + /// + /// Gets or sets the style for rendering feedback message. + /// + public string FeedbackText + { + get => _feedbackText; + set => _feedbackText = ValidateNoContent(value); + } + + // BrightCyan by default. + private string _feedbackText = "\x1b[96m"; + + /// + /// Gets or sets the style for rendering feedback actions. + /// + public string FeedbackAction + { + get => _feedbackAction; + set => _feedbackAction = ValidateNoContent(value); + } + + // BrightWhite by default. + private string _feedbackAction = "\x1b[97m"; + } + + /// + /// Contains formatting styles for FileInfo objects. + /// + public sealed class FileInfoFormatting + { + /// + /// Gets or sets the style for directories. + /// + public string Directory + { + get => _directory; + set => _directory = ValidateNoContent(value); + } + + private string _directory = "\x1b[44;1m"; + + /// + /// Gets or sets the style for symbolic links. + /// + public string SymbolicLink + { + get => _symbolicLink; + set => _symbolicLink = ValidateNoContent(value); + } + + private string _symbolicLink = "\x1b[36;1m"; + + /// + /// Gets or sets the style for executables. + /// + public string Executable + { + get => _executable; + set => _executable = ValidateNoContent(value); + } + + private string _executable = "\x1b[32;1m"; + + /// + /// Custom dictionary handling validation of extension and content. + /// + public sealed class FileExtensionDictionary + { + private static string ValidateExtension(string extension) + { + if (!extension.StartsWith('.')) + { + throw new ArgumentException(PSStyleStrings.ExtensionNotStartingWithPeriod); + } + + return extension; + } + + private readonly Dictionary _extensionDictionary = new(StringComparer.OrdinalIgnoreCase); + + /// + /// Add new extension and decoration to dictionary. + /// + /// Extension to add. + /// ANSI string value to add. + public void Add(string extension, string decoration) + { + _extensionDictionary.Add(ValidateExtension(extension), ValidateNoContent(decoration)); + } + + /// + /// Add new extension and decoration to dictionary without validation. + /// + /// Extension to add. + /// ANSI string value to add. + internal void AddWithoutValidation(string extension, string decoration) + { + _extensionDictionary.Add(extension, decoration); + } + + /// + /// Remove an extension from dictionary. + /// + /// Extension to remove. + public void Remove(string extension) + { + _extensionDictionary.Remove(ValidateExtension(extension)); + } + + /// + /// Clear the dictionary. + /// + public void Clear() + { + _extensionDictionary.Clear(); + } + + /// + /// Gets or sets the decoration by specified extension. + /// + /// Extension to get decoration for. + /// The decoration for specified extension. + public string this[string extension] + { + get + { + return _extensionDictionary[ValidateExtension(extension)]; + } + + set + { + _extensionDictionary[ValidateExtension(extension)] = ValidateNoContent(value); + } + } + + /// + /// Gets whether the dictionary contains the specified extension. + /// + /// Extension to check for. + /// True if the dictionary contains the specified extension, otherwise false. + public bool ContainsKey(string extension) + { + if (string.IsNullOrEmpty(extension)) + { + return false; + } + + return _extensionDictionary.ContainsKey(ValidateExtension(extension)); + } + + /// + /// Gets the extensions for the dictionary. + /// + /// The extensions for the dictionary. + public IEnumerable Keys + { + get + { + return _extensionDictionary.Keys; + } + } + } + + /// + /// Gets the style for archive. + /// + public FileExtensionDictionary Extension { get; } + + /// + /// Initializes a new instance of the class. + /// + public FileInfoFormatting() + { + Extension = new FileExtensionDictionary(); + + // archives + Extension.AddWithoutValidation(".zip", "\x1b[31;1m"); + Extension.AddWithoutValidation(".tgz", "\x1b[31;1m"); + Extension.AddWithoutValidation(".gz", "\x1b[31;1m"); + Extension.AddWithoutValidation(".tar", "\x1b[31;1m"); + Extension.AddWithoutValidation(".nupkg", "\x1b[31;1m"); + Extension.AddWithoutValidation(".cab", "\x1b[31;1m"); + Extension.AddWithoutValidation(".7z", "\x1b[31;1m"); + + // powershell + Extension.AddWithoutValidation(".ps1", "\x1b[33;1m"); + Extension.AddWithoutValidation(".psd1", "\x1b[33;1m"); + Extension.AddWithoutValidation(".psm1", "\x1b[33;1m"); + Extension.AddWithoutValidation(".ps1xml", "\x1b[33;1m"); + } } /// /// Gets or sets the rendering mode for output. /// - public OutputRendering OutputRendering { get; set; } = OutputRendering.Automatic; + public OutputRendering OutputRendering { get; set; } = OutputRendering.Host; /// /// Gets value to turn off all attributes. @@ -363,6 +666,16 @@ public sealed class FormattingData /// public string Bold { get; } = "\x1b[1m"; + /// + /// Gets value to turn off dim. + /// + public string DimOff { get; } = "\x1b[22m"; + + /// + /// Gets value to turn on dim. + /// + public string Dim { get; } = "\x1b[2m"; + /// /// Gets value to turn on hidden. /// @@ -444,6 +757,11 @@ public string FormatHyperlink(string text, Uri link) /// public BackgroundColor Background { get; } + /// + /// Gets FileInfo colors. + /// + public FileInfoFormatting FileInfo { get; } + private static readonly PSStyle s_psstyle = new PSStyle(); private PSStyle() @@ -452,6 +770,20 @@ private PSStyle() Progress = new ProgressConfiguration(); Foreground = new ForegroundColor(); Background = new BackgroundColor(); + FileInfo = new FileInfoFormatting(); + } + + private static string ValidateNoContent(string text) + { + ArgumentNullException.ThrowIfNull(text); + + var decorartedString = new ValueStringDecorated(text); + if (decorartedString.ContentLength > 0) + { + throw new ArgumentException(string.Format(PSStyleStrings.TextContainsContent, decorartedString.ToString(OutputRendering.PlainText))); + } + + return text; } /// @@ -464,6 +796,116 @@ public static PSStyle Instance return s_psstyle; } } + + /// + /// The map of background console colors to escape sequences. + /// + private static readonly string[] BackgroundColorMap = + { + "\x1b[40m", // Black + "\x1b[44m", // DarkBlue + "\x1b[42m", // DarkGreen + "\x1b[46m", // DarkCyan + "\x1b[41m", // DarkRed + "\x1b[45m", // DarkMagenta + "\x1b[43m", // DarkYellow + "\x1b[47m", // Gray + "\x1b[100m", // DarkGray + "\x1b[104m", // Blue + "\x1b[102m", // Green + "\x1b[106m", // Cyan + "\x1b[101m", // Red + "\x1b[105m", // Magenta + "\x1b[103m", // Yellow + "\x1b[107m", // White + }; + + /// + /// The map of foreground console colors to escape sequences. + /// + private static readonly string[] ForegroundColorMap = + { + "\x1b[30m", // Black + "\x1b[34m", // DarkBlue + "\x1b[32m", // DarkGreen + "\x1b[36m", // DarkCyan + "\x1b[31m", // DarkRed + "\x1b[35m", // DarkMagenta + "\x1b[33m", // DarkYellow + "\x1b[37m", // Gray + "\x1b[90m", // DarkGray + "\x1b[94m", // Blue + "\x1b[92m", // Green + "\x1b[96m", // Cyan + "\x1b[91m", // Red + "\x1b[95m", // Magenta + "\x1b[93m", // Yellow + "\x1b[97m", // White + }; + + /// + /// Return the VT escape sequence for a ConsoleColor. + /// + /// The to be mapped from. + /// Whether or not it's a background color. + /// The VT escape sequence representing the color. + internal static string MapColorToEscapeSequence(ConsoleColor color, bool isBackground) + { + int index = (int)color; + if (index < 0 || index >= ForegroundColorMap.Length) + { + throw new ArgumentOutOfRangeException(paramName: nameof(color)); + } + + return (isBackground ? BackgroundColorMap : ForegroundColorMap)[index]; + } + + /// + /// Return the VT escape sequence for a foreground color. + /// + /// The foreground color to be mapped from. + /// The VT escape sequence representing the foreground color. + public static string MapForegroundColorToEscapeSequence(ConsoleColor foregroundColor) + => MapColorToEscapeSequence(foregroundColor, isBackground: false); + + /// + /// Return the VT escape sequence for a background color. + /// + /// The background color to be mapped from. + /// The VT escape sequence representing the background color. + public static string MapBackgroundColorToEscapeSequence(ConsoleColor backgroundColor) + => MapColorToEscapeSequence(backgroundColor, isBackground: true); + + /// + /// Return the VT escape sequence for a pair of foreground and background colors. + /// + /// The foreground color of the color pair. + /// The background color of the color pair. + /// The VT escape sequence representing the foreground and background color pair. + public static string MapColorPairToEscapeSequence(ConsoleColor foregroundColor, ConsoleColor backgroundColor) + { + int foreIndex = (int)foregroundColor; + int backIndex = (int)backgroundColor; + + if (foreIndex < 0 || foreIndex >= ForegroundColorMap.Length) + { + throw new ArgumentOutOfRangeException(paramName: nameof(foregroundColor)); + } + + if (backIndex < 0 || backIndex >= ForegroundColorMap.Length) + { + throw new ArgumentOutOfRangeException(paramName: nameof(backgroundColor)); + } + + string foreground = ForegroundColorMap[foreIndex]; + string background = BackgroundColorMap[backIndex]; + + return string.Concat( + foreground.AsSpan(start: 0, length: foreground.Length - 1), + ";".AsSpan(), + background.AsSpan(start: 2)); + } } + #endregion PSStyle } diff --git a/src/System.Management.Automation/FormatAndOutput/common/StringDecorated.cs b/src/System.Management.Automation/FormatAndOutput/common/StringDecorated.cs index 3c1041f9c47..c76d5ebc50e 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/StringDecorated.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/StringDecorated.cs @@ -3,6 +3,7 @@ #nullable enable +using System.Collections.Generic; using System.Text.RegularExpressions; namespace System.Management.Automation.Internal @@ -20,10 +21,7 @@ private string PlainText { get { - if (_plaintextcontent == null) - { - _plaintextcontent = ValueStringDecorated.AnsiRegex.Replace(_text, string.Empty); - } + _plaintextcontent ??= ValueStringDecorated.AnsiRegex.Replace(_text, string.Empty); return _plaintextcontent; } @@ -55,7 +53,10 @@ public StringDecorated(string text) /// Render the decorarted string using automatic output rendering. /// /// Rendered string based on automatic output rendering. - public override string ToString() => _isDecorated ? ToString(OutputRendering.Automatic) : _text; + public override string ToString() => ToString( + PSStyle.Instance.OutputRendering == OutputRendering.PlainText + ? OutputRendering.PlainText + : OutputRendering.Ansi); /// /// Return string representation of content depending on output rendering mode. @@ -64,28 +65,17 @@ public StringDecorated(string text) /// Rendered string based on outputRendering. public string ToString(OutputRendering outputRendering) { - if (!_isDecorated) - { - return _text; - } - - if (outputRendering == OutputRendering.Automatic) + if (outputRendering == OutputRendering.Host) { - outputRendering = OutputRendering.Ansi; - if (PSStyle.Instance.OutputRendering == OutputRendering.PlainText) - { - outputRendering = OutputRendering.PlainText; - } + throw new ArgumentException(StringDecoratedStrings.RequireExplicitRendering); } - if (outputRendering == OutputRendering.PlainText) - { - return PlainText; - } - else + if (!_isDecorated) { return _text; } + + return outputRendering == OutputRendering.PlainText ? PlainText : _text; } } @@ -95,22 +85,53 @@ internal struct ValueStringDecorated private readonly bool _isDecorated; private readonly string _text; private string? _plaintextcontent; + private Dictionary? _vtRanges; private string PlainText { get { - if (_plaintextcontent == null) - { - _plaintextcontent = AnsiRegex.Replace(_text, string.Empty); - } + _plaintextcontent ??= AnsiRegex.Replace(_text, string.Empty); return _plaintextcontent; } } + // graphics/color mode ESC[1;2;...m + private const string GraphicsRegex = @"(\x1b\[\d*(;\d+)*m)"; + + // CSI escape sequences + private const string CsiRegex = @"(\x1b\[\?\d+[hl])"; + + // Hyperlink escape sequences. Note: '.*?' makes '.*' do non-greedy match. + private const string HyperlinkRegex = @"(\x1b\]8;;.*?\x1b\\)"; + // replace regex with .NET 6 API once available - internal static readonly Regex AnsiRegex = new Regex(@"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])", RegexOptions.Compiled); + internal static readonly Regex AnsiRegex = new Regex($"{GraphicsRegex}|{CsiRegex}|{HyperlinkRegex}", RegexOptions.Compiled); + + /// + /// Get the ranges of all escape sequences in the text. + /// + /// + /// A dictionary with the key being the starting index of an escape sequence, + /// and the value being the length of the escape sequence. + /// + internal Dictionary? EscapeSequenceRanges + { + get + { + if (_isDecorated && _vtRanges is null) + { + _vtRanges = new Dictionary(); + foreach (Match match in AnsiRegex.Matches(_text)) + { + _vtRanges.Add(match.Index, match.Length); + } + } + + return _vtRanges; + } + } /// /// Initializes a new instance of the struct. @@ -120,7 +141,8 @@ public ValueStringDecorated(string text) { _text = text; _isDecorated = text.Contains(ESC); - _plaintextcontent = null; + _plaintextcontent = _isDecorated ? null : text; + _vtRanges = null; } /// @@ -139,7 +161,10 @@ public ValueStringDecorated(string text) /// Render the decorarted string using automatic output rendering. /// /// Rendered string based on automatic output rendering. - public override string ToString() => _isDecorated ? ToString(OutputRendering.Automatic) : _text; + public override string ToString() => ToString( + PSStyle.Instance.OutputRendering == OutputRendering.PlainText + ? OutputRendering.PlainText + : OutputRendering.Ansi); /// /// Return string representation of content depending on output rendering mode. @@ -148,28 +173,17 @@ public ValueStringDecorated(string text) /// Rendered string based on outputRendering. public string ToString(OutputRendering outputRendering) { - if (!_isDecorated) + if (outputRendering == OutputRendering.Host) { - return _text; - } - - if (outputRendering == OutputRendering.Automatic) - { - outputRendering = OutputRendering.Ansi; - if (PSStyle.Instance.OutputRendering == OutputRendering.PlainText) - { - outputRendering = OutputRendering.PlainText; - } + throw new ArgumentException(StringDecoratedStrings.RequireExplicitRendering); } - if (outputRendering == OutputRendering.PlainText) - { - return PlainText; - } - else + if (!_isDecorated) { return _text; } + + return outputRendering == OutputRendering.PlainText ? PlainText : _text; } } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/TableWriter.cs b/src/System.Management.Automation/FormatAndOutput/common/TableWriter.cs index 54aaa9438c5..49b9005a88e 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/TableWriter.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/TableWriter.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.Management.Automation; using System.Management.Automation.Internal; using System.Text; @@ -16,16 +17,17 @@ internal class TableWriter /// /// Information about each column boundaries. /// - private class ColumnInfo + private sealed class ColumnInfo { internal int startCol = 0; internal int width = 0; internal int alignment = TextAlignment.Left; + internal bool HeaderMatchesProperty = true; } /// /// Class containing information about the tabular layout. /// - private class ScreenInfo + private sealed class ScreenInfo { internal int screenColumns = 0; internal int screenRows = 0; @@ -41,9 +43,6 @@ private class ScreenInfo private ScreenInfo _si; - private const char ESC = '\u001b'; - private const string ResetConsoleVt100Code = "\u001b[m"; - private List _header; internal static int ComputeWideViewBestItemsPerRowFit(int stringLen, int screenColumns) @@ -84,9 +83,10 @@ internal static int ComputeWideViewBestItemsPerRowFit(int stringLen, int screenC /// Number of character columns on the screen. /// Array of specified column widths. /// Array of alignment flags. + /// Array of flags where the header label matches a property name. /// If true, suppress header printing. /// Number of rows on the screen. - internal void Initialize(int leftMarginIndent, int screenColumns, Span columnWidths, ReadOnlySpan alignment, bool suppressHeader, int screenRows = int.MaxValue) + internal void Initialize(int leftMarginIndent, int screenColumns, Span columnWidths, ReadOnlySpan alignment, ReadOnlySpan headerMatchesProperty, bool suppressHeader, int screenRows = int.MaxValue) { if (leftMarginIndent < 0) { @@ -141,6 +141,11 @@ internal void Initialize(int leftMarginIndent, int screenColumns, Span colu _si.columnInfo[k].startCol = startCol; _si.columnInfo[k].width = columnWidths[k]; _si.columnInfo[k].alignment = alignment[k]; + if (!headerMatchesProperty.IsEmpty) + { + _si.columnInfo[k].HeaderMatchesProperty = headerMatchesProperty[k]; + } + startCol += columnWidths[k] + ScreenInfo.separatorCharacterCount; } } @@ -153,6 +158,9 @@ internal int GenerateHeader(string[] values, LineOutput lo) } else if (_header != null) { + string style = PSStyle.Instance.Formatting.TableHeader; + string reset = PSStyle.Instance.Reset; + foreach (string line in _header) { lo.WriteLine(line); @@ -164,7 +172,7 @@ internal int GenerateHeader(string[] values, LineOutput lo) _header = new List(); // generate the row with the header labels - GenerateRow(values, lo, true, null, lo.DisplayCells, _header); + GenerateRow(values, lo, true, null, lo.DisplayCells, _header, isHeader: true); // generate an array of "--" as header markers below // the column header labels @@ -191,14 +199,16 @@ internal int GenerateHeader(string[] values, LineOutput lo) breakLine[k] = StringUtil.DashPadding(count); } - GenerateRow(breakLine, lo, false, null, lo.DisplayCells, _header); + GenerateRow(breakLine, lo, false, null, lo.DisplayCells, _header, isHeader: true); return _header.Count; } - internal void GenerateRow(string[] values, LineOutput lo, bool multiLine, ReadOnlySpan alignment, DisplayCells dc, List generatedRows) + internal void GenerateRow(string[] values, LineOutput lo, bool multiLine, ReadOnlySpan alignment, DisplayCells dc, List generatedRows, bool isHeader = false) { if (_disabled) + { return; + } // build the current row alignment settings int cols = _si.columnInfo.Length; @@ -216,15 +226,22 @@ internal void GenerateRow(string[] values, LineOutput lo, bool multiLine, ReadOn for (int i = 0; i < currentAlignment.Length; i++) { if (alignment[i] == TextAlignment.Undefined) + { currentAlignment[i] = _si.columnInfo[i].alignment; + } else + { currentAlignment[i] = alignment[i]; + } } } + string style = PSStyle.Instance.Formatting.TableHeader; + string reset = PSStyle.Instance.Reset; + if (multiLine) { - foreach (string line in GenerateTableRow(values, currentAlignment, lo.DisplayCells)) + foreach (string line in GenerateTableRow(values, currentAlignment, lo.DisplayCells, isHeader)) { generatedRows?.Add(line); lo.WriteLine(line); @@ -232,13 +249,13 @@ internal void GenerateRow(string[] values, LineOutput lo, bool multiLine, ReadOn } else { - string line = GenerateRow(values, currentAlignment, dc); + string line = GenerateRow(values, currentAlignment, dc, isHeader); generatedRows?.Add(line); lo.WriteLine(line); } } - private string[] GenerateTableRow(string[] values, ReadOnlySpan alignment, DisplayCells ds) + private string[] GenerateTableRow(string[] values, ReadOnlySpan alignment, DisplayCells ds, bool isHeader) { // select the active columns (skip hidden ones) Span validColumnArray = _si.columnInfo.Length <= OutCommandInner.StackAllocThreshold ? stackalloc int[_si.columnInfo.Length] : new int[_si.columnInfo.Length]; @@ -267,8 +284,7 @@ private string[] GenerateTableRow(string[] values, ReadOnlySpan alignment, } // obtain a set of tokens for each field - scArray[k] = GenerateMultiLineRowField(values[validColumnArray[k]], validColumnArray[k], - alignment[validColumnArray[k]], ds, addPadding); + scArray[k] = GenerateMultiLineRowField(values[validColumnArray[k]], validColumnArray[k], alignment[validColumnArray[k]], ds, addPadding); // NOTE: the following padding operations assume that we // pad with a blank (or any character that ALWAYS maps to a single screen cell @@ -299,7 +315,9 @@ private string[] GenerateTableRow(string[] values, ReadOnlySpan alignment, for (int k = 0; k < scArray.Length; k++) { if (scArray[k].Count > screenRows) + { screenRows = scArray[k].Count; + } } // column headers can span multiple rows if the width of the column is shorter than the header text like: @@ -311,7 +329,6 @@ private string[] GenerateTableRow(string[] values, ReadOnlySpan alignment, // 1 2 3 // // To ensure we don't add whitespace to the end, we need to determine the last column in each row with content - System.Span lastColWithContent = screenRows <= OutCommandInner.StackAllocThreshold ? stackalloc int[screenRows] : new int[screenRows]; for (int row = 0; row < screenRows; row++) { @@ -366,17 +383,36 @@ private string[] GenerateTableRow(string[] values, ReadOnlySpan alignment, for (int row = 0; row < screenRows; row++) { StringBuilder sb = new StringBuilder(); + // for a given row, walk the columns for (int col = 0; col < scArray.Length; col++) { + string value = scArray[col][row]; + // if the column is the last column with content, we need to trim trailing whitespace, unless there is only one row if (col == lastColWithContent[row] && screenRows > 1) { - sb.Append(scArray[col][row].TrimEnd()); + value = value.TrimEnd(); } - else + + if (isHeader) { - sb.Append(scArray[col][row]); + if (_si.columnInfo[col].HeaderMatchesProperty) + { + sb.Append(PSStyle.Instance.Formatting.TableHeader); + } + else if (value.Length > 0) + { + // after the first column, each additional column starts with a whitespace for separation + value = value.Insert(col == 0 ? 0 : 1, PSStyle.Instance.Formatting.CustomTableHeaderLabel); + } + } + + sb.Append(value); + + if (isHeader) + { + sb.Append(PSStyle.Instance.Reset); } } @@ -403,7 +439,7 @@ private StringCollection GenerateMultiLineRowField(string val, int k, int alignm return sc; } - private string GenerateRow(string[] values, ReadOnlySpan alignment, DisplayCells dc) + private string GenerateRow(string[] values, ReadOnlySpan alignment, DisplayCells dc, bool isHeader) { StringBuilder sb = new StringBuilder(); @@ -437,11 +473,18 @@ private string GenerateRow(string[] values, ReadOnlySpan alignment, Display } } - sb.Append(GenerateRowField(values[k], _si.columnInfo[k].width, alignment[k], dc, addPadding)); - if (values[k].Contains(ESC)) + string rowField = GenerateRowField(values[k], _si.columnInfo[k].width, alignment[k], dc, addPadding); + if (isHeader) + { + sb.Append(PSStyle.Instance.Formatting.TableHeader); + } + + sb.Append(rowField); + + if (isHeader || (rowField is not null && rowField.Contains(ValueStringDecorated.ESC) && !rowField.AsSpan().TrimEnd().EndsWith(PSStyle.Instance.Reset))) { // Reset the console output if the content of this column contains ESC - sb.Append(ResetConsoleVt100Code); + sb.Append(PSStyle.Instance.Reset); } } @@ -452,14 +495,12 @@ private static string GenerateRowField(string val, int width, int alignment, Dis { // make sure the string does not have any embedded in it string s = StringManipulationHelper.TruncateAtNewLine(val); - - string currentValue = s; - int currentValueDisplayLength = dc.Length(currentValue); + int currentValueDisplayLength = dc.Length(s); if (currentValueDisplayLength < width) { // the string is shorter than the width of the column - // need to pad with with blanks to reach the desired width + // need to pad with blanks to reach the desired width int padCount = width - currentValueDisplayLength; switch (alignment) { @@ -511,18 +552,10 @@ private static string GenerateRowField(string val, int width, int alignment, Dis case TextAlignment.Right: { // get from "abcdef" to "...f" - int tailCount = dc.GetTailSplitLength(s, truncationDisplayLength); - s = s.Substring(s.Length - tailCount); - s = PSObjectHelper.Ellipsis + s; - } - - break; - - case TextAlignment.Center: - { - // get from "abcdef" to "a..." - s = s.Substring(0, dc.GetHeadSplitLength(s, truncationDisplayLength)); - s += PSObjectHelper.Ellipsis; + s = s.VtSubstring( + startOffset: dc.TruncateHead(s, truncationDisplayLength), + prependStr: PSObjectHelper.EllipsisStr, + appendStr: null); } break; @@ -531,8 +564,11 @@ private static string GenerateRowField(string val, int width, int alignment, Dis { // left align is the default // get from "abcdef" to "a..." - s = s.Substring(0, dc.GetHeadSplitLength(s, truncationDisplayLength)); - s += PSObjectHelper.Ellipsis; + s = s.VtSubstring( + startOffset: 0, + length: dc.TruncateTail(s, truncationDisplayLength), + prependStr: null, + appendStr: PSObjectHelper.EllipsisStr); } break; @@ -541,23 +577,12 @@ private static string GenerateRowField(string val, int width, int alignment, Dis else { // not enough space for the ellipsis, just truncate at the width - int len = width; - switch (alignment) { case TextAlignment.Right: { // get from "abcdef" to "f" - int tailCount = dc.GetTailSplitLength(s, len); - s = s.Substring(s.Length - tailCount, tailCount); - } - - break; - - case TextAlignment.Center: - { - // get from "abcdef" to "a" - s = s.Substring(0, dc.GetHeadSplitLength(s, len)); + s = s.VtSubstring(startOffset: dc.TruncateHead(s, width)); } break; @@ -566,7 +591,7 @@ private static string GenerateRowField(string val, int width, int alignment, Dis { // left align is the default // get from "abcdef" to "a" - s = s.Substring(0, dc.GetHeadSplitLength(s, len)); + s = s.VtSubstring(startOffset: 0, length: dc.TruncateTail(s, width)); } break; diff --git a/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshObjectUtil.cs b/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshObjectUtil.cs index 7760e86b13d..ea200bcb5f5 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshObjectUtil.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshObjectUtil.cs @@ -26,6 +26,7 @@ internal static class PSObjectHelper #endregion tracer internal const char Ellipsis = '\u2026'; + internal const string EllipsisStr = "\u2026"; internal static string PSObjectIsOfExactType(Collection typeNames) { @@ -120,8 +121,7 @@ internal static PSPropertyExpressionResult GetDisplayName(PSObject target, PSPro /// Object to extract the IEnumerable from. internal static IEnumerable GetEnumerable(object obj) { - PSObject mshObj = obj as PSObject; - if (mshObj != null) + if (obj is PSObject mshObj) { obj = mshObj.BaseObject; } @@ -207,8 +207,9 @@ private static string GetObjectName(object x, PSPropertyExpressionFactory expres /// Expression factory to create PSPropertyExpression. /// Limit on IEnumerable enumeration. /// Stores errors during string conversion. + /// Determine if to format floating point numbers using current culture. /// String representation. - internal static string SmartToString(PSObject so, PSPropertyExpressionFactory expressionFactory, int enumerationLimit, StringFormatError formatErrorObject) + internal static string SmartToString(PSObject so, PSPropertyExpressionFactory expressionFactory, int enumerationLimit, StringFormatError formatErrorObject, bool formatFloat = false) { if (so == null) return string.Empty; @@ -226,8 +227,7 @@ internal static string SmartToString(PSObject so, PSPropertyExpressionFactory ex IEnumerator enumerator = e.GetEnumerator(); if (enumerator != null) { - IBlockingEnumerator be = enumerator as IBlockingEnumerator; - if (be != null) + if (enumerator is IBlockingEnumerator be) { while (be.MoveNext(false)) { @@ -293,7 +293,23 @@ internal static string SmartToString(PSObject so, PSPropertyExpressionFactory ex return sb.ToString(); } - // take care of the case there is no base object + if (formatFloat && so.BaseObject is not null) + { + // format numbers using the current culture + if (so.BaseObject is double dbl) + { + return dbl.ToString("F"); + } + else if (so.BaseObject is float f) + { + return f.ToString("F"); + } + else if (so.BaseObject is decimal d) + { + return d.ToString("F"); + } + } + return so.ToString(); } catch (Exception e) when (e is ExtendedTypeSystemException || e is InvalidOperationException) @@ -332,43 +348,49 @@ internal static string FormatField(FieldFormattingDirective directive, object va StringFormatError formatErrorObject, PSPropertyExpressionFactory expressionFactory) { PSObject so = PSObjectHelper.AsPSObject(val); - if (directive != null && !string.IsNullOrEmpty(directive.formatString)) + bool isTable = false; + if (directive is not null) { - // we have a formatting directive, apply it - // NOTE: with a format directive, we do not make any attempt - // to deal with IEnumerable - try + isTable = directive.isTable; + if (!string.IsNullOrEmpty(directive.formatString)) { - // use some heuristics to determine if we have "composite formatting" - // 2004/11/16-JonN This is heuristic but should be safe enough - if (directive.formatString.Contains("{0") || directive.formatString.Contains('}')) + // we have a formatting directive, apply it + // NOTE: with a format directive, we do not make any attempt + // to deal with IEnumerable + try { - // we do have it, just use it - return string.Format(CultureInfo.CurrentCulture, directive.formatString, so); + // use some heuristics to determine if we have "composite formatting" + // 2004/11/16-JonN This is heuristic but should be safe enough + if (directive.formatString.Contains("{0") || directive.formatString.Contains('}')) + { + // we do have it, just use it + return string.Format(CultureInfo.CurrentCulture, directive.formatString, so); + } + // we fall back to the PSObject's IFormattable.ToString() + // pass a null IFormatProvider + return so.ToString(directive.formatString, formatProvider: null); } - // we fall back to the PSObject's IFormattable.ToString() - // pass a null IFormatProvider - return so.ToString(directive.formatString, null); - } - catch (Exception e) // 2004/11/17-JonN This covers exceptions thrown in - // string.Format and PSObject.ToString(). - // I think we can swallow these. - { - // NOTE: we catch all the exceptions, since we do not know - // what the underlying object access would throw - if (formatErrorObject != null) + catch (Exception e) // 2004/11/17-JonN This covers exceptions thrown in + // string.Format and PSObject.ToString(). + // I think we can swallow these. { - formatErrorObject.sourceObject = so; - formatErrorObject.exception = e; - formatErrorObject.formatString = directive.formatString; - return string.Empty; + // NOTE: we catch all the exceptions, since we do not know + // what the underlying object access would throw + if (formatErrorObject is not null) + { + formatErrorObject.sourceObject = so; + formatErrorObject.exception = e; + formatErrorObject.formatString = directive.formatString; + return string.Empty; + } } } } + // we do not have a formatting directive or we failed the formatting (fallback) // but we did not report as an error; // this call would deal with IEnumerable if the object implements it - return PSObjectHelper.SmartToString(so, expressionFactory, enumerationLimit, formatErrorObject); + return PSObjectHelper.SmartToString(so, expressionFactory, enumerationLimit, formatErrorObject, isTable); } private static PSMemberSet MaskDeserializedAndGetStandardMembers(PSObject so) @@ -394,22 +416,18 @@ private static PSMemberSet MaskDeserializedAndGetStandardMembers(PSObject so) private static List GetDefaultPropertySet(PSMemberSet standardMembersSet) { - if (standardMembersSet != null) + if (standardMembersSet != null && standardMembersSet.Members[TypeTable.DefaultDisplayPropertySet] is PSPropertySet defaultDisplayPropertySet) { - PSPropertySet defaultDisplayPropertySet = standardMembersSet.Members[TypeTable.DefaultDisplayPropertySet] as PSPropertySet; - if (defaultDisplayPropertySet != null) + List retVal = new List(); + foreach (string prop in defaultDisplayPropertySet.ReferencedPropertyNames) { - List retVal = new List(); - foreach (string prop in defaultDisplayPropertySet.ReferencedPropertyNames) + if (!string.IsNullOrEmpty(prop)) { - if (!string.IsNullOrEmpty(prop)) - { - retVal.Add(new PSPropertyExpression(prop)); - } + retVal.Add(new PSPropertyExpression(prop)); } - - return retVal; } + + return retVal; } return new List(); @@ -433,21 +451,17 @@ internal static List GetDefaultPropertySet(PSObject so) private static PSPropertyExpression GetDefaultNameExpression(PSMemberSet standardMembersSet) { - if (standardMembersSet != null) + if (standardMembersSet != null && standardMembersSet.Members[TypeTable.DefaultDisplayProperty] is PSNoteProperty defaultDisplayProperty) { - PSNoteProperty defaultDisplayProperty = standardMembersSet.Members[TypeTable.DefaultDisplayProperty] as PSNoteProperty; - if (defaultDisplayProperty != null) + string expressionString = defaultDisplayProperty.Value.ToString(); + if (string.IsNullOrEmpty(expressionString)) { - string expressionString = defaultDisplayProperty.Value.ToString(); - if (string.IsNullOrEmpty(expressionString)) - { - // invalid data, the PSObject is empty - return null; - } - else - { - return new PSPropertyExpression(expressionString); - } + // invalid data, the PSObject is empty + return null; + } + else + { + return new PSPropertyExpression(expressionString); } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/Utilities/Mshexpression.cs b/src/System.Management.Automation/FormatAndOutput/common/Utilities/Mshexpression.cs index 68921ddb0e8..552d511fd4e 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/Utilities/Mshexpression.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/Utilities/Mshexpression.cs @@ -215,8 +215,7 @@ public List ResolveNames(PSObject target, bool expand) foreach (PSMemberInfo member in members) { // it can be a property set - PSPropertySet propertySet = member as PSPropertySet; - if (propertySet != null) + if (member is PSPropertySet propertySet) { if (expand) { @@ -326,15 +325,12 @@ private PSPropertyExpressionResult GetValue(PSObject target, bool eatExceptions) } else { - if (_getValueDynamicSite == null) - { - _getValueDynamicSite = - CallSite>.Create( - PSGetMemberBinder.Get( - _stringValue, - classScope: (Type)null, - @static: false)); - } + _getValueDynamicSite ??= + CallSite>.Create( + PSGetMemberBinder.Get( + _stringValue, + classScope: (Type)null, + @static: false)); result = _getValueDynamicSite.Target.Invoke(_getValueDynamicSite, target); } diff --git a/src/System.Management.Automation/FormatAndOutput/out-console/ConsoleLineOutput.cs b/src/System.Management.Automation/FormatAndOutput/out-console/ConsoleLineOutput.cs index 4750b099b01..f828b673de0 100644 --- a/src/System.Management.Automation/FormatAndOutput/out-console/ConsoleLineOutput.cs +++ b/src/System.Management.Automation/FormatAndOutput/out-console/ConsoleLineOutput.cs @@ -1,11 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -// NOTE: define this if you want to test the output on US machine and ASCII -// characters -//#define TEST_MULTICELL_ON_SINGLE_CELL_LOCALE - using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.Management.Automation; using System.Management.Automation.Internal; @@ -17,105 +14,50 @@ namespace Microsoft.PowerShell.Commands.Internal.Format { -#if TEST_MULTICELL_ON_SINGLE_CELL_LOCALE - - /// - /// Test class to provide easily overridable behavior for testing on US machines - /// using US data. - /// NOTE: the class just forces any uppercase letter [A-Z] to be prepended - /// with an underscore (e.g. "A" becomes "_A", but "a" stays the same) - /// - internal class DisplayCellsTest : DisplayCells - { - internal override int Length(string str, int offset) - { - int len = 0; - for (int k = offset; k < str.Length; k++) - { - len += this.Length(str[k]); - } - - return len; - } - - internal override int Length(char character) - { - if (character >= 'A' && character <= 'Z') - return 2; - return 1; - } - - internal override int GetHeadSplitLength(string str, int offset, int displayCells) - { - return GetSplitLengthInternalHelper(str, offset, displayCells, true); - } - - internal override int GetTailSplitLength(string str, int offset, int displayCells) - { - return GetSplitLengthInternalHelper(str, offset, displayCells, false); - } - - internal string GenerateTestString(string str) - { - StringBuilder sb = new StringBuilder(); - for (int k = 0; k < str.Length; k++) - { - char ch = str[k]; - if (this.Length(ch) == 2) - { - sb.Append('_'); - } - - sb.Append(ch); - } - - return sb.ToString(); - } - - } -#endif - /// /// Tear off class. /// - internal class DisplayCellsPSHost : DisplayCells + internal class DisplayCellsHost : DisplayCells { - internal DisplayCellsPSHost(PSHostRawUserInterface rawUserInterface) + internal DisplayCellsHost(PSHostRawUserInterface rawUserInterface) { _rawUserInterface = rawUserInterface; } internal override int Length(string str, int offset) { - Dbg.Assert(offset >= 0, "offset >= 0"); - Dbg.Assert(string.IsNullOrEmpty(str) || (offset < str.Length), "offset < str.Length"); - - try + if (string.IsNullOrEmpty(str)) { - return _rawUserInterface.LengthInBufferCells(str, offset); + return 0; } - catch + + if (offset < 0 || offset >= str.Length) { - // thrown when external host rawui is not implemented, in which case - // we will fallback to the default value. + throw PSTraceSource.NewArgumentException(nameof(offset)); } - return string.IsNullOrEmpty(str) ? 0 : str.Length - offset; - } - - internal override int Length(string str) - { try { - return _rawUserInterface.LengthInBufferCells(str); + var valueStrDec = new ValueStringDecorated(str); + if (valueStrDec.IsDecorated) + { + str = valueStrDec.ToString(OutputRendering.PlainText); + } + + int length = 0; + for (; offset < str.Length; offset++) + { + length += _rawUserInterface.LengthInBufferCells(str[offset]); + } + + return length; } catch { // thrown when external host rawui is not implemented, in which case // we will fallback to the default value. + return base.Length(str, offset); } - - return string.IsNullOrEmpty(str) ? 0 : str.Length; } internal override int Length(char character) @@ -128,19 +70,8 @@ internal override int Length(char character) { // thrown when external host rawui is not implemented, in which case // we will fallback to the default value. + return base.Length(character); } - - return 1; - } - - internal override int GetHeadSplitLength(string str, int offset, int displayCells) - { - return GetSplitLengthInternalHelper(str, offset, displayCells, true); - } - - internal override int GetTailSplitLength(string str, int offset, int displayCells) - { - return GetSplitLengthInternalHelper(str, offset, displayCells, false); } private readonly PSHostRawUserInterface _rawUserInterface; @@ -156,6 +87,11 @@ internal sealed class ConsoleLineOutput : LineOutput internal static readonly PSTraceSource tracer = PSTraceSource.GetTracer("ConsoleLineOutput", "ConsoleLineOutput"); #endregion tracer + /// + /// The default buffer cell calculation already works for the PowerShell console host and Visual studio code host. + /// + private static readonly HashSet s_psHost = new(StringComparer.Ordinal) { "ConsoleHost", "Visual Studio Code Host" }; + #region LineOutput implementation /// /// The # of columns is just the width of the screen buffer (not the @@ -234,12 +170,13 @@ internal override DisplayCells DisplayCells get { CheckStopProcessing(); - if (_displayCellsPSHost != null) + if (_displayCellsHost != null) { - return _displayCellsPSHost; + return _displayCellsHost; } + // fall back if we do not have a Msh host specific instance - return _displayCellsPSHost; + return _displayCellsDefault; } } #endregion @@ -247,39 +184,38 @@ internal override DisplayCells DisplayCells /// /// Constructor for the ConsoleLineOutput. /// - /// PSHostUserInterface to wrap. + /// PSHostUserInterface to wrap. /// True if we require prompting for page breaks. /// Error context to throw exceptions. - internal ConsoleLineOutput(PSHostUserInterface hostConsole, bool paging, TerminatingErrorContext errorContext) + internal ConsoleLineOutput(PSHost host, bool paging, TerminatingErrorContext errorContext) { - if (hostConsole == null) - throw PSTraceSource.NewArgumentNullException(nameof(hostConsole)); + if (host == null) + { + throw PSTraceSource.NewArgumentNullException(nameof(host)); + } + if (errorContext == null) + { throw PSTraceSource.NewArgumentNullException(nameof(errorContext)); + } - _console = hostConsole; + _console = host.UI; _errorContext = errorContext; if (paging) { tracer.WriteLine("paging is needed"); - // if we need to do paging, instantiate a prompt handler - // that will take care of the screen interaction + + // If we need to do paging, instantiate a prompt handler that will take care of the screen interaction string promptString = StringUtil.Format(FormatAndOut_out_xxx.ConsoleLineOutput_PagingPrompt); _prompt = new PromptHandler(promptString, this); } - PSHostRawUserInterface raw = _console.RawUI; - if (raw != null) + if (!s_psHost.Contains(host.Name) && _console.RawUI is not null) { - tracer.WriteLine("there is a valid raw interface"); -#if TEST_MULTICELL_ON_SINGLE_CELL_LOCALE - // create a test instance with fake behavior - this._displayCellsPSHost = new DisplayCellsTest(); -#else // set only if we have a valid raw interface - _displayCellsPSHost = new DisplayCellsPSHost(raw); -#endif + tracer.WriteLine("there is a valid raw interface"); + _displayCellsHost = new DisplayCellsHost(_console.RawUI); } // instantiate the helper to do the line processing when ILineOutput.WriteXXX() is called @@ -302,9 +238,6 @@ internal ConsoleLineOutput(PSHostUserInterface hostConsole, bool paging, Termina /// String to write. private void OnWriteLine(string s) { -#if TEST_MULTICELL_ON_SINGLE_CELL_LOCALE - s = ((DisplayCellsTest)this._displayCellsPSHost).GenerateTestString(s); -#endif // Do any default transcription. _console.TranscribeResult(s); @@ -349,10 +282,6 @@ private void OnWriteLine(string s) /// String to write. private void OnWrite(string s) { -#if TEST_MULTICELL_ON_SINGLE_CELL_LOCALE - s = ((DisplayCellsTest)this._displayCellsPSHost).GenerateTestString(s); -#endif - switch (this.WriteStream) { case WriteStreamType.Error: @@ -466,7 +395,7 @@ private bool NeedToPrompt /// /// Object to manage prompting. /// - private class PromptHandler + private sealed class PromptHandler { /// /// Prompt handler with the given prompt. @@ -591,7 +520,7 @@ internal PromptResponse PromptUser(PSHostUserInterface console) private readonly PromptHandler _prompt = null; /// - /// Conter for the # of lines written when prompting is on. + /// Counter for the # of lines written when prompting is on. /// private long _linesWritten = 0; @@ -601,14 +530,14 @@ internal PromptResponse PromptUser(PSHostUserInterface console) private bool _disableLineWrittenEvent = false; /// - /// Refecence to the PSHostUserInterface interface we use. + /// Reference to the PSHostUserInterface interface we use. /// private readonly PSHostUserInterface _console = null; /// /// Msh host specific string manipulation helper. /// - private readonly DisplayCells _displayCellsPSHost; + private readonly DisplayCells _displayCellsHost; /// /// Reference to error context to throw Msh exceptions. diff --git a/src/System.Management.Automation/FormatAndOutput/out-console/OutConsole.cs b/src/System.Management.Automation/FormatAndOutput/out-console/OutConsole.cs index 2582a1e68db..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 /// /// Null sink to absorb pipeline output. /// - [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 { @@ -49,7 +49,7 @@ public class OutDefaultCommand : FrontEndCommandBase /// invoked via API. This ensures that the objects pass through the formatting and output /// system, but can still make it to the API consumer. /// - [Parameter()] + [Parameter] public SwitchParameter Transcript { get; set; } /// @@ -65,14 +65,11 @@ public OutDefaultCommand() /// protected override void BeginProcessing() { - PSHostUserInterface console = this.Host.UI; - ConsoleLineOutput lineOutput = new ConsoleLineOutput(console, false, new TerminatingErrorContext(this)); + var lineOutput = new ConsoleLineOutput(Host, false, new TerminatingErrorContext(this)); ((OutputManagerInner)this.implementation).LineOutput = lineOutput; - MshCommandRuntime mrt = this.CommandRuntime as MshCommandRuntime; - - if (mrt != null) + if (this.CommandRuntime is MshCommandRuntime mrt) { mrt.MergeUnclaimedPreviousErrorResults = true; } @@ -206,8 +203,7 @@ public SwitchParameter Paging /// protected override void BeginProcessing() { - PSHostUserInterface console = this.Host.UI; - ConsoleLineOutput lineOutput = new ConsoleLineOutput(console, _paging, new TerminatingErrorContext(this)); + var lineOutput = new ConsoleLineOutput(Host, _paging, new TerminatingErrorContext(this)); ((OutputManagerInner)this.implementation).LineOutput = lineOutput; base.BeginProcessing(); diff --git a/src/System.Management.Automation/GlobalSuppressions.cs b/src/System.Management.Automation/GlobalSuppressions.cs new file mode 100644 index 00000000000..99e9a7c98ac --- /dev/null +++ b/src/System.Management.Automation/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage( + "Style", + "IDE0044:Add readonly modifier", + Justification = "see src/System.Management.Automation/engine/ComInterop/README.md", + Scope = "NamespaceAndDescendants", + Target = "~N:System.Management.Automation.ComInterop")] diff --git a/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.cs b/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.cs new file mode 100644 index 00000000000..29325d27899 --- /dev/null +++ b/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.cs @@ -0,0 +1,136 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Globalization; +using Microsoft.CodeAnalysis; + +namespace SMA +{ + /// + /// Source Code Generator to create partial PSVersionInfo class. + /// + [Generator] + public class PSVersionInfoGenerator : IIncrementalGenerator + { + /// + /// Not used. + /// + /// Generator initialization context. + public void Initialize(IncrementalGeneratorInitializationContext context) + { + IncrementalValueProvider buildOptionsProvider = context.AnalyzerConfigOptionsProvider + .Select(static (provider, _) => + { + provider.GlobalOptions.TryGetValue("build_property.ProductVersion", out var productVersion); + provider.GlobalOptions.TryGetValue("build_property.PSCoreBuildVersion", out var mainVersion); + provider.GlobalOptions.TryGetValue("build_property.PowerShellVersion", out var gitDescribe); + provider.GlobalOptions.TryGetValue("build_property.ReleaseTag", out var releaseTag); + + BuildOptions options = new() + { + ProductVersion = productVersion ?? string.Empty, + MainVersion = mainVersion ?? string.Empty, + GitDescribe = gitDescribe ?? string.Empty, + ReleaseTag = releaseTag ?? string.Empty + }; + + return options; + }); + + context.RegisterSourceOutput( + buildOptionsProvider, + static (context, buildOptions) => + { + string gitCommitId = string.IsNullOrEmpty(buildOptions.ReleaseTag) ? buildOptions.GitDescribe : buildOptions.ReleaseTag; + if (gitCommitId.StartsWith("v")) + { + gitCommitId = gitCommitId.Substring(1); + } + + var versions = ParsePSVersion(buildOptions.MainVersion); + string result = string.Format( + CultureInfo.InvariantCulture, + SourceTemplate, + buildOptions.ProductVersion, + gitCommitId, + versions.major, + versions.minor, + versions.patch, + versions.preReleaseLabel); + + // We must use specific file name suffix (*.g.cs,*.g, *.i.cs, *.generated.cs, *.designer.cs) + // so that Roslyn analyzers skip the file. + context.AddSource("PSVersionInfo.g.cs", result); + }); + } + + private struct BuildOptions + { + public string ProductVersion; + public string MainVersion; + public string GitDescribe; + public string ReleaseTag; + } + + // We must put " +// This file is auto-generated by PSVersionInfoGenerator. +// + +namespace System.Management.Automation +{{ + public static partial class PSVersionInfo + {{ + // Defined in 'PowerShell.Common.props' as 'ProductVersion' + // Example: + // - when built from a commit: ProductVersion = '7.3.0-preview.8 Commits: 29 SHA: 52c6b...' + // - when built from a preview release tag: ProductVersion = '7.3.0-preview.8 SHA: f1ec9...' + // - when built from a stable release tag: ProductVersion = '7.3.0 SHA: f1ec9...' + internal const string ProductVersion = ""{0}""; + + // The git commit id that the build is based off. + // Defined in 'PowerShell.Common.props' as 'PowerShellVersion' or 'ReleaseTag', + // depending on whether the '-ReleaseTag' is specified when building. + // Example: + // - when built from a commit: GitCommitId = '7.3.0-preview.8-29-g52c6b...' + // - when built from a preview release tag: GitCommitId = '7.3.0-preview.8' + // - when built from a stable release tag: GitCommitId = '7.3.0' + internal const string GitCommitId = ""{1}""; + + // The PowerShell version components. + // The version string is defined in 'PowerShell.Common.props' as 'PSCoreBuildVersion', + // but we break it into components to save the overhead of parsing at runtime. + // Example: + // - '7.3.0-preview.8' for preview release or private build + // - '7.3.0' for stable release + private const int Version_Major = {2}; + private const int Version_Minor = {3}; + private const int Version_Patch = {4}; + private const string Version_Label = ""{5}""; + }} +}}"; + + private static (int major, int minor, int patch, string preReleaseLabel) ParsePSVersion(string mainVersion) + { + // We only handle the pre-defined PSVersion format here, e.g. 7.x.x or 7.x.x-preview.x + int dashIndex = mainVersion.IndexOf('-'); + bool hasLabel = dashIndex != -1; + string preReleaseLabel = hasLabel ? mainVersion.Substring(dashIndex + 1) : string.Empty; + + if (hasLabel) + { + mainVersion = mainVersion.Substring(0, dashIndex); + } + + int majorEnd = mainVersion.IndexOf('.'); + int minorEnd = mainVersion.LastIndexOf('.'); + + int major = int.Parse(mainVersion.Substring(0, majorEnd), NumberStyles.Integer, CultureInfo.InvariantCulture); + int minor = int.Parse(mainVersion.Substring(majorEnd + 1, minorEnd - majorEnd - 1), NumberStyles.Integer, CultureInfo.InvariantCulture); + int patch = int.Parse(mainVersion.Substring(minorEnd + 1), NumberStyles.Integer, CultureInfo.InvariantCulture); + + return (major, minor, patch, preReleaseLabel); + } + } +} diff --git a/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj b/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj new file mode 100644 index 00000000000..1e53a35f966 --- /dev/null +++ b/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj @@ -0,0 +1,20 @@ + + + Generate code for SMA using source generator + SMA.Generator + + + + + netstandard2.0 + 13.0 + true + true + enable + + + + + + + diff --git a/src/System.Management.Automation/System.Management.Automation.csproj b/src/System.Management.Automation/System.Management.Automation.csproj index 98231ee66bc..ad3ce6e46ed 100644 --- a/src/System.Management.Automation/System.Management.Automation.csproj +++ b/src/System.Management.Automation/System.Management.Automation.csproj @@ -2,33 +2,54 @@ PowerShell's System.Management.Automation project - $(NoWarn);CS1570;CS1734;CA1416 + $(NoWarn);CS1570;CS1734;CA1416;CA2022 System.Management.Automation + + + true + gen\SourceGenerated + + + + + + + + + + + + - + - + - - - - - - - - - - + + + + + + + + + + + + + - - + + + + @@ -36,6 +57,9 @@ + + + @@ -44,7 +68,6 @@ - @@ -63,9 +86,11 @@ + + + - @@ -73,4 +98,7 @@ + + + diff --git a/src/System.Management.Automation/cimSupport/cmdletization/EnumWriter.cs b/src/System.Management.Automation/cimSupport/cmdletization/EnumWriter.cs index 66649420101..77328bf0257 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/EnumWriter.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/EnumWriter.cs @@ -23,8 +23,8 @@ private static ModuleBuilder CreateModuleBuilder() return mb; } - private static Lazy s_moduleBuilder = new(CreateModuleBuilder, isThreadSafe: true); - private static object s_moduleBuilderUsageLock = new(); + private static readonly Lazy s_moduleBuilder = new(CreateModuleBuilder, isThreadSafe: true); + private static readonly object s_moduleBuilderUsageLock = new(); internal static string GetEnumFullName(EnumMetadataEnum enumMetadata) { diff --git a/src/System.Management.Automation/cimSupport/cmdletization/MethodInvocationInfo.cs b/src/System.Management.Automation/cimSupport/cmdletization/MethodInvocationInfo.cs index 1640dec33b9..c2d30f6610e 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/MethodInvocationInfo.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/MethodInvocationInfo.cs @@ -21,8 +21,8 @@ public sealed class MethodInvocationInfo /// Return value of the method (ok to pass if the method doesn't return anything). public MethodInvocationInfo(string name, IEnumerable parameters, MethodParameter returnValue) { - if (name == null) throw new ArgumentNullException(nameof(name)); - if (parameters == null) throw new ArgumentNullException(nameof(parameters)); + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(parameters); // returnValue can be null MethodName = name; @@ -62,20 +62,17 @@ internal IEnumerable GetArgumentsOfType() where T : class continue; } - var objectInstance = methodParameter.Value as T; - if (objectInstance != null) + if (methodParameter.Value is T objectInstance) { result.Add(objectInstance); continue; } - var objectInstanceArray = methodParameter.Value as IEnumerable; - if (objectInstanceArray != null) + if (methodParameter.Value is IEnumerable objectInstanceArray) { foreach (object element in objectInstanceArray) { - var objectInstance2 = element as T; - if (objectInstance2 != null) + if (element is T objectInstance2) { result.Add(objectInstance2); } diff --git a/src/System.Management.Automation/cimSupport/cmdletization/ObjectModelWrapper.cs b/src/System.Management.Automation/cimSupport/cmdletization/ObjectModelWrapper.cs index a840ca527fd..082d7218023 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/ObjectModelWrapper.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/ObjectModelWrapper.cs @@ -17,43 +17,26 @@ public abstract class CmdletAdapter { internal void Initialize(PSCmdlet cmdlet, string className, string classVersion, IDictionary privateData) { - if (cmdlet == null) - { - throw new ArgumentNullException(nameof(cmdlet)); - } - - if (string.IsNullOrEmpty(className)) - { - throw new ArgumentNullException(nameof(className)); - } + ArgumentNullException.ThrowIfNull(cmdlet); + ArgumentException.ThrowIfNullOrEmpty(className); - if (classVersion == null) // possible and ok to have classVersion==string.Empty - { - throw new ArgumentNullException(nameof(classVersion)); - } - - if (privateData == null) - { - throw new ArgumentNullException(nameof(privateData)); - } + // possible and ok to have classVersion==string.Empty + ArgumentNullException.ThrowIfNull(classVersion); + ArgumentNullException.ThrowIfNull(privateData); _cmdlet = cmdlet; _className = className; _classVersion = classVersion; _privateData = privateData; - var compiledScript = this.Cmdlet as PSScriptCmdlet; - if (compiledScript != null) + if (this.Cmdlet is PSScriptCmdlet compiledScript) { compiledScript.StoppingEvent += delegate { this.StopProcessing(); }; compiledScript.DisposingEvent += delegate { var disposable = this as IDisposable; - if (disposable != null) - { - disposable.Dispose(); - } + disposable?.Dispose(); }; } } diff --git a/src/System.Management.Automation/cimSupport/cmdletization/ScriptWriter.cs b/src/System.Management.Automation/cimSupport/cmdletization/ScriptWriter.cs index a663160645c..e0bbcaa3969 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/ScriptWriter.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/ScriptWriter.cs @@ -100,14 +100,12 @@ internal ScriptWriter( } catch (InvalidOperationException e) { - XmlSchemaException schemaException = e.InnerException as XmlSchemaException; - if (schemaException != null) + if (e.InnerException is XmlSchemaException schemaException) { throw new XmlException(schemaException.Message, schemaException, schemaException.LineNumber, schemaException.LinePosition); } - XmlException xmlException = e.InnerException as XmlException; - if (xmlException != null) + if (e.InnerException is XmlException xmlException) { throw xmlException; } @@ -240,7 +238,7 @@ private static string GetCmdletAttributes(CommonCmdletMetadata cmdletMetadata) StringBuilder attributes = new(150); if (cmdletMetadata.Aliases != null) { - attributes.Append("[Alias('" + string.Join("','", cmdletMetadata.Aliases.Select(alias => CodeGeneration.EscapeSingleQuotedStringContent(alias))) + "')]"); + attributes.Append("[Alias('" + string.Join("','", cmdletMetadata.Aliases.Select(static alias => CodeGeneration.EscapeSingleQuotedStringContent(alias))) + "')]"); } if (cmdletMetadata.Obsolete != null) @@ -249,7 +247,7 @@ private static string GetCmdletAttributes(CommonCmdletMetadata cmdletMetadata) ? ("'" + CodeGeneration.EscapeSingleQuotedStringContent(cmdletMetadata.Obsolete.Message) + "'") : string.Empty; string newline = (attributes.Length > 0) ? Environment.NewLine : string.Empty; - attributes.AppendFormat(CultureInfo.InvariantCulture, "{0}[Obsolete({1})]", newline, obsoleteMsg); + attributes.Append(CultureInfo.InvariantCulture, $"{newline}[Obsolete({obsoleteMsg})]"); } return attributes.ToString(); @@ -386,7 +384,7 @@ private List GetMethodParameterSets(StaticCmdletMetadata staticCmdlet) return new List(parameterSetNames.Keys); } - private Dictionary _staticMethodMetadataToUniqueId = new(); + private readonly Dictionary _staticMethodMetadataToUniqueId = new(); private string GetMethodParameterSet(CommonMethodMetadata methodMetadata) { @@ -517,13 +515,53 @@ private Type GetDotNetType(TypeMetadata typeMetadata) Dbg.Assert(typeMetadata != null, "Caller should verify typeMetadata != null"); string psTypeText; - List matchingEnums = (_cmdletizationMetadata.Enums ?? Enumerable.Empty()) - .Where(e => Regex.IsMatch( - typeMetadata.PSType, - string.Format(CultureInfo.InvariantCulture, @"\b{0}\b", Regex.Escape(e.EnumName)), - RegexOptions.CultureInvariant)) - .ToList(); - EnumMetadataEnum matchingEnum = matchingEnums.Count == 1 ? matchingEnums[0] : null; + EnumMetadataEnum matchingEnum = null; + + if (_cmdletizationMetadata.Enums is not null) + { + string psType = typeMetadata.PSType; + foreach (EnumMetadataEnum e in _cmdletizationMetadata.Enums) + { + int index = psType.IndexOf(e.EnumName, StringComparison.Ordinal); + if (index == -1) + { + // Fast return if 'PSType' doesn't contain the enum name at all. + continue; + } + + bool matchFound = false; + if (index == 0) + { + // Handle 2 common cases here (cover over 99% of how enum name is used in 'PSType'): + // - 'PSType' is exactly the enum name. + // - 'PSType' is the array format of the enum. + ReadOnlySpan remains = psType.AsSpan(e.EnumName.Length); + matchFound = remains.Length is 0 || remains.Equals("[]", StringComparison.Ordinal); + } + + if (!matchFound) + { + // Now we have to fall back to the expensive regular expression matching, because 'PSType' + // could be a composite type like 'Nullable' or 'Dictionary', + // but we don't want the case where the enum name is part of another type's name. + matchFound = Regex.IsMatch(psType, $@"\b{Regex.Escape(e.EnumName)}\b"); + } + + if (matchFound) + { + if (matchingEnum is null) + { + matchingEnum = e; + continue; + } + + // If more than one matching enum names were found, we treat it as no match found. + matchingEnum = null; + break; + } + } + } + if (matchingEnum != null) { psTypeText = typeMetadata.PSType.Replace(matchingEnum.EnumName, EnumWriter.GetEnumFullName(matchingEnum)); @@ -788,8 +826,7 @@ private void SetParameters(CommandMetadata commandMetadata, params Dictionary p.Items != null)) + foreach (PropertyMetadata property in getCmdletParameters.QueryableProperties.Where(static p => p.Items != null)) { for (int i = 0; i < property.Items.Length; i++) { @@ -1681,7 +1730,7 @@ private void GenerateQueryParametersProcessing( if (getCmdletParameters.QueryableAssociations != null) { - foreach (Association association in getCmdletParameters.QueryableAssociations.Where(a => a.AssociatedInstance != null)) + foreach (Association association in getCmdletParameters.QueryableAssociations.Where(static a => a.AssociatedInstance != null)) { ParameterMetadata parameterMetadata = GenerateAssociationClause( commonParameterSets, queryParameterSets, methodParameterSets, association, association.AssociatedInstance, output); @@ -1983,7 +2032,7 @@ private void WriteCmdlet(TextWriter output, InstanceCmdletMetadata instanceCmdle } else if (queryParameterSets.Count == 1) { - commandMetadata.DefaultParameterSetName = queryParameterSets.Single(); + commandMetadata.DefaultParameterSetName = queryParameterSets[0]; } AddPassThruParameter(commonParameters, instanceCmdlet); @@ -2100,7 +2149,7 @@ private void WriteGetCmdlet(TextWriter output) /* 1 */ CodeGeneration.EscapeSingleQuotedStringContent(commandMetadata.Name)); } - private static object s_enumCompilationLock = new(); + private static readonly object s_enumCompilationLock = new(); private static void CompileEnum(EnumMetadataEnum enumMetadata) { @@ -2203,7 +2252,7 @@ internal void ReportExportedCommands(PSModuleInfo moduleInfo, string prefix) { cmdletMetadatas = cmdletMetadatas.Concat( - _cmdletizationMetadata.Class.InstanceCmdlets.Cmdlet.Select(c => c.CmdletMetadata)); + _cmdletizationMetadata.Class.InstanceCmdlets.Cmdlet.Select(static c => c.CmdletMetadata)); } } @@ -2211,7 +2260,7 @@ internal void ReportExportedCommands(PSModuleInfo moduleInfo, string prefix) { cmdletMetadatas = cmdletMetadatas.Concat( - _cmdletizationMetadata.Class.StaticCmdlets.Select(c => c.CmdletMetadata)); + _cmdletizationMetadata.Class.StaticCmdlets.Select(static c => c.CmdletMetadata)); } foreach (CommonCmdletMetadata cmdletMetadata in cmdletMetadatas) 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 /// [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 } /// - [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 /// [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 } /// - [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 } /// - [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 } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string CmdletAdapter { get @@ -171,7 +171,7 @@ public string CmdletAdapter } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string ClassName { get @@ -186,7 +186,7 @@ public string ClassName } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string ClassVersion { get @@ -204,7 +204,7 @@ public string ClassVersion /// [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 } /// - [System.Xml.Serialization.XmlElementAttribute("Cmdlet")] + [System.Xml.Serialization.XmlElement("Cmdlet")] public InstanceCmdletMetadata[] Cmdlet { get @@ -260,7 +260,7 @@ public InstanceCmdletMetadata[] Cmdlet /// [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; /// - [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 } /// - [System.Xml.Serialization.XmlArrayItemAttribute(IsNullable = false)] + [System.Xml.Serialization.XmlArrayItem(IsNullable = false)] public Association[] QueryableAssociations { get @@ -302,7 +302,7 @@ public Association[] QueryableAssociations } /// - [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 } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string DefaultCmdletParameterSet { get @@ -335,7 +335,7 @@ public string DefaultCmdletParameterSet /// [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 } /// - [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 } /// - [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 } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string PropertyName { get @@ -414,7 +414,7 @@ public string PropertyName /// [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; /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string PSType { get @@ -437,7 +437,7 @@ public string PSType } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string ETSType { get @@ -455,7 +455,7 @@ public string ETSType /// [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 } /// - [System.Xml.Serialization.XmlAttributeAttribute("Association")] + [System.Xml.Serialization.XmlAttribute("Association")] public string Association1 { get @@ -496,7 +496,7 @@ public string Association1 } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string SourceRole { get @@ -511,7 +511,7 @@ public string SourceRole } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string ResultRole { get @@ -529,7 +529,7 @@ public string ResultRole /// [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 /// [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; /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool ErrorOnNoMatch { get @@ -591,7 +591,7 @@ public bool ErrorOnNoMatch } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ErrorOnNoMatchSpecified { get @@ -607,10 +607,10 @@ public bool ErrorOnNoMatchSpecified } /// - [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; /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool ValueFromPipeline { get @@ -639,7 +639,7 @@ public bool ValueFromPipeline } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ValueFromPipelineSpecified { get @@ -654,7 +654,7 @@ public bool ValueFromPipelineSpecified } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool ValueFromPipelineByPropertyName { get @@ -669,7 +669,7 @@ public bool ValueFromPipelineByPropertyName } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ValueFromPipelineByPropertyNameSpecified { get @@ -684,7 +684,7 @@ public bool ValueFromPipelineByPropertyNameSpecified } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string[] CmdletParameterSets { get @@ -700,13 +700,13 @@ public string[] CmdletParameterSets } /// - [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 } /// - [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 } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool IsMandatory { get @@ -896,7 +896,7 @@ public bool IsMandatory } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool IsMandatorySpecified { get @@ -911,7 +911,7 @@ public bool IsMandatorySpecified } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string[] Aliases { get @@ -926,7 +926,7 @@ public string[] Aliases } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string PSName { get @@ -941,7 +941,7 @@ public string PSName } /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "nonNegativeInteger")] + [System.Xml.Serialization.XmlAttribute(DataType = "nonNegativeInteger")] public string Position { get @@ -959,7 +959,7 @@ public string Position /// [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; /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "nonNegativeInteger")] + [System.Xml.Serialization.XmlAttribute(DataType = "nonNegativeInteger")] public string Min { get @@ -982,7 +982,7 @@ public string Min } /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "nonNegativeInteger")] + [System.Xml.Serialization.XmlAttribute(DataType = "nonNegativeInteger")] public string Max { get @@ -1000,7 +1000,7 @@ public string Max /// [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; /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "nonNegativeInteger")] + [System.Xml.Serialization.XmlAttribute(DataType = "nonNegativeInteger")] public string Min { get @@ -1023,7 +1023,7 @@ public string Min } /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "nonNegativeInteger")] + [System.Xml.Serialization.XmlAttribute(DataType = "nonNegativeInteger")] public string Max { get @@ -1041,7 +1041,7 @@ public string Max /// [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; /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "integer")] + [System.Xml.Serialization.XmlAttribute(DataType = "integer")] public string Min { get @@ -1064,7 +1064,7 @@ public string Min } /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "integer")] + [System.Xml.Serialization.XmlAttribute(DataType = "integer")] public string Max { get @@ -1082,13 +1082,13 @@ public string Max /// [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; /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string Message { get @@ -1106,7 +1106,7 @@ public string Message /// [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; /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool ValueFromPipelineByPropertyName { get @@ -1129,7 +1129,7 @@ public bool ValueFromPipelineByPropertyName } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ValueFromPipelineByPropertyNameSpecified { get @@ -1147,7 +1147,7 @@ public bool ValueFromPipelineByPropertyNameSpecified /// [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; /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool ValueFromPipeline { get @@ -1174,7 +1174,7 @@ public bool ValueFromPipeline } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ValueFromPipelineSpecified { get @@ -1189,7 +1189,7 @@ public bool ValueFromPipelineSpecified } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool ValueFromPipelineByPropertyName { get @@ -1204,7 +1204,7 @@ public bool ValueFromPipelineByPropertyName } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ValueFromPipelineByPropertyNameSpecified { get @@ -1222,7 +1222,7 @@ public bool ValueFromPipelineByPropertyNameSpecified /// [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 } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string OptionName { get @@ -1278,7 +1278,7 @@ public string OptionName /// [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 /// [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 } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string Verb { get @@ -1364,7 +1364,7 @@ public string Verb } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string Noun { get @@ -1379,7 +1379,7 @@ public string Noun } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string[] Aliases { get @@ -1394,7 +1394,7 @@ public string[] Aliases } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public ConfirmImpact ConfirmImpact { get @@ -1409,7 +1409,7 @@ public ConfirmImpact ConfirmImpact } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ConfirmImpactSpecified { get @@ -1424,7 +1424,7 @@ public bool ConfirmImpactSpecified } /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "anyURI")] + [System.Xml.Serialization.XmlAttribute(DataType = "anyURI")] public string HelpUri { get @@ -1441,7 +1441,7 @@ public string HelpUri /// [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 { /// @@ -1460,7 +1460,7 @@ public enum ConfirmImpact /// [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 } /// - [System.Xml.Serialization.XmlElementAttribute("Method")] + [System.Xml.Serialization.XmlElement("Method")] public StaticMethodMetadata[] Method { get @@ -1500,13 +1500,13 @@ public StaticMethodMetadata[] Method /// [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; /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string DefaultCmdletParameterSet { get @@ -1524,7 +1524,7 @@ public string DefaultCmdletParameterSet /// [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; /// - [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 } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string CmdletParameterSet { get @@ -1565,7 +1565,7 @@ public string CmdletParameterSet /// [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 /// [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 } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string PSName { get @@ -1642,11 +1642,11 @@ public string PSName } /// - [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 } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string ParameterName { get @@ -1685,7 +1685,7 @@ public string ParameterName } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string DefaultValue { get @@ -1703,7 +1703,7 @@ public string DefaultValue /// [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 } /// - [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 } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string MethodName { get @@ -1784,7 +1784,7 @@ public string MethodName /// [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 /// [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; /// - [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 /// [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 } /// - [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 /// [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; /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool AllowGlobbing { get @@ -1949,7 +1949,7 @@ public bool AllowGlobbing } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool AllowGlobbingSpecified { get @@ -1966,7 +1966,7 @@ public bool AllowGlobbingSpecified /// [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 { /// @@ -1985,7 +1985,7 @@ public enum ItemsChoiceType /// [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; /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string Name { get @@ -2008,7 +2008,7 @@ public string Name } /// - [System.Xml.Serialization.XmlTextAttribute()] + [System.Xml.Serialization.XmlText()] public string Value { get @@ -2026,7 +2026,7 @@ public string Value /// [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; /// - [System.Xml.Serialization.XmlElementAttribute("Value")] + [System.Xml.Serialization.XmlElement("Value")] public EnumMetadataEnumValue[] Value { get @@ -2055,7 +2055,7 @@ public EnumMetadataEnumValue[] Value } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string EnumName { get @@ -2070,7 +2070,7 @@ public string EnumName } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string UnderlyingType { get @@ -2085,7 +2085,7 @@ public string UnderlyingType } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool BitwiseFlags { get @@ -2100,7 +2100,7 @@ public bool BitwiseFlags } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool BitwiseFlagsSpecified { get @@ -2118,7 +2118,7 @@ public bool BitwiseFlagsSpecified /// [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; /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string Name { get @@ -2141,7 +2141,7 @@ public string Name } /// - [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/CoreCLR/cmdlets-over-objects.xmlSerializer.autogen.cs b/src/System.Management.Automation/cimSupport/cmdletization/xml/CoreCLR/cmdlets-over-objects.xmlSerializer.autogen.cs index 9c53032284e..6cfed8ebe2d 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/xml/CoreCLR/cmdlets-over-objects.xmlSerializer.autogen.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/xml/CoreCLR/cmdlets-over-objects.xmlSerializer.autogen.cs @@ -289,12 +289,12 @@ protected Exception CreateUnknownNodeException() protected Exception CreateUnknownTypeException(XmlQualifiedName type) { - return new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "XmlUnknownType. Name: {0}, Namespace {1}, CurrentTag: {2}", type.Name, type.Namespace, CurrentTag())); + return new InvalidOperationException(string.Create(CultureInfo.CurrentCulture, $"XmlUnknownType. Name: {type.Name}, Namespace: {type.Namespace}, CurrentTag: {CurrentTag()}")); } protected Exception CreateUnknownConstantException(string value, Type enumType) { - return new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "XmlUnknownConstant. Value: {0}, EnumType: {1}", value, enumType.Name)); + return new InvalidOperationException(string.Create(CultureInfo.CurrentCulture, $"XmlUnknownConstant. Value: {value}, EnumType: {enumType.Name}")); } protected Array ShrinkArray(Array a, int length, Type elementType, bool isNullable) @@ -426,7 +426,7 @@ internal XmlQualifiedName ToXmlQualifiedName(string value, bool decodeName) if (ns == null) { // Namespace prefix '{0}' is not defined. - throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "XmlUndefinedAlias. Prefix: {0}", prefix)); + throw new InvalidOperationException(string.Create(CultureInfo.CurrentCulture, $"XmlUndefinedAlias. Prefix: {prefix}")); } return new XmlQualifiedName(_r.NameTable.Add(localName), ns); @@ -6678,10 +6678,7 @@ internal sealed class PowerShellMetadataSerializer { internal object Deserialize(XmlReader reader) { - if (reader == null) - { - throw new ArgumentNullException("reader"); - } + ArgumentNullException.ThrowIfNull(reader); XmlSerializationReader1 cdxmlSerializationReader = new XmlSerializationReader1(reader); return cdxmlSerializationReader.Read50_PowerShellMetadata(); 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 a05d597999a..f463e5052bb 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 @@ -514,7 +514,11 @@ private void Write49_EnumMetadataEnumValue(string n, string ns, global::Microsof { if ((object)o == null) { - if (isNullable) WriteNullTagLiteral(n, ns); + if (isNullable) + { + WriteNullTagLiteral(n, ns); + } + return; } @@ -531,7 +535,11 @@ private void Write49_EnumMetadataEnumValue(string n, string ns, global::Microsof } WriteStartElement(n, ns, o, false, null); - if (needType) WriteXsiType(@"EnumMetadataEnumValue", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); + if (needType) + { + WriteXsiType(@"EnumMetadataEnumValue", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); + } + WriteAttribute(@"Name", @"", ((global::System.String)o.@Name)); WriteAttribute(@"Value", @"", ((global::System.String)o.@Value)); WriteEndElement(o); @@ -541,7 +549,11 @@ private void Write48_EnumMetadataEnum(string n, string ns, global::Microsoft.Pow { if ((object)o == null) { - if (isNullable) WriteNullTagLiteral(n, ns); + if (isNullable) + { + WriteNullTagLiteral(n, ns); + } + return; } @@ -558,7 +570,11 @@ private void Write48_EnumMetadataEnum(string n, string ns, global::Microsoft.Pow } WriteStartElement(n, ns, o, false, null); - if (needType) WriteXsiType(@"EnumMetadataEnum", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); + if (needType) + { + WriteXsiType(@"EnumMetadataEnum", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); + } + WriteAttribute(@"EnumName", @"", ((global::System.String)o.@EnumName)); WriteAttribute(@"UnderlyingType", @"", ((global::System.String)o.@UnderlyingType)); if (o.@BitwiseFlagsSpecified) @@ -587,7 +603,11 @@ private void Write37_EnumMetadataEnumValue(string n, string ns, global::Microsof { if ((object)o == null) { - if (isNullable) WriteNullTagLiteral(n, ns); + if (isNullable) + { + WriteNullTagLiteral(n, ns); + } + return; } @@ -604,7 +624,11 @@ private void Write37_EnumMetadataEnumValue(string n, string ns, global::Microsof } WriteStartElement(n, ns, o, false, null); - if (needType) WriteXsiType(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); + if (needType) + { + WriteXsiType(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); + } + WriteAttribute(@"Name", @"", ((global::System.String)o.@Name)); WriteAttribute(@"Value", @"", ((global::System.String)o.@Value)); WriteEndElement(o); @@ -614,7 +638,11 @@ private void Write47_ClassMetadataData(string n, string ns, global::Microsoft.Po { if ((object)o == null) { - if (isNullable) WriteNullTagLiteral(n, ns); + if (isNullable) + { + WriteNullTagLiteral(n, ns); + } + return; } @@ -631,7 +659,11 @@ private void Write47_ClassMetadataData(string n, string ns, global::Microsoft.Po } WriteStartElement(n, ns, o, false, null); - if (needType) WriteXsiType(@"ClassMetadataData", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); + if (needType) + { + WriteXsiType(@"ClassMetadataData", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); + } + WriteAttribute(@"Name", @"", ((global::System.String)o.@Name)); if ((object)(o.@Value) != null) { @@ -660,7 +692,11 @@ private void Write13_WildcardablePropertyQuery(string n, string ns, global::Micr { if ((object)o == null) { - if (isNullable) WriteNullTagLiteral(n, ns); + if (isNullable) + { + WriteNullTagLiteral(n, ns); + } + return; } @@ -677,7 +713,11 @@ private void Write13_WildcardablePropertyQuery(string n, string ns, global::Micr } WriteStartElement(n, ns, o, false, null); - if (needType) WriteXsiType(@"WildcardablePropertyQuery", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); + if (needType) + { + WriteXsiType(@"WildcardablePropertyQuery", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); + } + if (o.@AllowGlobbingSpecified) { WriteAttribute(@"AllowGlobbing", @"", System.Xml.XmlConvert.ToString((global::System.Boolean)((global::System.Boolean)o.@AllowGlobbing))); @@ -695,7 +735,11 @@ private void Write12_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl { if ((object)o == null) { - if (isNullable) WriteNullTagLiteral(n, ns); + if (isNullable) + { + WriteNullTagLiteral(n, ns); + } + return; } @@ -712,7 +756,11 @@ private void Write12_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl } WriteStartElement(n, ns, o, false, null); - if (needType) WriteXsiType(@"CmdletParameterMetadataForGetCmdletFilteringParameter", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); + if (needType) + { + 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))); @@ -725,7 +773,11 @@ private void Write12_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl for (int i = 0; i < a.Length; i++) { global::System.String ai = (global::System.String)a[i]; - if (i != 0) Writer.WriteString(" "); + if (i != 0) + { + Writer.WriteString(" "); + } + WriteValue(ai); } @@ -752,7 +804,11 @@ private void Write12_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl for (int i = 0; i < a.Length; i++) { global::System.String ai = (global::System.String)a[i]; - if (i != 0) Writer.WriteString(" "); + if (i != 0) + { + Writer.WriteString(" "); + } + WriteValue(ai); } @@ -811,7 +867,11 @@ private void Write7_ObsoleteAttributeMetadata(string n, string ns, global::Micro { if ((object)o == null) { - if (isNullable) WriteNullTagLiteral(n, ns); + if (isNullable) + { + WriteNullTagLiteral(n, ns); + } + return; } @@ -828,7 +888,11 @@ private void Write7_ObsoleteAttributeMetadata(string n, string ns, global::Micro } WriteStartElement(n, ns, o, false, null); - if (needType) WriteXsiType(@"ObsoleteAttributeMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); + if (needType) + { + WriteXsiType(@"ObsoleteAttributeMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); + } + WriteAttribute(@"Message", @"", ((global::System.String)o.@Message)); WriteEndElement(o); } @@ -837,7 +901,11 @@ private void Write6_Item(string n, string ns, global::Microsoft.PowerShell.Cmdle { if ((object)o == null) { - if (isNullable) WriteNullTagLiteral(n, ns); + if (isNullable) + { + WriteNullTagLiteral(n, ns); + } + return; } @@ -854,7 +922,11 @@ private void Write6_Item(string n, string ns, global::Microsoft.PowerShell.Cmdle } WriteStartElement(n, ns, o, false, null); - if (needType) WriteXsiType(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); + if (needType) + { + WriteXsiType(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); + } + WriteAttribute(@"Min", @"", ((global::System.String)o.@Min)); WriteAttribute(@"Max", @"", ((global::System.String)o.@Max)); WriteEndElement(o); @@ -864,7 +936,11 @@ private void Write5_Item(string n, string ns, global::Microsoft.PowerShell.Cmdle { if ((object)o == null) { - if (isNullable) WriteNullTagLiteral(n, ns); + if (isNullable) + { + WriteNullTagLiteral(n, ns); + } + return; } @@ -881,7 +957,11 @@ private void Write5_Item(string n, string ns, global::Microsoft.PowerShell.Cmdle } WriteStartElement(n, ns, o, false, null); - if (needType) WriteXsiType(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); + if (needType) + { + WriteXsiType(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); + } + WriteAttribute(@"Min", @"", ((global::System.String)o.@Min)); WriteAttribute(@"Max", @"", ((global::System.String)o.@Max)); WriteEndElement(o); @@ -891,7 +971,11 @@ private void Write4_Item(string n, string ns, global::Microsoft.PowerShell.Cmdle { if ((object)o == null) { - if (isNullable) WriteNullTagLiteral(n, ns); + if (isNullable) + { + WriteNullTagLiteral(n, ns); + } + return; } @@ -908,7 +992,11 @@ private void Write4_Item(string n, string ns, global::Microsoft.PowerShell.Cmdle } WriteStartElement(n, ns, o, false, null); - if (needType) WriteXsiType(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); + if (needType) + { + WriteXsiType(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); + } + WriteAttribute(@"Min", @"", ((global::System.String)o.@Min)); WriteAttribute(@"Max", @"", ((global::System.String)o.@Max)); WriteEndElement(o); diff --git a/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.xsd b/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.xsd index 0766b785451..67ee8b0b0dc 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.xsd +++ b/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.xsd @@ -9,10 +9,10 @@ Licensed under the MIT License. - + - + @@ -25,16 +25,16 @@ Licensed under the MIT License. ]> - This schema defines the format of PowerShell CIM Modules. - A PowerShell CIM Module defines a set of cmdlets that interact with a CIM class. - + A PowerShell CIM Module defines a set of cmdlets that interact with a CIM class. + A PowerShell CIM Module needs to be saved in a file with ".cdxml" extension. A ".cdxml" file can be imported into a PowerShell session directly by Import-Module cmdlet, or by referring to the ".cdxml" file from NestedModules or RootModule entry of @@ -87,7 +87,7 @@ Licensed under the MIT License. - + @@ -103,36 +103,36 @@ Licensed under the MIT License. - + EnumName attribute specifies the name of a .NET enum. This is the name to use in a PSType attribute. - - The name should include a namespace to avoid naming conflicts + + The name should include a namespace to avoid naming conflicts (i.e. the name should be "Networking.MyEnum" rather than "MyEnum"). - + The system will prefix the name of the enum with the following namespace: "Microsoft.PowerShell.Cmdletization.GeneratedTypes" (i.e. "Networking.MyEnum" will become "Microsoft.PowerShell.Cmdletization.GeneratedTypes.Networking.MyEnum"). When referring to the enum in types.ps1xml and format.ps1xml files, one has to use the full, prefixed name of the enum. - + Underlying type of the enum. - + C# Language Specification allows (in section 4.1.9 "Enumeration types") only the following - underlying types: - byte (System.Byte), - sbyte (System.SByte), - short (System.Int16), - ushort (System.UInt16), - int (System.Int32), - uint (System.UInt32), + underlying types: + byte (System.Byte), + sbyte (System.SByte), + short (System.Int16), + ushort (System.UInt16), + int (System.Int32), + uint (System.UInt32), long (System.Int64), ulong (System.UInt64). @@ -142,7 +142,7 @@ Licensed under the MIT License. - BitwiseFlags attribute specifies if the .NET enum will be decorated with a System.FlagsAttribute. + BitwiseFlags attribute specifies if the .NET enum will be decorated with a System.FlagsAttribute. @@ -196,7 +196,7 @@ Licensed under the MIT License. - + @@ -240,10 +240,10 @@ Licensed under the MIT License. CmdletAdapter attribute specifies which .NET class is responsible for translating - cmdlet invocations into queries and method invocations. - - If this attribute is ommited, then by default the cmdlets are translated into WMI queries and method invocations. - + cmdlet invocations into queries and method invocations. + + If this attribute is ommited, then by default the cmdlets are translated into WMI queries and method invocations. + The class specified here has to be derived from Microsoft.PowerShell.Cmdletization.CmdletAdapter class. @@ -253,7 +253,7 @@ Licensed under the MIT License. ClassName attribute specified the class that the cmdlets work against. - + Example: "root/cimv2/Win32_Process" @@ -282,8 +282,8 @@ Licensed under the MIT License. Cmdlet element under InstanceCmdlets element defines a cmdlet that wraps an instance method. - - Cmdlet parameters of a cmdlet defined this way are a sum of + + Cmdlet parameters of a cmdlet defined this way are a sum of 1) cmdlet parameters defined through GetCmdletParameters elements 2) cmdlet parameters mapped to input parameters of the method defined by Method element @@ -313,7 +313,7 @@ Licensed under the MIT License. Cmdlet element under StaticCmdlets element defines a cmdlet that wraps one or more static methods. - + Cmdlet parameters of a cmdlet defined this way are mapped to input parameters of methods defined by Method element Each wrapped method corresponds to a parameter set of the cmdlet. @@ -341,9 +341,9 @@ Licensed under the MIT License. GetCmdlet element defines cmdlet metadata for the cmdlet that queries for object instances. - + If GetCmdlet element is ommited, then the default verb ("Get") and noun (based on <DefaultNoun> element) are going to be used. - + GetCmdlet element is typically used for one of the following items: - To allow the Get cmdlet to have different GetCmdletParameters than other cmdlets (for example to make all parameters optional for Get cmdlet, but make some parameters mandatory for other cmdlets) - To change the verb of the cmdlet (for example to use "Find" where appropriate) @@ -365,19 +365,19 @@ Licensed under the MIT License. - + - + Verb attribute specifies the verb of the cmdlet. - + Please refer to Cmdlet Design Guidelines for a list of approved verbs. - + Verb attribute is equivalent to the verbName parameter of System.Management.Automation.CmdletAttribute constructor. @@ -387,9 +387,9 @@ Licensed under the MIT License. Noun attribute specifies the noun of the cmdlet. - + If the Noun attribute is ommited, then contents of the DefaultNoun element are used. - + Noun attribute is equivalent to the nounName parameter of System.Management.Automation.CmdletAttribute constructor. @@ -407,9 +407,9 @@ Licensed under the MIT License. ConfirmImpact attribute specifies the impact of the cmdlet. - + ConfirmImpact attribute determines the default -Confirm and -WhatIf behavior. - + ConfirmImpact attribute is equivalent to the ConfirmImpact property of System.Management.Automation.CmdletAttribute. Presence of the ConfirmImpact attribute is equivalent to setting to true the SupportsShouldProcess property of System.Management.Automation.CmdletAttribute. @@ -420,11 +420,11 @@ Licensed under the MIT License. HelpUri attribute specifies the URI with the help content. - + HelpUri attribute is used for the following help experience: Get-Help -Online <cmdlet name> - + HelpUri attribute is equivalent to the HelpUri property of System.Management.Automation.CmdletAttribute - + Example: "http://go.microsoft.com/fwlink/?LinkID=113309" @@ -454,12 +454,12 @@ Licensed under the MIT License. - + CmdletParameterSet attribute specifies the name of a cmdlet parameter set associated with the static method. - + If CmdletParameterSet is ommited, then the name of the cmdlet parameter set is auto-generated based on the name of the method. @@ -467,7 +467,7 @@ Licensed under the MIT License. - + @@ -508,8 +508,8 @@ Licensed under the MIT License. MethodName attribute specified the name of the method that the cmdlet invocations are mapped to. - - Some method names are recognized and handled in a special way. + + Some method names are recognized and handled in a special way. "cim:CreateInstance" is mapped to the WMI's static, intrinsic CreateInstance method. Names of method parameters have to map to names of properties. "cim:ModifyInstance" is mapped to the WMI's instance, intrinsic ModifyInstance method. Names of method parameters have to map to names of properties. "cim:DeleteInstance" is mapped to the WMI's instance, intrinsic DeleteInstance method. All method parameters are ignored. @@ -547,7 +547,7 @@ Licensed under the MIT License. - + @@ -594,7 +594,7 @@ Licensed under the MIT License. - + @@ -606,12 +606,12 @@ Licensed under the MIT License. - + Association attribute specifies the name of the association between the cmdlet argument and the instances the cmdlet acts against. - + Association attribute is equivalent to the associationClassName parameter of EnumerateAssociatedInstances method of Microsoft.Management.Infrastructure.CimSession class. @@ -627,7 +627,7 @@ Licensed under the MIT License. - + @@ -638,7 +638,7 @@ Licensed under the MIT License. - + @@ -651,43 +651,43 @@ Licensed under the MIT License. RegularQuery element defines a cmdlet parameter that limits which objects will be processed by the cmdlet - only objects with a property value equal to the cmdlet parameter argument will be processed. - + Comparison of strings and characters is always case-insensitive. - + Example for <RegularQuery> element that is applied to an ObjectId property: The following cmdlet invocation: Get-MyObject -ObjectId 123,456 will be translated into the following WQL query: - SELECT * FROM MyObject WHERE ((ObjectId = 123) OR (ObjectId = 456)) - + SELECT * FROM MyObject WHERE ((ObjectId = 123) OR (ObjectId = 456)) + Example for <RegularQuery AllowGlobbing="false" > element that is applied to a Name property: The following cmdlet invocation: - Get-MyObject -LiteralName p*,q* + Get-MyObject -LiteralName p*,q* will be translated into the following WQL query: - SELECT * FROM MyObject WHERE ((Name = "p*") OR (Name = "q*")) - + SELECT * FROM MyObject WHERE ((Name = "p*") OR (Name = "q*")) + Example for <RegularQuery AllowGlobbing="true" > element that is applied to a Name property: The following cmdlet invocation: - Get-MyObject -Name p*,q* + Get-MyObject -Name p*,q* will be translated into the following WQL query: - SELECT * FROM MyObject WHERE ((Name like "p%") OR (Name like "q%")) + SELECT * FROM MyObject WHERE ((Name like "p%") OR (Name like "q%")) - + ExcludeQuery element defines a cmdlet parameter that limits which objects will be processed by the cmdlet - only objects with a property value *not* equal to the cmdlet parameter argument will be processed. - + Comparison of strings and characters is always case-insensitive. - + Example for <ExcludeQuery> element that is applied to an ObjectId property: The following cmdlet invocation: Get-MyObject -ExcludeObjectId 123,456 will be translated into the following WQL query: - SELECT * FROM MyObject WHERE ((NOT Name = 123) AND (NOT Name = 456)) + SELECT * FROM MyObject WHERE ((NOT Name = 123) AND (NOT Name = 456)) @@ -697,7 +697,7 @@ Licensed under the MIT License. MinValueQuery element defines a cmdlet parameter that limits which objects will be processed by the cmdlet - only objects with a property value greater than or equal to the cmdlet parameter argument will be processed. - + Example for <MinValueQuery> element that is applied to an WorkingSet property: The following cmdlet invocation: Get-MyObject -MinWorkingSet 123 @@ -725,17 +725,17 @@ Licensed under the MIT License. - + - AllowGlobbing attribute specifies if strings with globbing characters (wildcards) are supported. - + AllowGlobbing attribute specifies if strings with globbing characters (wildcards) are supported. + Example of a wildcard: "foo*" (matches all strings beginning with "foo") - + If AllowGlobbing attribute is ommited then its value is based on the type of the filtered property. @@ -777,19 +777,19 @@ Licensed under the MIT License. - + - + CmdletParameterSets attribute is a whitespace-separated list of names of parameter sets, that the cmdlet parameter should belong to. - + If this parameter is ommited, then the cmdlet parameter belongs to all parameter sets. @@ -797,7 +797,7 @@ Licensed under the MIT License. - + @@ -817,14 +817,14 @@ Licensed under the MIT License. - + - + @@ -844,14 +844,14 @@ Licensed under the MIT License. - + PSName attribute specifies the name of a cmdlet parameter. - + If PSName attribute is ommited then it is based on the contents of PropertyName or ParameterName or OptionName attribute (whichever one is applicable). - + Example: <Property PropertyName="Name"> ... @@ -870,11 +870,11 @@ Licensed under the MIT License. Position attribute specifies position of the cmdlet parameter. - + If Position attribute is ommited, then the cmdlet parameter cannot be used positionally - the user always has to explicitly specify the name of the parameter. - + System may change relative parameter positions to guarantee that cmdlet parameters defined by GetCmdletParameters element are always - before cmdlet parameters defined under Method element. + before cmdlet parameters defined under Method element. @@ -885,7 +885,7 @@ Licensed under the MIT License. - + @@ -898,19 +898,19 @@ Licensed under the MIT License. PSType attribute specifies the name of the .NET type of the cmdlet parameter. - + Example: "System.String" - + ETSType attribute specifies the PowerShell type name of the type of the cmdlet parameter. - + ETSType attribute is equivalent to System.Management.Automation.PSTypeNameAttribute. - + Example: "Microsoft.Management.Infrastructure.CimInstance#Win32_Process" @@ -1011,7 +1011,7 @@ Licensed under the MIT License. - + @@ -1026,7 +1026,7 @@ Licensed under the MIT License. - + @@ -1034,7 +1034,7 @@ Licensed under the MIT License. - + diff --git a/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs b/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs index f9aa9f9b73c..9a3e5f137ad 100644 --- a/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs +++ b/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs @@ -21,8 +21,6 @@ namespace Microsoft.PowerShell.Cim /// Implementing the PropertyOnlyAdapter for the time being as CimInstanceTypeAdapter currently /// supports only properties. If method support is needed in future, this should derive from /// Adapter class. - /// - /// The Adapter registration is done in monad\src\singleshell\installer\MshManagementMshSnapin.cs /// public sealed class CimInstanceAdapter : PSPropertyAdapter { @@ -63,8 +61,7 @@ private static PSAdaptedProperty GetPSComputerNameAdapter(CimInstance cimInstanc public override System.Collections.ObjectModel.Collection GetProperties(object baseObject) { // baseObject should never be null - CimInstance cimInstance = baseObject as CimInstance; - if (cimInstance == null) + if (baseObject is not CimInstance cimInstance) { string msg = string.Format(CultureInfo.InvariantCulture, CimInstanceTypeAdapterResources.BaseObjectNotCimInstance, @@ -109,8 +106,7 @@ public override PSAdaptedProperty GetProperty(object baseObject, string property } // baseObject should never be null - CimInstance cimInstance = baseObject as CimInstance; - if (cimInstance == null) + if (baseObject is not CimInstance cimInstance) { string msg = string.Format(CultureInfo.InvariantCulture, CimInstanceTypeAdapterResources.BaseObjectNotCimInstance, @@ -144,8 +140,7 @@ public override PSAdaptedProperty GetFirstPropertyOrDefault(object baseObject, M } // baseObject should never be null - CimInstance cimInstance = baseObject as CimInstance; - if (cimInstance == null) + if (baseObject is not CimInstance cimInstance) { string msg = string.Format( CultureInfo.InvariantCulture, @@ -197,13 +192,9 @@ internal static string CimTypeToTypeNameDisplayString(CimType cimType) /// public override string GetPropertyTypeName(PSAdaptedProperty adaptedProperty) { - if (adaptedProperty == null) - { - throw new ArgumentNullException(nameof(adaptedProperty)); - } + ArgumentNullException.ThrowIfNull(adaptedProperty); - CimProperty cimProperty = adaptedProperty.Tag as CimProperty; - if (cimProperty != null) + if (adaptedProperty.Tag is CimProperty cimProperty) { return CimTypeToTypeNameDisplayString(cimProperty.CimType); } @@ -222,13 +213,9 @@ public override string GetPropertyTypeName(PSAdaptedProperty adaptedProperty) /// public override object GetPropertyValue(PSAdaptedProperty adaptedProperty) { - if (adaptedProperty == null) - { - throw new ArgumentNullException(nameof(adaptedProperty)); - } + ArgumentNullException.ThrowIfNull(adaptedProperty); - CimProperty cimProperty = adaptedProperty.Tag as CimProperty; - if (cimProperty != null) + if (adaptedProperty.Tag is CimProperty cimProperty) { return cimProperty.Value; } @@ -246,16 +233,11 @@ private static void AddTypeNameHierarchy(IList typeNamesWithNamespace, I { if (!string.IsNullOrEmpty(namespaceName)) { - string fullTypeName = string.Format(CultureInfo.InvariantCulture, - "Microsoft.Management.Infrastructure.CimInstance#{0}/{1}", - namespaceName, - className); + string fullTypeName = string.Create(CultureInfo.InvariantCulture, $"Microsoft.Management.Infrastructure.CimInstance#{namespaceName}/{className}"); typeNamesWithNamespace.Add(fullTypeName); } - typeNamesWithoutNamespace.Add(string.Format(CultureInfo.InvariantCulture, - "Microsoft.Management.Infrastructure.CimInstance#{0}", - className)); + typeNamesWithoutNamespace.Add(string.Create(CultureInfo.InvariantCulture, $"Microsoft.Management.Infrastructure.CimInstance#{className}")); } private static List GetInheritanceChain(CimInstance cimInstance) @@ -377,10 +359,7 @@ public override bool IsSettable(PSAdaptedProperty adaptedProperty) /// public override void SetPropertyValue(PSAdaptedProperty adaptedProperty, object value) { - if (adaptedProperty == null) - { - throw new ArgumentNullException(nameof(adaptedProperty)); - } + ArgumentNullException.ThrowIfNull(adaptedProperty); if (!IsSettable(adaptedProperty)) { diff --git a/src/System.Management.Automation/engine/ApplicationInfo.cs b/src/System.Management.Automation/engine/ApplicationInfo.cs index 5fe76424f5a..ca46023b66b 100644 --- a/src/System.Management.Automation/engine/ApplicationInfo.cs +++ b/src/System.Management.Automation/engine/ApplicationInfo.cs @@ -8,7 +8,7 @@ namespace System.Management.Automation { /// - /// Provides information for applications that are not directly executable by Monad. + /// Provides information for applications that are not directly executable by PowerShell. /// /// /// An application is any file that is executable by Windows either directly or through diff --git a/src/System.Management.Automation/engine/ArgumentToVersionTransformationAttribute.cs b/src/System.Management.Automation/engine/ArgumentToVersionTransformationAttribute.cs new file mode 100644 index 00000000000..facaa9e3e1a --- /dev/null +++ b/src/System.Management.Automation/engine/ArgumentToVersionTransformationAttribute.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace System.Management.Automation +{ + /// + /// To make it easier to specify a version, we add some conversions that wouldn't happen otherwise: + /// * A simple integer, i.e. 2; + /// * A string without a dot, i.e. "2". + /// + internal class ArgumentToVersionTransformationAttribute : ArgumentTransformationAttribute + { + /// + public override object Transform(EngineIntrinsics engineIntrinsics, object inputData) + { + object version = PSObject.Base(inputData); + + if (version is string versionStr) + { + if (TryConvertFromString(versionStr, out var convertedVersion)) + { + return convertedVersion; + } + + if (versionStr.Contains('.')) + { + // If the string contains a '.', let the Version constructor handle the conversion. + return inputData; + } + } + + if (version is double) + { + // The conversion to int below is wrong, but the usual conversions will turn + // the double into a string, so just return the original object. + return inputData; + } + + if (LanguagePrimitives.TryConvertTo(version, out var majorVersion)) + { + return new Version(majorVersion, 0); + } + + return inputData; + } + + protected virtual bool TryConvertFromString(string versionString, [NotNullWhen(true)] out Version? version) + { + version = null; + return false; + } + } +} diff --git a/src/System.Management.Automation/engine/AsyncByteStreamTransfer.cs b/src/System.Management.Automation/engine/AsyncByteStreamTransfer.cs new file mode 100644 index 00000000000..f1391924d6b --- /dev/null +++ b/src/System.Management.Automation/engine/AsyncByteStreamTransfer.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System.Buffers; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Management.Automation; + +/// +/// Represents the transfer of bytes from one to another +/// asynchronously. +/// +internal sealed class AsyncByteStreamTransfer : IDisposable +{ + private const int DefaultBufferSize = 1024; + + private readonly BytePipe _bytePipe; + + private readonly BytePipe _destinationPipe; + + private readonly Memory _buffer; + + private readonly CancellationTokenSource _cts = new(); + + private Task? _readToBufferTask; + + public AsyncByteStreamTransfer( + BytePipe bytePipe, + BytePipe destinationPipe) + { + _bytePipe = bytePipe; + _destinationPipe = destinationPipe; + _buffer = new byte[DefaultBufferSize]; + } + + public Task EOF => _readToBufferTask ?? Task.CompletedTask; + + public void BeginReadChunks() + { + _readToBufferTask = Task.Run(ReadBufferAsync); + } + + public void Dispose() => _cts.Cancel(); + + private async Task ReadBufferAsync() + { + Stream stream; + Stream? destinationStream = null; + try + { + stream = await _bytePipe.GetStream(_cts.Token); + destinationStream = await _destinationPipe.GetStream(_cts.Token); + + while (true) + { + int bytesRead; + bytesRead = await stream.ReadAsync(_buffer, _cts.Token); + if (bytesRead is 0) + { + break; + } + + destinationStream.Write(_buffer.Span.Slice(0, bytesRead)); + } + } + catch (IOException) + { + return; + } + catch (OperationCanceledException) + { + return; + } + finally + { + destinationStream?.Close(); + } + } +} diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index 8ab2530b030..2f7de2a2149 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Management.Automation.Internal; using System.Management.Automation.Language; +using System.Management.Automation.Security; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -1088,6 +1089,12 @@ public ValidateRangeAttribute(ValidateRangeKind kind) : base() private static void ValidateRange(object element, ValidateRangeKind rangeKind) { + if (element is TimeSpan ts) + { + ValidateTimeSpanRange(ts, rangeKind); + return; + } + Type commonType = GetCommonType(typeof(int), element.GetType()); if (commonType == null) { @@ -1212,6 +1219,59 @@ private void ValidateRange(object element) } } + private static void ValidateTimeSpanRange(TimeSpan element, ValidateRangeKind rangeKind) + { + TimeSpan zero = TimeSpan.Zero; + + switch (rangeKind) + { + case ValidateRangeKind.Positive: + if (zero.CompareTo(element) >= 0) + { + throw new ValidationMetadataException( + "ValidateRangePositiveFailure", + null, + Metadata.ValidateRangePositiveFailure, + element.ToString()); + } + + break; + case ValidateRangeKind.NonNegative: + if (zero.CompareTo(element) > 0) + { + throw new ValidationMetadataException( + "ValidateRangeNonNegativeFailure", + null, + Metadata.ValidateRangeNonNegativeFailure, + element.ToString()); + } + + break; + case ValidateRangeKind.Negative: + if (zero.CompareTo(element) <= 0) + { + throw new ValidationMetadataException( + "ValidateRangeNegativeFailure", + null, + Metadata.ValidateRangeNegativeFailure, + element.ToString()); + } + + break; + case ValidateRangeKind.NonPositive: + if (zero.CompareTo(element) < 0) + { + throw new ValidationMetadataException( + "ValidateRangeNonPositiveFailure", + null, + Metadata.ValidateRangeNonPositiveFailure, + element.ToString()); + } + + break; + } + } + private static Type GetCommonType(Type minType, Type maxType) { Type resultType = null; @@ -1252,6 +1312,28 @@ private static Type GetCommonType(Type minType, Type maxType) return resultType; } + + /// + /// Returns only the elements that passed the attribute's validation. + /// + /// The objects to validate. + internal IEnumerable GetValidatedElements(IEnumerable elementsToValidate) + { + foreach (var el in elementsToValidate) + { + try + { + ValidateElement(el); + } + catch (ValidationMetadataException) + { + // Element was not in range - drop + continue; + } + + yield return el; + } + } } /// @@ -1274,11 +1356,11 @@ public sealed class ValidatePatternAttribute : ValidateEnumeratedArgumentsAttrib /// Gets or sets the custom error message pattern that is displayed to the user. /// The text representation of the object being validated and the validating regex is passed as /// the first and second formatting parameters to the ErrorMessage formatting pattern. - /// + /// /// /// [ValidatePattern("\s+", ErrorMessage="The text '{0}' did not pass validation of regex '{1}'")] /// - /// + /// /// public string ErrorMessage { get; set; } @@ -1340,11 +1422,11 @@ public sealed class ValidateScriptAttribute : ValidateEnumeratedArgumentsAttribu /// Gets or sets the custom error message that is displayed to the user. /// The item being validated and the validating scriptblock is passed as the first and second /// formatting argument. - /// + /// /// /// [ValidateScript("$_ % 2", ErrorMessage = "The item '{0}' did not pass validation of script '{1}'")] /// - /// + /// /// public string ErrorMessage { get; set; } @@ -1605,11 +1687,11 @@ public sealed class ValidateSetAttribute : ValidateEnumeratedArgumentsAttribute /// Gets or sets the custom error message that is displayed to the user. /// The item being validated and a text representation of the validation set is passed as the /// first and second formatting argument to the formatting pattern. - /// + /// /// /// [ValidateSet("A","B","C", ErrorMessage="The item '{0}' is not part of the set '{1}'.") /// - /// + /// /// public string ErrorMessage { get; set; } @@ -1729,7 +1811,7 @@ public ValidateSetAttribute(Type valuesGeneratorType) // Add a valid values generator to the cache. // We don't cache valid values; we expect that valid values will be cached in the generator. validValuesGenerator = s_ValidValuesGeneratorCache.GetOrAdd( - valuesGeneratorType, (key) => (IValidateSetValuesGenerator)Activator.CreateInstance(key)); + valuesGeneratorType, static (key) => (IValidateSetValuesGenerator)Activator.CreateInstance(key)); } } @@ -1770,11 +1852,21 @@ protected override void Validate(object arguments, EngineIntrinsics engineIntrin { if (ExecutionContext.IsMarkedAsUntrusted(arguments)) { - throw new ValidationMetadataException( - "ValidateTrustedDataFailure", - null, - Metadata.ValidateTrustedDataFailure, - arguments); + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + throw new ValidationMetadataException( + "ValidateTrustedDataFailure", + null, + Metadata.ValidateTrustedDataFailure, + arguments); + } + + SystemPolicy.LogWDACAuditMessage( + context: null, + title: Metadata.WDACParameterArgNotTrustedLogTitle, + message: StringUtil.Format(Metadata.WDACParameterArgNotTrustedMessage, arguments), + fqid: "ParameterArgumentNotTrusted", + dropIntoDebugger: true); } } } @@ -1785,7 +1877,7 @@ protected override void Validate(object arguments, EngineIntrinsics engineIntrin /// /// Allows a NULL as the argument to a mandatory parameter. /// - [AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class AllowNullAttribute : CmdletMetadataAttribute { /// @@ -1797,7 +1889,7 @@ public AllowNullAttribute() { } /// /// Allows an empty string as the argument to a mandatory string parameter. /// - [AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class AllowEmptyStringAttribute : CmdletMetadataAttribute { /// @@ -1809,7 +1901,7 @@ public AllowEmptyStringAttribute() { } /// /// Allows an empty collection as the argument to a mandatory collection parameter. /// - [AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class AllowEmptyCollectionAttribute : CmdletMetadataAttribute { /// @@ -1994,7 +2086,10 @@ protected override void Validate(object arguments, EngineIntrinsics engineIntrin { // If the element of the collection is of value type, then no need to check for null // because a value-type value cannot be null. - if (isElementValueType) { return; } + if (isElementValueType) + { + return; + } IEnumerator enumerator = LanguagePrimitives.GetEnumerator(arguments); while (enumerator.MoveNext()) @@ -2013,15 +2108,30 @@ protected override void Validate(object arguments, EngineIntrinsics engineIntrin } /// - /// Validates that the parameters's argument is not null, is not an empty string, and is not - /// an empty collection. + /// Validates that the parameters's argument is not null, is not an empty string or a + /// string with white-space characters only, and is not an empty collection. /// - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] - public sealed class ValidateNotNullOrEmptyAttribute : NullValidationAttributeBase + public abstract class ValidateNotNullOrAttributeBase : NullValidationAttributeBase { + /// + /// Used to check the type of string validation to perform. + /// + protected readonly bool _checkWhiteSpace; + + /// + /// Validates that the parameters's argument is not null, is not an empty string or a + /// string with white-space characters only, and is not an empty collection. + /// + protected ValidateNotNullOrAttributeBase(bool checkWhiteSpace) + { + _checkWhiteSpace = checkWhiteSpace; + } + /// /// Validates that the parameters's argument is not null, is not an empty string, and is /// not an empty collection. If argument is a collection, each argument is verified. + /// It can also validate that the parameters's argument is not a string that consists + /// only of white-space characters. /// /// The arguments to verify. /// @@ -2041,7 +2151,17 @@ protected override void Validate(object arguments, EngineIntrinsics engineIntrin } else if (arguments is string str) { - if (string.IsNullOrEmpty(str)) + if (_checkWhiteSpace) + { + if (string.IsNullOrWhiteSpace(str)) + { + throw new ValidationMetadataException( + "ArgumentIsEmptyOrWhiteSpace", + null, + Metadata.ValidateNotNullOrWhiteSpaceFailure); + } + } + else if (string.IsNullOrEmpty(str)) { throw new ValidationMetadataException( "ArgumentIsEmpty", @@ -2053,7 +2173,10 @@ protected override void Validate(object arguments, EngineIntrinsics engineIntrin { bool isEmpty = true; IEnumerator enumerator = LanguagePrimitives.GetEnumerator(arguments); - if (enumerator.MoveNext()) { isEmpty = false; } + if (enumerator.MoveNext()) + { + isEmpty = false; + } // If the element of the collection is of value type, then no need to check for null // because a value-type value cannot be null. @@ -2072,7 +2195,17 @@ protected override void Validate(object arguments, EngineIntrinsics engineIntrin if (element is string elementAsString) { - if (string.IsNullOrEmpty(elementAsString)) + if (_checkWhiteSpace) + { + if (string.IsNullOrWhiteSpace(elementAsString)) + { + throw new ValidationMetadataException( + "ArgumentCollectionContainsEmptyOrWhiteSpace", + null, + Metadata.ValidateNotNullOrWhiteSpaceCollectionFailure); + } + } + else if (string.IsNullOrEmpty(elementAsString)) { throw new ValidationMetadataException( "ArgumentCollectionContainsEmpty", @@ -2104,6 +2237,42 @@ protected override void Validate(object arguments, EngineIntrinsics engineIntrin } } + /// + /// Validates that the parameters's argument is not null, is not an empty string, and is + /// not an empty collection. If argument is a collection, each argument is verified. + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class ValidateNotNullOrEmptyAttribute : ValidateNotNullOrAttributeBase + { + /// + /// Validates that the parameters's argument is not null, is not an empty string, and is + /// not an empty collection. If argument is a collection, each argument is verified. + /// + public ValidateNotNullOrEmptyAttribute() + : base(checkWhiteSpace: false) + { + } + } + + /// + /// Validates that the parameters's argument is not null, is not an empty string, is not a string that + /// consists only of white-space characters, and is not an empty collection. If argument is a collection, + /// each argument is verified. + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class ValidateNotNullOrWhiteSpaceAttribute : ValidateNotNullOrAttributeBase + { + /// + /// Validates that the parameters's argument is not null, is not an empty string, is not a string that + /// consists only of white-space characters, and is not an empty collection. If argument is a collection, + /// each argument is verified. + /// + public ValidateNotNullOrWhiteSpaceAttribute() + : base(checkWhiteSpace: true) + { + } + } + #endregion NULL validation attributes #endregion Data validate Attributes diff --git a/src/System.Management.Automation/engine/AutomationEngine.cs b/src/System.Management.Automation/engine/AutomationEngine.cs index 7da8f4200e5..592796d5bb0 100644 --- a/src/System.Management.Automation/engine/AutomationEngine.cs +++ b/src/System.Management.Automation/engine/AutomationEngine.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Linq; using System.Management.Automation.Host; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; +using System.Text; namespace System.Management.Automation { @@ -14,8 +14,15 @@ namespace System.Management.Automation /// internal class AutomationEngine { + static AutomationEngine() + { + // Register the encoding provider to load encodings that are not supported by default, + // so as to allow them to be used in user's script/code. + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + } + // Holds the parser to use for this instance of the engine... - internal Language.Parser EngineParser; + internal Parser EngineParser; /// /// Returns the handle to the execution context @@ -79,7 +86,7 @@ internal string Expand(string s) /// Compile a piece of text into a parse tree for later execution. /// /// The text to parse. - /// True iff the scriptblock will be added to history. + /// True if-and-only-if the scriptblock will be added to history. /// The parse text as a parsetree node. internal ScriptBlock ParseScriptBlock(string script, bool addToHistory) { @@ -98,12 +105,22 @@ internal ScriptBlock ParseScriptBlock(string script, string fileName, bool addTo if (errors.Length > 0) { - if (errors[0].IncompleteInput) + ParseException ex = errors[0].IncompleteInput + ? new IncompleteParseException(errors[0].Message, errors[0].ErrorId) + : new ParseException(errors); + + if (addToHistory) { - throw new IncompleteParseException(errors[0].Message, errors[0].ErrorId); + // Try associating the parsing error with the history item if we can. + InvocationInfo invInfo = ex.ErrorRecord.InvocationInfo; + LocalRunspace localRunspace = Context.CurrentRunspace as LocalRunspace; + if (invInfo is not null && localRunspace?.History is not null) + { + invInfo.HistoryId = localRunspace.History.GetNextHistoryId(); + } } - throw new ParseException(errors); + throw ex; } return new ScriptBlock(ast, isFilter: false); diff --git a/src/System.Management.Automation/engine/AutomationNull.cs b/src/System.Management.Automation/engine/AutomationNull.cs index 38016c3c513..7cf4742b7b4 100644 --- a/src/System.Management.Automation/engine/AutomationNull.cs +++ b/src/System.Management.Automation/engine/AutomationNull.cs @@ -9,7 +9,7 @@ namespace System.Management.Automation.Internal /// /// It's a singleton class. Sealed to prevent subclassing. Any operation that /// returns no actual value should return this object AutomationNull.Value. - /// Anything that evaluates an MSH expression should be prepared to deal + /// Anything that evaluates a PowerShell expression should be prepared to deal /// with receiving this result and discarding it. When received in an /// evaluation where a value is required, it should be replaced with null. /// diff --git a/src/System.Management.Automation/engine/BytePipe.cs b/src/System.Management.Automation/engine/BytePipe.cs new file mode 100644 index 00000000000..03eb827df98 --- /dev/null +++ b/src/System.Management.Automation/engine/BytePipe.cs @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PowerShell.Telemetry; + +namespace System.Management.Automation; + +/// +/// Represents a lazily retrieved for transfering bytes +/// to or from. +/// +internal abstract class BytePipe +{ + public abstract Task GetStream(CancellationToken cancellationToken); + + internal AsyncByteStreamTransfer Bind(BytePipe bytePipe) + { + Debug.Assert(bytePipe is not null); + return new AsyncByteStreamTransfer(bytePipe, destinationPipe: this); + } +} + +/// +/// Represents a lazily retrieved from the underlying +/// . +/// +internal sealed class NativeCommandProcessorBytePipe : BytePipe +{ + private readonly NativeCommandProcessor _nativeCommand; + + private readonly bool _stdout; + + internal NativeCommandProcessorBytePipe( + NativeCommandProcessor nativeCommand, + bool stdout) + { + Debug.Assert(nativeCommand is not null); + _nativeCommand = nativeCommand; + _stdout = stdout; + } + + public override async Task GetStream(CancellationToken cancellationToken) + { + // If the native command we're wrapping is the upstream command then + // NativeCommandProcessor.Prepare will have already been called before + // the creation of this BytePipe. + if (_stdout) + { + return _nativeCommand.GetStream(stdout: true); + } + + await _nativeCommand.WaitForProcessInitializationAsync(cancellationToken); + return _nativeCommand.GetStream(stdout: false); + } +} + +/// +/// Provides an byte pipe implementation representing a . +/// +internal sealed class FileBytePipe : BytePipe +{ + private readonly Stream _stream; + + private FileBytePipe(Stream stream) + { + Debug.Assert(stream is not null); + _stream = stream; + } + + internal static FileBytePipe Create(string fileName, bool append) + { + FileStream fileStream; + try + { + PathUtils.MasterStreamOpen( + fileName, + resolvedEncoding: null, + defaultEncoding: false, + append, + Force: true, + NoClobber: false, + out fileStream, + streamWriter: out _, + readOnlyFileInfo: out _, + isLiteralPath: true); + } + catch (Exception e) when (e.Data.Contains(typeof(ErrorRecord))) + { + // The error record is attached to the exception when thrown to preserve + // the call stack. + ErrorRecord? errorRecord = e.Data[typeof(ErrorRecord)] as ErrorRecord; + if (errorRecord is null) + { + throw; + } + + e.Data.Remove(typeof(ErrorRecord)); + throw new RuntimeException(null, e, errorRecord); + } + + ApplicationInsightsTelemetry.SendExperimentalUseData("PSNativeCommandPreserveBytePipe", "f"); + + return new FileBytePipe(fileStream); + } + + public override Task GetStream(CancellationToken cancellationToken) => Task.FromResult(_stream); +} diff --git a/src/System.Management.Automation/engine/COM/ComDispatch.cs b/src/System.Management.Automation/engine/COM/ComDispatch.cs index 3f6afa6440e..8e0417e17cc 100644 --- a/src/System.Management.Automation/engine/COM/ComDispatch.cs +++ b/src/System.Management.Automation/engine/COM/ComDispatch.cs @@ -5,6 +5,7 @@ using COM = System.Runtime.InteropServices.ComTypes; +#nullable enable namespace System.Management.Automation { /// @@ -19,7 +20,7 @@ internal interface IDispatch int GetTypeInfoCount(out int info); [PreserveSig] - int GetTypeInfo(int iTInfo, int lcid, out COM.ITypeInfo ppTInfo); + int GetTypeInfo(int iTInfo, int lcid, out COM.ITypeInfo? ppTInfo); void GetIDsOfNames( [MarshalAs(UnmanagedType.LPStruct)] Guid iid, @@ -34,7 +35,7 @@ void Invoke( int lcid, COM.INVOKEKIND wFlags, [In, Out][MarshalAs(UnmanagedType.LPArray)] COM.DISPPARAMS[] paramArray, - out object pVarResult, + out object? pVarResult, out ComInvoker.EXCEPINFO pExcepInfo, out uint puArgErr); } diff --git a/src/System.Management.Automation/engine/COM/ComInvoker.cs b/src/System.Management.Automation/engine/COM/ComInvoker.cs index 3834be3de8e..a1b4a4c1ba7 100644 --- a/src/System.Management.Automation/engine/COM/ComInvoker.cs +++ b/src/System.Management.Automation/engine/COM/ComInvoker.cs @@ -280,7 +280,7 @@ internal static object Invoke(IDispatch target, int dispId, object[] args, bool[ { for (int i = 0; i < argCount; i++) { - VariantClear(variantArgArray + s_variantSize * i); + Interop.Windows.VariantClear(variantArgArray + s_variantSize * i); } Marshal.FreeCoTaskMem(variantArgArray); @@ -297,7 +297,7 @@ internal static object Invoke(IDispatch target, int dispId, object[] args, bool[ { for (int i = 0; i < refCount; i++) { - VariantClear(tmpVariants + s_variantSize * i); + Interop.Windows.VariantClear(tmpVariants + s_variantSize * i); } Marshal.FreeCoTaskMem(tmpVariants); @@ -305,13 +305,6 @@ internal static object Invoke(IDispatch target, int dispId, object[] args, bool[ } } - /// - /// Clear variables of type VARIANTARG (or VARIANT) before the memory containing the VARIANTARG is freed. - /// - /// - [DllImport("oleaut32.dll")] - internal static extern void VariantClear(IntPtr pVariant); - /// /// We have to declare 'bstrSource', 'bstrDescription' and 'bstrHelpFile' as pointers because /// CLR marshalling layer would try to free those BSTRs by default and that is not correct. diff --git a/src/System.Management.Automation/engine/COM/ComTypeInfo.cs b/src/System.Management.Automation/engine/COM/ComTypeInfo.cs index 851325e82ec..eab6122a002 100644 --- a/src/System.Management.Automation/engine/COM/ComTypeInfo.cs +++ b/src/System.Management.Automation/engine/COM/ComTypeInfo.cs @@ -105,7 +105,10 @@ private void Initialize() for (int i = 0; i < typeattr.cFuncs; i++) { COM.FUNCDESC funcdesc = GetFuncDesc(_typeinfo, i); - if (funcdesc.memid == DISPID_NEWENUM) { NewEnumInvokeKind = funcdesc.invkind; } + if (funcdesc.memid == DISPID_NEWENUM) + { + NewEnumInvokeKind = funcdesc.invkind; + } if ((funcdesc.wFuncFlags & 0x1) == 0x1) { @@ -183,10 +186,7 @@ private void AddProperty(string strName, COM.FUNCDESC funcdesc, int index) _properties[strName] = prop; } - if (prop != null) - { - prop.UpdateFuncDesc(funcdesc, index); - } + prop?.UpdateFuncDesc(funcdesc, index); } private void AddMethod(string strName, int index) @@ -198,10 +198,7 @@ private void AddMethod(string strName, int index) _methods[strName] = method; } - if (method != null) - { - method.AddFuncDesc(index); - } + method?.AddFuncDesc(index); } /// @@ -209,7 +206,6 @@ private void AddMethod(string strName, int index) /// /// Reference to ITypeInfo from which to get TypeAttr. /// - [ArchitectureSensitive] internal static COM.TYPEATTR GetTypeAttr(COM.ITypeInfo typeinfo) { IntPtr pTypeAttr; @@ -224,7 +220,6 @@ internal static COM.TYPEATTR GetTypeAttr(COM.ITypeInfo typeinfo) /// /// /// - [ArchitectureSensitive] internal static COM.FUNCDESC GetFuncDesc(COM.ITypeInfo typeinfo, int index) { IntPtr pFuncDesc; diff --git a/src/System.Management.Automation/engine/CmdletInfo.cs b/src/System.Management.Automation/engine/CmdletInfo.cs index 05b7548f9fc..cf3ba1cab13 100644 --- a/src/System.Management.Automation/engine/CmdletInfo.cs +++ b/src/System.Management.Automation/engine/CmdletInfo.cs @@ -9,7 +9,7 @@ namespace System.Management.Automation { /// - /// The command information for MSH cmdlets that are directly executable by MSH. + /// The command information for cmdlets that are directly executable by PowerShell. /// public class CmdletInfo : CommandInfo { @@ -380,11 +380,8 @@ public override ReadOnlyCollection OutputType } } - if (provider == null) - { - // No path argument, so just use the current path to choose the provider. - provider = Context.SessionState.Path.CurrentLocation.Provider; - } + // If no path argument, just use the current path to choose the provider. + provider ??= Context.SessionState.Path.CurrentLocation.Provider; provider.GetOutputTypes(Name, providerTypes); if (providerTypes.Count > 0) diff --git a/src/System.Management.Automation/engine/CmdletParameterBinderController.cs b/src/System.Management.Automation/engine/CmdletParameterBinderController.cs index 440b9ead537..88fadc3342d 100644 --- a/src/System.Management.Automation/engine/CmdletParameterBinderController.cs +++ b/src/System.Management.Automation/engine/CmdletParameterBinderController.cs @@ -202,10 +202,7 @@ internal void BindCommandLineParameters(Collection arg internal void BindCommandLineParametersNoValidation(Collection arguments) { var psCompiledScriptCmdlet = this.Command as PSScriptCmdlet; - if (psCompiledScriptCmdlet != null) - { - psCompiledScriptCmdlet.PrepareForBinding(this.CommandLineParameters); - } + psCompiledScriptCmdlet?.PrepareForBinding(this.CommandLineParameters); InitUnboundArguments(arguments); CommandMetadata cmdletMetadata = _commandMetadata; @@ -270,8 +267,7 @@ internal void BindCommandLineParametersNoValidation(Collection(); + ObsoleteParameterWarningList ??= new List(); ObsoleteParameterWarningList.Add(warningRecord); } @@ -1628,7 +1620,10 @@ private void HandleCommandLineDynamicParameters(out ParameterBindingException ou } catch (Exception e) // Catch-all OK, this is a third-party callout { - if (e is ProviderInvocationException) { throw; } + if (e is ProviderInvocationException) + { + throw; + } ParameterBindingException bindingException = new ParameterBindingException( @@ -2661,7 +2656,7 @@ Cmdlet command IEnumerable allParameterSetMetadatas = boundParameters.Values .Concat(unboundParameters) - .SelectMany(p => p.Parameter.ParameterSetData.Values); + .SelectMany(static p => p.Parameter.ParameterSetData.Values); uint allParameterSetFlags = 0; foreach (ParameterSetSpecificMetadata parameterSetMetadata in allParameterSetMetadatas) { @@ -2675,8 +2670,8 @@ Cmdlet command "This method should only be called when there is an ambiguity wrt parameter sets"); IEnumerable parameterSetMetadatasForUnboundMandatoryParameters = unboundParameters - .SelectMany(p => p.Parameter.ParameterSetData.Values) - .Where(p => p.IsMandatory); + .SelectMany(static p => p.Parameter.ParameterSetData.Values) + .Where(static p => p.IsMandatory); foreach (ParameterSetSpecificMetadata parameterSetMetadata in parameterSetMetadatasForUnboundMandatoryParameters) { remainingParameterSetsWithNoMandatoryUnboundParameters &= (~parameterSetMetadata.ParameterSetFlag); @@ -2991,7 +2986,7 @@ internal static string BuildMissingParamsString(CollectionThe original event with handler added. private object InPlaceAdd(object handler) { - Requires.NotNull(handler, nameof(handler)); + Requires.NotNull(handler); VerifyHandler(handler); ComEventsSink comEventSink = ComEventsSink.FromRuntimeCallableWrapper(_rcw, _sourceIid, true); @@ -88,7 +88,7 @@ private object InPlaceAdd(object handler) /// The original event with handler removed. private object InPlaceSubtract(object handler) { - Requires.NotNull(handler, nameof(handler)); + Requires.NotNull(handler); VerifyHandler(handler); ComEventsSink comEventSink = ComEventsSink.FromRuntimeCallableWrapper(_rcw, _sourceIid, false); diff --git a/src/System.Management.Automation/engine/ComInterop/ComBinder.cs b/src/System.Management.Automation/engine/ComInterop/ComBinder.cs index 726f336ec71..a88abc09f1e 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComBinder.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComBinder.cs @@ -35,8 +35,8 @@ public static bool IsComObject(object value) /// True if operation was bound successfully; otherwise, false. public static bool TryBindGetMember(GetMemberBinder binder, DynamicMetaObject instance, out DynamicMetaObject result, bool delayInvocation) { - Requires.NotNull(binder, nameof(binder)); - Requires.NotNull(instance, nameof(instance)); + Requires.NotNull(binder); + Requires.NotNull(instance); if (TryGetMetaObject(ref instance)) { @@ -66,9 +66,9 @@ public static bool TryBindGetMember(GetMemberBinder binder, DynamicMetaObject in /// True if operation was bound successfully; otherwise, false. public static bool TryBindSetMember(SetMemberBinder binder, DynamicMetaObject instance, DynamicMetaObject value, out DynamicMetaObject result) { - Requires.NotNull(binder, nameof(binder)); - Requires.NotNull(instance, nameof(instance)); - Requires.NotNull(value, nameof(value)); + Requires.NotNull(binder); + Requires.NotNull(instance); + Requires.NotNull(value); if (TryGetMetaObject(ref instance)) { @@ -91,9 +91,9 @@ public static bool TryBindSetMember(SetMemberBinder binder, DynamicMetaObject in /// True if operation was bound successfully; otherwise, false. public static bool TryBindInvoke(InvokeBinder binder, DynamicMetaObject instance, DynamicMetaObject[] args, out DynamicMetaObject result) { - Requires.NotNull(binder, nameof(binder)); - Requires.NotNull(instance, nameof(instance)); - Requires.NotNull(args, nameof(args)); + Requires.NotNull(binder); + Requires.NotNull(instance); + Requires.NotNull(args); if (TryGetMetaObjectInvoke(ref instance)) { @@ -116,9 +116,9 @@ public static bool TryBindInvoke(InvokeBinder binder, DynamicMetaObject instance /// True if operation was bound successfully; otherwise, false. public static bool TryBindInvokeMember(InvokeMemberBinder binder, bool isSetProperty, DynamicMetaObject instance, DynamicMetaObject[] args, out DynamicMetaObject result) { - Requires.NotNull(binder, nameof(binder)); - Requires.NotNull(instance, nameof(instance)); - Requires.NotNull(args, nameof(args)); + Requires.NotNull(binder); + Requires.NotNull(instance); + Requires.NotNull(args); if (TryGetMetaObject(ref instance)) { @@ -158,9 +158,9 @@ public static bool TryBindInvokeMember(InvokeMemberBinder binder, bool isSetProp /// True if operation was bound successfully; otherwise, false. public static bool TryBindGetIndex(GetIndexBinder binder, DynamicMetaObject instance, DynamicMetaObject[] args, out DynamicMetaObject result) { - Requires.NotNull(binder, nameof(binder)); - Requires.NotNull(instance, nameof(instance)); - Requires.NotNull(args, nameof(args)); + Requires.NotNull(binder); + Requires.NotNull(instance); + Requires.NotNull(args); if (TryGetMetaObjectInvoke(ref instance)) { @@ -183,10 +183,10 @@ public static bool TryBindGetIndex(GetIndexBinder binder, DynamicMetaObject inst /// True if operation was bound successfully; otherwise, false. public static bool TryBindSetIndex(SetIndexBinder binder, DynamicMetaObject instance, DynamicMetaObject[] args, DynamicMetaObject value, out DynamicMetaObject result) { - Requires.NotNull(binder, nameof(binder)); - Requires.NotNull(instance, nameof(instance)); - Requires.NotNull(args, nameof(args)); - Requires.NotNull(value, nameof(value)); + Requires.NotNull(binder); + Requires.NotNull(instance); + Requires.NotNull(args); + Requires.NotNull(value); if (TryGetMetaObjectInvoke(ref instance)) { @@ -208,8 +208,8 @@ public static bool TryBindSetIndex(SetIndexBinder binder, DynamicMetaObject inst /// True if operation was bound successfully; otherwise, false. public static bool TryConvert(ConvertBinder binder, DynamicMetaObject instance, out DynamicMetaObject result) { - Requires.NotNull(binder, nameof(binder)); - Requires.NotNull(instance, nameof(instance)); + Requires.NotNull(binder); + Requires.NotNull(instance); if (IsComObject(instance.Value)) { @@ -245,7 +245,7 @@ public static bool TryConvert(ConvertBinder binder, DynamicMetaObject instance, /// The collection of member names. internal static IList GetDynamicDataMemberNames(object value) { - Requires.NotNull(value, nameof(value)); + Requires.NotNull(value); Requires.Condition(IsComObject(value), nameof(value)); return ComObject.ObjectToComObject(value).GetMemberNames(true); @@ -260,7 +260,7 @@ internal static IList GetDynamicDataMemberNames(object value) /// The collection of pairs that represent data member's names and their data. internal static IList> GetDynamicDataMembers(object value, IEnumerable names) { - Requires.NotNull(value, nameof(value)); + Requires.NotNull(value); Requires.Condition(IsComObject(value), nameof(value)); return ComObject.ObjectToComObject(value).GetMembers(names); @@ -310,8 +310,8 @@ internal class ComGetMemberBinder : GetMemberBinder private readonly GetMemberBinder _originalBinder; internal bool _canReturnCallables; - internal ComGetMemberBinder(GetMemberBinder originalBinder, bool canReturnCallables) : - base(originalBinder.Name, originalBinder.IgnoreCase) + internal ComGetMemberBinder(GetMemberBinder originalBinder, bool canReturnCallables) + : base(originalBinder.Name, originalBinder.IgnoreCase) { _originalBinder = originalBinder; _canReturnCallables = canReturnCallables; @@ -343,8 +343,8 @@ internal class ComInvokeMemberBinder : InvokeMemberBinder private readonly InvokeMemberBinder _originalBinder; internal bool IsPropertySet; - internal ComInvokeMemberBinder(InvokeMemberBinder originalBinder, bool isPropertySet) : - base(originalBinder.Name, originalBinder.IgnoreCase, originalBinder.CallInfo) + internal ComInvokeMemberBinder(InvokeMemberBinder originalBinder, bool isPropertySet) + : base(originalBinder.Name, originalBinder.IgnoreCase, originalBinder.CallInfo) { _originalBinder = originalBinder; this.IsPropertySet = isPropertySet; diff --git a/src/System.Management.Automation/engine/ComInterop/ComEventsSink.Extended.cs b/src/System.Management.Automation/engine/ComInterop/ComEventsSink.Extended.cs index f52ded959c2..0d63276a3ea 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComEventsSink.Extended.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComEventsSink.Extended.cs @@ -18,10 +18,7 @@ private void Initialize(object rcw, Guid iid) public void AddHandler(int dispid, object func) { ComEventsMethod method = FindMethod(dispid); - if (method == null) - { - method = AddMethod(dispid); - } + method ??= AddMethod(dispid); if (func is Delegate d) { diff --git a/src/System.Management.Automation/engine/ComInterop/ComFallbackMetaObject.cs b/src/System.Management.Automation/engine/ComInterop/ComFallbackMetaObject.cs index 758281c7ac4..8a6e81f0800 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComFallbackMetaObject.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComFallbackMetaObject.cs @@ -23,31 +23,31 @@ internal ComFallbackMetaObject(Expression expression, BindingRestrictions restri public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) { - Requires.NotNull(binder, nameof(binder)); + Requires.NotNull(binder); return binder.FallbackGetIndex(UnwrapSelf(), indexes); } public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) { - Requires.NotNull(binder, nameof(binder)); + Requires.NotNull(binder); return binder.FallbackSetIndex(UnwrapSelf(), indexes, value); } public override DynamicMetaObject BindGetMember(GetMemberBinder binder) { - Requires.NotNull(binder, nameof(binder)); + Requires.NotNull(binder); return binder.FallbackGetMember(UnwrapSelf()); } public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) { - Requires.NotNull(binder, nameof(binder)); + Requires.NotNull(binder); return binder.FallbackInvokeMember(UnwrapSelf(), args); } public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) { - Requires.NotNull(binder, nameof(binder)); + Requires.NotNull(binder); return binder.FallbackSetMember(UnwrapSelf(), value); } diff --git a/src/System.Management.Automation/engine/ComInterop/ComInvokeBinder.cs b/src/System.Management.Automation/engine/ComInterop/ComInvokeBinder.cs index 6876425acf7..3765f5ba68b 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComInvokeBinder.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComInvokeBinder.cs @@ -114,10 +114,7 @@ private ParameterExpression ParamVariantsVariable { get { - if (_paramVariants == null) - { - _paramVariants = Expression.Variable(VariantArray.GetStructType(_args.Length), "paramVariants"); - } + _paramVariants ??= Expression.Variable(VariantArray.GetStructType(_args.Length), "paramVariants"); return _paramVariants; } } @@ -140,10 +137,7 @@ private static Type MarshalType(DynamicMetaObject mo, bool isByRef) if (isByRef) { // Null just means that null was supplied. - if (marshalType == null) - { - marshalType = mo.Expression.Type; - } + marshalType ??= mo.Expression.Type; marshalType = marshalType.MakeByRefType(); } return marshalType; @@ -173,7 +167,10 @@ internal DynamicMetaObject Invoke() private static void AddNotNull(List list, ParameterExpression var) { - if (var != null) list.Add(var); + if (var != null) + { + list.Add(var); + } } private Expression CreateScope(Expression expression) @@ -398,7 +395,7 @@ private Expression GenerateFinallyBlock() } /// - /// Create a stub for the target of the optimized lopop. + /// Create a stub for the target of the optimized loop. /// /// private Expression MakeIDispatchInvokeTarget() diff --git a/src/System.Management.Automation/engine/ComInterop/ComMetaObject.cs b/src/System.Management.Automation/engine/ComInterop/ComMetaObject.cs index bc75f83ba0a..39c9d1dbe05 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComMetaObject.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComMetaObject.cs @@ -16,37 +16,37 @@ internal ComMetaObject(Expression expression, BindingRestrictions restrictions, public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) { - Requires.NotNull(binder, nameof(binder)); + Requires.NotNull(binder); return binder.Defer(args.AddFirst(WrapSelf())); } public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) { - Requires.NotNull(binder, nameof(binder)); + Requires.NotNull(binder); return binder.Defer(args.AddFirst(WrapSelf())); } public override DynamicMetaObject BindGetMember(GetMemberBinder binder) { - Requires.NotNull(binder, nameof(binder)); + Requires.NotNull(binder); return binder.Defer(WrapSelf()); } public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) { - Requires.NotNull(binder, nameof(binder)); + Requires.NotNull(binder); return binder.Defer(WrapSelf(), value); } public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) { - Requires.NotNull(binder, nameof(binder)); + Requires.NotNull(binder); return binder.Defer(WrapSelf(), indexes); } public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) { - Requires.NotNull(binder, nameof(binder)); + Requires.NotNull(binder); return binder.Defer(WrapSelf(), indexes.AddLast(value)); } diff --git a/src/System.Management.Automation/engine/ComInterop/ComMethodDesc.cs b/src/System.Management.Automation/engine/ComInterop/ComMethodDesc.cs index 35b4c9fa25d..45932e1598a 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComMethodDesc.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComMethodDesc.cs @@ -95,7 +95,9 @@ public bool IsPropertyPutRef } internal int ParamCount { get; } + public Type ReturnType { get; set; } + public Type InputType { get; set; } public ParameterInformation[] ParameterInformation diff --git a/src/System.Management.Automation/engine/ComInterop/ComRuntimeHelpers.cs b/src/System.Management.Automation/engine/ComInterop/ComRuntimeHelpers.cs index c10f1424cf5..5bd086874a6 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComRuntimeHelpers.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComRuntimeHelpers.cs @@ -296,8 +296,15 @@ internal static class UnsafeMethods { #region public members - public static unsafe IntPtr ConvertInt32ByrefToPtr(ref int value) { return (IntPtr)System.Runtime.CompilerServices.Unsafe.AsPointer(ref value); } - public static unsafe IntPtr ConvertVariantByrefToPtr(ref Variant value) { return (IntPtr)System.Runtime.CompilerServices.Unsafe.AsPointer(ref value); } + public static unsafe IntPtr ConvertInt32ByrefToPtr(ref int value) + { + return (IntPtr)System.Runtime.CompilerServices.Unsafe.AsPointer(ref value); + } + + public static unsafe IntPtr ConvertVariantByrefToPtr(ref Variant value) + { + return (IntPtr)System.Runtime.CompilerServices.Unsafe.AsPointer(ref value); + } internal static Variant GetVariantForObject(object obj) { @@ -315,7 +322,7 @@ internal static void InitVariantForObject(object obj, ref Variant variant) Debug.Assert(obj != null); // GetNativeVariantForObject is very expensive for values that marshal as VT_DISPATCH - // also is is extremely common scenario when object at hand is an RCW. + // also is extremely common scenario when object at hand is an RCW. // Therefore we are going to test for IDispatch before defaulting to GetNativeVariantForObject. if (obj is IDispatch) { @@ -365,8 +372,7 @@ public static unsafe int IDispatchInvoke( fixed (ExcepInfo* pExcepInfo = &excepInfo) fixed (uint* pArgErr = &argErr) { - var pfnIDispatchInvoke = (delegate* unmanaged) - (*(*(void***)dispatchPointer + 6 /* IDispatch.Invoke slot */)); + var pfnIDispatchInvoke = (delegate* unmanaged)(*(*(void***)dispatchPointer + 6 /* IDispatch.Invoke slot */)); int hresult = pfnIDispatchInvoke(dispatchPointer, memberDispId, &IID_NULL, 0, (ushort)flags, pDispParams, pResult, pExcepInfo, pArgErr); @@ -375,7 +381,7 @@ public static unsafe int IDispatchInvoke( && (flags & ComTypes.INVOKEKIND.INVOKE_FUNC) != 0 && (flags & (ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT | ComTypes.INVOKEKIND.INVOKE_PROPERTYPUTREF)) == 0) { - // Re-invoke with no result argument to accomodate Word + // Re-invoke with no result argument to accommodate Word hresult = pfnIDispatchInvoke(dispatchPointer, memberDispId, &IID_NULL, 0, (ushort)ComTypes.INVOKEKIND.INVOKE_FUNC, pDispParams, null, pExcepInfo, pArgErr); } diff --git a/src/System.Management.Automation/engine/ComInterop/ComTypeClassDesc.cs b/src/System.Management.Automation/engine/ComInterop/ComTypeClassDesc.cs index 1ff4fac18cf..2f2886d6555 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComTypeClassDesc.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComTypeClassDesc.cs @@ -17,15 +17,11 @@ internal class ComTypeClassDesc : ComTypeDesc, IDynamicMetaObjectProvider public object CreateInstance() { - if (_typeObj == null) - { - _typeObj = Type.GetTypeFromCLSID(Guid); - } + _typeObj ??= Type.GetTypeFromCLSID(Guid); return Activator.CreateInstance(Type.GetTypeFromCLSID(Guid)); } - internal ComTypeClassDesc(ComTypes.ITypeInfo typeInfo, ComTypeLibDesc typeLibDesc) : - base(typeInfo, typeLibDesc) + internal ComTypeClassDesc(ComTypes.ITypeInfo typeInfo, ComTypeLibDesc typeLibDesc) : base(typeInfo, typeLibDesc) { ComTypes.TYPEATTR typeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(typeInfo); Guid = typeAttr.guid; @@ -47,19 +43,12 @@ private void AddInterface(ComTypes.ITypeInfo itfTypeInfo, bool isSourceItf) if (isSourceItf) { - if (_sourceItfs == null) - { - _sourceItfs = new LinkedList(); - } + _sourceItfs ??= new LinkedList(); _sourceItfs.AddLast(itfName); } else { - if (_itfs == null) - { - _itfs = new LinkedList(); - } - + _itfs ??= new LinkedList(); _itfs.AddLast(itfName); } } diff --git a/src/System.Management.Automation/engine/ComInterop/ComTypeDesc.cs b/src/System.Management.Automation/engine/ComInterop/ComTypeDesc.cs index 92e9ea8ed6f..a4b90913e9b 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComTypeDesc.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComTypeDesc.cs @@ -118,6 +118,7 @@ internal bool TryGetPutRef(string name, out ComMethodDesc method) method = null; return false; } + internal void AddPutRef(string name, ComMethodDesc method) { name = name.ToUpper(System.Globalization.CultureInfo.InvariantCulture); diff --git a/src/System.Management.Automation/engine/ComInterop/ComTypeEnumDesc.cs b/src/System.Management.Automation/engine/ComInterop/ComTypeEnumDesc.cs index 5752fac1e5f..00fe57c2b44 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComTypeEnumDesc.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComTypeEnumDesc.cs @@ -15,13 +15,9 @@ internal sealed class ComTypeEnumDesc : ComTypeDesc, IDynamicMetaObjectProvider private readonly string[] _memberNames; private readonly object[] _memberValues; - public override string ToString() - { - return string.Format(CultureInfo.CurrentCulture, "", TypeName); - } + public override string ToString() => $""; - internal ComTypeEnumDesc(ComTypes.ITypeInfo typeInfo, ComTypeLibDesc typeLibDesc) : - base(typeInfo, typeLibDesc) + internal ComTypeEnumDesc(ComTypes.ITypeInfo typeInfo, ComTypeLibDesc typeLibDesc) : base(typeInfo, typeLibDesc) { ComTypes.TYPEATTR typeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(typeInfo); string[] memberNames = new string[typeAttr.cVars]; diff --git a/src/System.Management.Automation/engine/ComInterop/ComTypeLibDesc.cs b/src/System.Management.Automation/engine/ComInterop/ComTypeLibDesc.cs index 2b81c76b6eb..8f373e52a72 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComTypeLibDesc.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComTypeLibDesc.cs @@ -32,10 +32,7 @@ private ComTypeLibDesc() _classes = new LinkedList(); } - public override string ToString() - { - return string.Format(CultureInfo.CurrentCulture, "", Name); - } + public override string ToString() => $""; public string Documentation { diff --git a/src/System.Management.Automation/engine/ComInterop/DispCallable.cs b/src/System.Management.Automation/engine/ComInterop/DispCallable.cs index df0f54009ac..e99bdd3741f 100644 --- a/src/System.Management.Automation/engine/ComInterop/DispCallable.cs +++ b/src/System.Management.Automation/engine/ComInterop/DispCallable.cs @@ -19,10 +19,7 @@ internal DispCallable(IDispatchComObject dispatch, string memberName, int dispId DispId = dispId; } - public override string ToString() - { - return string.Format(CultureInfo.CurrentCulture, "", MemberName); - } + public override string ToString() => $""; public IDispatchComObject DispatchComObject { get; } diff --git a/src/System.Management.Automation/engine/ComInterop/ExcepInfo.cs b/src/System.Management.Automation/engine/ComInterop/ExcepInfo.cs index 4f829b6241d..74314850a59 100644 --- a/src/System.Management.Automation/engine/ComInterop/ExcepInfo.cs +++ b/src/System.Management.Automation/engine/ComInterop/ExcepInfo.cs @@ -10,7 +10,7 @@ namespace System.Management.Automation.ComInterop { /// - /// This is similar to ComTypes.EXCEPINFO, but lets us do our own custom marshaling. + /// This is similar to ComTypes.EXCEPINFO, but lets us do our own custom marshalling. /// [StructLayout(LayoutKind.Sequential)] internal struct ExcepInfo diff --git a/src/System.Management.Automation/engine/ComInterop/Helpers.cs b/src/System.Management.Automation/engine/ComInterop/Helpers.cs index 513c3126476..a780e095e20 100644 --- a/src/System.Management.Automation/engine/ComInterop/Helpers.cs +++ b/src/System.Management.Automation/engine/ComInterop/Helpers.cs @@ -1,8 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#nullable enable + using System; using System.Linq.Expressions; +using System.Runtime.CompilerServices; namespace System.Management.Automation.ComInterop { @@ -33,12 +36,9 @@ internal static Expression Convert(Expression expression, Type type) internal static class Requires { [System.Diagnostics.Conditional("DEBUG")] - internal static void NotNull(object value, string paramName) + internal static void NotNull(object value, [CallerArgumentExpression("value")] string? paramName = null) { - if (value == null) - { - throw new ArgumentNullException(paramName); - } + ArgumentNullException.ThrowIfNull(value, paramName); } [System.Diagnostics.Conditional("DEBUG")] diff --git a/src/System.Management.Automation/engine/ComInterop/IDispatchComObject.cs b/src/System.Management.Automation/engine/ComInterop/IDispatchComObject.cs index c3a16639b62..7c2a3c106a7 100644 --- a/src/System.Management.Automation/engine/ComInterop/IDispatchComObject.cs +++ b/src/System.Management.Automation/engine/ComInterop/IDispatchComObject.cs @@ -22,7 +22,7 @@ namespace System.Management.Automation.ComInterop /// default arguments?). So obj.foo() is ambiguous as it could mean invoking method foo, /// or it could mean invoking the function pointer returned by property foo. /// We are attempting to find whether we need to call a method or a property by examining - /// the ITypeInfo associated with the IDispatch. ITypeInfo tell's use what parameters the method + /// the ITypeInfo associated with the IDispatch. ITypeInfo tells us what parameters the method /// expects, is it a method or a property, what is the default property of the object, how to /// create an enumerator for collections etc. /// @@ -99,7 +99,7 @@ public override string ToString() typeName = "IDispatch"; } - return string.Format(CultureInfo.CurrentCulture, "{0} ({1})", RuntimeCallableWrapper.ToString(), typeName); + return $"{RuntimeCallableWrapper} ({typeName})"; } public ComTypeDesc ComTypeDesc @@ -222,7 +222,7 @@ internal bool TryGetMemberMethodExplicit(string name, out ComMethodDesc method) return false; } - throw Error.CouldNotGetDispId(name, string.Format(CultureInfo.InvariantCulture, "0x{0:X})", hresult)); + throw Error.CouldNotGetDispId(name, string.Create(CultureInfo.InvariantCulture, $"0x{hresult:X})")); } internal bool TryGetPropertySetterExplicit(string name, out ComMethodDesc method, Type limitType, bool holdsNull) @@ -258,7 +258,7 @@ internal bool TryGetPropertySetterExplicit(string name, out ComMethodDesc method return false; } - throw Error.CouldNotGetDispId(name, string.Format(CultureInfo.InvariantCulture, "0x{0:X})", hresult)); + throw Error.CouldNotGetDispId(name, string.Create(CultureInfo.InvariantCulture, $"0x{hresult:X})")); } internal override IList GetMemberNames(bool dataOnly) @@ -271,10 +271,7 @@ internal override IList GetMemberNames(bool dataOnly) internal override IList> GetMembers(IEnumerable names) { - if (names == null) - { - names = GetMemberNames(true); - } + names ??= GetMemberNames(true); Type comType = RuntimeCallableWrapper.GetType(); diff --git a/src/System.Management.Automation/engine/ComInterop/IDispatchMetaObject.cs b/src/System.Management.Automation/engine/ComInterop/IDispatchMetaObject.cs index b340cf54967..9826ac9d467 100644 --- a/src/System.Management.Automation/engine/ComInterop/IDispatchMetaObject.cs +++ b/src/System.Management.Automation/engine/ComInterop/IDispatchMetaObject.cs @@ -23,7 +23,7 @@ internal IDispatchMetaObject(Expression expression, IDispatchComObject self) public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) { - Requires.NotNull(binder, nameof(binder)); + Requires.NotNull(binder); ComMethodDesc method = null; @@ -63,7 +63,7 @@ public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, Dy public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) { - Requires.NotNull(binder, nameof(binder)); + Requires.NotNull(binder); if (_self.TryGetGetItem(out ComMethodDesc method)) { @@ -108,7 +108,7 @@ public override DynamicMetaObject BindGetMember(GetMemberBinder binder) ComBinder.ComGetMemberBinder comBinder = binder as ComBinder.ComGetMemberBinder; bool canReturnCallables = comBinder?._canReturnCallables ?? false; - Requires.NotNull(binder, nameof(binder)); + Requires.NotNull(binder); // 1. Try methods if (_self.TryGetMemberMethod(binder.Name, out ComMethodDesc method)) @@ -187,7 +187,7 @@ private DynamicMetaObject BindEvent(ComEventDesc eventDesc) public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) { - Requires.NotNull(binder, nameof(binder)); + Requires.NotNull(binder); if (_self.TryGetGetItem(out ComMethodDesc getItem)) { @@ -203,7 +203,7 @@ public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMet public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) { - Requires.NotNull(binder, nameof(binder)); + Requires.NotNull(binder); if (_self.TryGetSetItem(out ComMethodDesc setItem)) { @@ -238,7 +238,7 @@ public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMet public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) { - Requires.NotNull(binder, nameof(binder)); + Requires.NotNull(binder); return // 1. Check for simple property put diff --git a/src/System.Management.Automation/engine/ComInterop/InteropServices/ComEventsMethod.cs b/src/System.Management.Automation/engine/ComInterop/InteropServices/ComEventsMethod.cs index d31ca0299c9..f8ff1e09855 100644 --- a/src/System.Management.Automation/engine/ComInterop/InteropServices/ComEventsMethod.cs +++ b/src/System.Management.Automation/engine/ComInterop/InteropServices/ComEventsMethod.cs @@ -83,10 +83,7 @@ private void PreProcessSignature() && pi.ParameterType.HasElementType && pi.ParameterType.GetElementType()!.IsEnum) { - if (targetTypes == null) - { - targetTypes = new Type?[_expectedParamsCount]; - } + targetTypes ??= new Type?[_expectedParamsCount]; targetTypes[i] = pi.ParameterType.GetElementType(); } diff --git a/src/System.Management.Automation/engine/ComInterop/InteropServices/Variant.cs b/src/System.Management.Automation/engine/ComInterop/InteropServices/Variant.cs index 24405957f72..941f471b666 100644 --- a/src/System.Management.Automation/engine/ComInterop/InteropServices/Variant.cs +++ b/src/System.Management.Automation/engine/ComInterop/InteropServices/Variant.cs @@ -276,9 +276,6 @@ public unsafe void CopyFromIndirect(object value) } } - [DllImport("oleaut32.dll")] - internal static extern void VariantClear(IntPtr variant); - /// /// Release any unmanaged memory associated with the Variant /// @@ -306,7 +303,7 @@ public void Clear() { fixed (void* pThis = &this) { - VariantClear((IntPtr)pThis); + Interop.Windows.VariantClear((nint)pThis); } } @@ -343,6 +340,7 @@ public sbyte AsI1 Debug.Assert(VariantType == VarEnum.VT_I1); return _typeUnion._unionTypes._i1; } + set { Debug.Assert(IsEmpty); @@ -360,6 +358,7 @@ public short AsI2 Debug.Assert(VariantType == VarEnum.VT_I2); return _typeUnion._unionTypes._i2; } + set { Debug.Assert(IsEmpty); @@ -377,6 +376,7 @@ public int AsI4 Debug.Assert(VariantType == VarEnum.VT_I4); return _typeUnion._unionTypes._i4; } + set { Debug.Assert(IsEmpty); @@ -394,6 +394,7 @@ public long AsI8 Debug.Assert(VariantType == VarEnum.VT_I8); return _typeUnion._unionTypes._i8; } + set { Debug.Assert(IsEmpty); @@ -411,6 +412,7 @@ public byte AsUi1 Debug.Assert(VariantType == VarEnum.VT_UI1); return _typeUnion._unionTypes._ui1; } + set { Debug.Assert(IsEmpty); @@ -428,6 +430,7 @@ public ushort AsUi2 Debug.Assert(VariantType == VarEnum.VT_UI2); return _typeUnion._unionTypes._ui2; } + set { Debug.Assert(IsEmpty); @@ -445,6 +448,7 @@ public uint AsUi4 Debug.Assert(VariantType == VarEnum.VT_UI4); return _typeUnion._unionTypes._ui4; } + set { Debug.Assert(IsEmpty); @@ -462,6 +466,7 @@ public ulong AsUi8 Debug.Assert(VariantType == VarEnum.VT_UI8); return _typeUnion._unionTypes._ui8; } + set { Debug.Assert(IsEmpty); @@ -479,6 +484,7 @@ public int AsInt Debug.Assert(VariantType == VarEnum.VT_INT); return _typeUnion._unionTypes._int; } + set { Debug.Assert(IsEmpty); @@ -496,6 +502,7 @@ public uint AsUint Debug.Assert(VariantType == VarEnum.VT_UINT); return _typeUnion._unionTypes._uint; } + set { Debug.Assert(IsEmpty); @@ -513,6 +520,7 @@ public bool AsBool Debug.Assert(VariantType == VarEnum.VT_BOOL); return _typeUnion._unionTypes._bool != 0; } + set { Debug.Assert(IsEmpty); @@ -532,6 +540,7 @@ public int AsError Debug.Assert(VariantType == VarEnum.VT_ERROR); return _typeUnion._unionTypes._error; } + set { Debug.Assert(IsEmpty); @@ -549,6 +558,7 @@ public float AsR4 Debug.Assert(VariantType == VarEnum.VT_R4); return _typeUnion._unionTypes._r4; } + set { Debug.Assert(IsEmpty); @@ -566,6 +576,7 @@ public double AsR8 Debug.Assert(VariantType == VarEnum.VT_R8); return _typeUnion._unionTypes._r8; } + set { Debug.Assert(IsEmpty); @@ -586,6 +597,7 @@ public decimal AsDecimal v._typeUnion._vt = 0; return v._decimal; } + set { Debug.Assert(IsEmpty); @@ -605,6 +617,7 @@ public decimal AsCy Debug.Assert(VariantType == VarEnum.VT_CY); return decimal.FromOACurrency(_typeUnion._unionTypes._cy); } + set { Debug.Assert(IsEmpty); @@ -622,6 +635,7 @@ public DateTime AsDate Debug.Assert(VariantType == VarEnum.VT_DATE); return DateTime.FromOADate(_typeUnion._unionTypes._date); } + set { Debug.Assert(IsEmpty); @@ -639,6 +653,7 @@ public string AsBstr Debug.Assert(VariantType == VarEnum.VT_BSTR); return (string)Marshal.PtrToStringBSTR(this._typeUnion._unionTypes._bstr); } + set { Debug.Assert(IsEmpty); @@ -660,6 +675,7 @@ public object? AsUnknown } return Marshal.GetObjectForIUnknown(_typeUnion._unionTypes._unknown); } + set { Debug.Assert(IsEmpty); @@ -688,6 +704,7 @@ public object? AsDispatch } return Marshal.GetObjectForIUnknown(_typeUnion._unionTypes._dispatch); } + set { Debug.Assert(IsEmpty); diff --git a/src/System.Management.Automation/engine/ComInterop/SplatCallSite.cs b/src/System.Management.Automation/engine/ComInterop/SplatCallSite.cs index 68c394033fc..926bb460a09 100644 --- a/src/System.Management.Automation/engine/ComInterop/SplatCallSite.cs +++ b/src/System.Management.Automation/engine/ComInterop/SplatCallSite.cs @@ -24,15 +24,13 @@ internal SplatCallSite(object callable) } public delegate object InvokeDelegate(object[] args); + internal object Invoke(object[] args) { Debug.Assert(args != null); // Create a CallSite and invoke it. - if (_site == null) - { - _site = CallSite>.Create(SplatInvokeBinder.Instance); - } + _site ??= CallSite>.Create(SplatInvokeBinder.Instance); return _site.Target(_site, _callable, args); } diff --git a/src/System.Management.Automation/engine/ComInterop/TypeUtils.cs b/src/System.Management.Automation/engine/ComInterop/TypeUtils.cs index 0d4ee61dcf7..04217bd0bb6 100644 --- a/src/System.Management.Automation/engine/ComInterop/TypeUtils.cs +++ b/src/System.Management.Automation/engine/ComInterop/TypeUtils.cs @@ -38,6 +38,7 @@ internal static bool AreReferenceAssignable(Type dest, Type src) } return false; } + //CONFORMING internal static bool AreAssignable(Type dest, Type src) { @@ -101,11 +102,9 @@ internal static MethodInfo GetUserDefinedCoercionMethod(Type convertFrom, Type c // try lifted conversion if (nnExprType != convertFrom || nnConvType != convertToType) { - method = FindConversionOperator(eMethods, nnExprType, nnConvType, implicitOnly); - if (method == null) - { - method = FindConversionOperator(cMethods, nnExprType, nnConvType, implicitOnly); - } + method = + FindConversionOperator(eMethods, nnExprType, nnConvType, implicitOnly) ?? + FindConversionOperator(cMethods, nnExprType, nnConvType, implicitOnly); if (method != null) { return method; diff --git a/src/System.Management.Automation/engine/ComInterop/VarEnumSelector.cs b/src/System.Management.Automation/engine/ComInterop/VarEnumSelector.cs index c7f6b120919..a9e1594ae1c 100644 --- a/src/System.Management.Automation/engine/ComInterop/VarEnumSelector.cs +++ b/src/System.Management.Automation/engine/ComInterop/VarEnumSelector.cs @@ -98,7 +98,7 @@ internal static Type GetTypeForVarEnum(VarEnum vt) } /// - /// Gets the managed type that an object needs to be coverted to in order for it to be able + /// Gets the managed type that an object needs to be converted to in order for it to be able /// to be represented as a Variant. /// /// In general, there is a many-to-many mapping between Type and VarEnum. However, this method @@ -211,7 +211,7 @@ private static List GetConversionsToComPrimitiveTypeFamilies(Type argum if (TypeUtils.IsImplicitlyConvertible(argumentType, candidateManagedType, true)) { compatibleComTypes.Add(candidateType); - // Move on to the next type family. We need atmost one type from each family + // Move on to the next type family. We need at most one type from each family break; } } @@ -352,7 +352,7 @@ private static bool TryGetPrimitiveComTypeViaConversion(Type argumentType, out V // We will try VT_DISPATCH and then call GetNativeVariantForObject. private const VarEnum VT_DEFAULT = VarEnum.VT_RECORD; - private VarEnum GetComType(ref Type argumentType) + private static VarEnum GetComType(ref Type argumentType) { if (argumentType == typeof(Missing)) { @@ -429,9 +429,9 @@ private VarEnum GetComType(ref Type argumentType) } /// - /// Get the COM Variant type that argument should be marshaled as for a call to COM. + /// Get the COM Variant type that argument should be marshalled as for a call to COM. /// - private VariantBuilder GetVariantBuilder(Type argumentType) + private static VariantBuilder GetVariantBuilder(Type argumentType) { //argumentType is coming from MarshalType, null means the dynamic object holds //a null value and not byref @@ -455,7 +455,7 @@ private VariantBuilder GetVariantBuilder(Type argumentType) if (elementType == typeof(object) || elementType == typeof(DBNull)) { //no meaningful value to pass ByRef. - //perhaps the calee will replace it with something. + //perhaps the callee will replace it with something. //need to pass as a variant reference elementVarEnum = VarEnum.VT_VARIANT; } diff --git a/src/System.Management.Automation/engine/ComInterop/Variant.Extended.cs b/src/System.Management.Automation/engine/ComInterop/Variant.Extended.cs index 3a47dbd2ba4..9cab38d0773 100644 --- a/src/System.Management.Automation/engine/ComInterop/Variant.Extended.cs +++ b/src/System.Management.Automation/engine/ComInterop/Variant.Extended.cs @@ -282,10 +282,7 @@ internal static System.Reflection.MethodInfo GetByrefSetter(VarEnum varType) } } - public override string ToString() - { - return string.Format(CultureInfo.CurrentCulture, "Variant ({0})", VariantType); - } + public override string ToString() => $"Variant ({VariantType})"; public void SetAsIConvertible(IConvertible value) { diff --git a/src/System.Management.Automation/engine/ComInterop/VariantArray.cs b/src/System.Management.Automation/engine/ComInterop/VariantArray.cs index a196eb96079..d4f8af57b74 100644 --- a/src/System.Management.Automation/engine/ComInterop/VariantArray.cs +++ b/src/System.Management.Automation/engine/ComInterop/VariantArray.cs @@ -58,10 +58,25 @@ internal static MemberExpression GetStructField(ParameterExpression variantArray internal static Type GetStructType(int args) { Debug.Assert(args >= 0); - if (args <= 1) return typeof(VariantArray1); - if (args <= 2) return typeof(VariantArray2); - if (args <= 4) return typeof(VariantArray4); - if (args <= 8) return typeof(VariantArray8); + if (args <= 1) + { + return typeof(VariantArray1); + } + + if (args <= 2) + { + return typeof(VariantArray2); + } + + if (args <= 4) + { + return typeof(VariantArray4); + } + + if (args <= 8) + { + return typeof(VariantArray8); + } int size = 1; while (args > size) diff --git a/src/System.Management.Automation/engine/ComInterop/VariantBuilder.cs b/src/System.Management.Automation/engine/ComInterop/VariantBuilder.cs index edcb0eb0f10..baabb25cd75 100644 --- a/src/System.Management.Automation/engine/ComInterop/VariantBuilder.cs +++ b/src/System.Management.Automation/engine/ComInterop/VariantBuilder.cs @@ -16,6 +16,7 @@ internal class VariantBuilder private MemberExpression _variant; private readonly ArgBuilder _argBuilder; private readonly VarEnum _targetComType; + internal ParameterExpression TempVariable { get; private set; } internal VariantBuilder(VarEnum targetComType, ArgBuilder builder) diff --git a/src/System.Management.Automation/engine/CommandBase.cs b/src/System.Management.Automation/engine/CommandBase.cs index da2e97e516b..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 } } + /// + /// Gets the CancellationToken that is signaled when the pipeline is stopping. + /// + internal CancellationToken StopToken => commandRuntime is MshCommandRuntime mcr + ? mcr.PipelineProcessor.PipelineStopToken + : default; + /// /// The information about the command. /// @@ -233,6 +239,13 @@ internal virtual void DoStopProcessing() { } + /// + /// When overridden in the derived class, performs clean-up after the command execution. + /// + internal virtual void DoCleanResource() + { + } + #endregion Override /// @@ -272,6 +285,26 @@ internal void InternalDispose(bool isDisposing) namespace System.Management.Automation { + #region NativeArgumentPassingStyle + /// + /// Defines the different native command argument parsing options. + /// + public enum NativeArgumentPassingStyle + { + /// Use legacy argument parsing via ProcessStartInfo.Arguments. + Legacy = 0, + + /// Use new style argument passing via ProcessStartInfo.ArgumentList. + Standard = 1, + + /// + /// Use specific to Windows passing style which is Legacy for selected files on Windows, but + /// Standard for everything else. This is the default behavior for Windows. + /// + Windows = 2 + } + #endregion NativeArgumentPassingStyle + #region ErrorView /// /// Defines the potential ErrorView options. @@ -286,6 +319,9 @@ public enum ErrorView /// Concise shows more information on the context of the error or just the message if not a script or parser error. ConciseView = 2, + + /// Detailed will leverage Get-Error to get much more detailed information for the error. + DetailedView = 3, } #endregion ErrorView @@ -367,11 +403,11 @@ public enum ConfirmImpact /// deriving from the PSCmdlet base class. The Cmdlet base class is the primary means by /// which users create their own Cmdlets. Extending this class provides support for the most /// common functionality, including object output and record processing. - /// If your Cmdlet requires access to the MSH Runtime (for example, variables in the session state, + /// If your Cmdlet requires access to the PowerShell Runtime (for example, variables in the session state, /// access to the host, or information about the current Cmdlet Providers,) then you should instead /// derive from the PSCmdlet base class. /// The public members defined by the PSCmdlet class are not designed to be overridden; instead, they - /// provided access to different aspects of the MSH runtime. + /// provided access to different aspects of the PowerShell runtime. /// In both cases, users should first develop and implement an object model to accomplish their /// task, extending the Cmdlet or PSCmdlet classes only as a thin management layer. /// diff --git a/src/System.Management.Automation/engine/CommandCompletion/CommandCompletion.cs b/src/System.Management.Automation/engine/CommandCompletion/CommandCompletion.cs index f31065080c6..9956cf9fa92 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CommandCompletion.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CommandCompletion.cs @@ -4,13 +4,11 @@ using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; -using System.Text.RegularExpressions; #if LEGACYTELEMETRY +using System.Diagnostics; using Microsoft.PowerShell.Telemetry.Internal; #endif @@ -93,7 +91,7 @@ public static Tuple MapStringInputToParsedInput(s /// public static CommandCompletion CompleteInput(string input, int cursorIndex, Hashtable options) { - if (input == null) + if (input == null || input.Length == 0) { return s_emptyCommandCompletion; } @@ -126,12 +124,16 @@ public static CommandCompletion CompleteInput(Ast ast, Token[] tokens, IScriptPo throw PSTraceSource.NewArgumentNullException(nameof(positionOfCursor)); } + if (ast.Extent.Text.Length == 0) + { + return s_emptyCommandCompletion; + } + return CompleteInputImpl(ast, tokens, positionOfCursor, options); } /// /// Invokes the script function TabExpansion2. - /// For legacy support, TabExpansion2 will indirectly call TabExpansion if it exists. /// /// The input script to complete. /// The offset in where completion is requested. @@ -141,7 +143,7 @@ public static CommandCompletion CompleteInput(Ast ast, Token[] tokens, IScriptPo [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "powershell")] public static CommandCompletion CompleteInput(string input, int cursorIndex, Hashtable options, PowerShell powershell) { - if (input == null) + if (input == null || input.Length == 0) { return s_emptyCommandCompletion; } @@ -180,21 +182,11 @@ public static CommandCompletion CompleteInput(string input, int cursorIndex, Has if (!powershell.IsChild) { CheckScriptCallOnRemoteRunspace(remoteRunspace); + + // TabExpansion2 script is not available prior to PSv3. if (remoteRunspace.GetCapabilities().Equals(Runspaces.RunspaceCapability.Default)) { - // Capability: - // NamedPipeTransport (0x2) -> If remoteMachine is Threshold or later - // SupportsDisconnect (0x1) -> If remoteMachine is Win8 or later - // Default (0x0) -> If remoteMachine is Win7 - // Remoting to a Win7 machine. Use the legacy tab completion function from V1/V2 - int replacementIndex; - int replacementLength; - - powershell.Commands.Clear(); - var results = InvokeLegacyTabExpansion(powershell, input, cursorIndex, true, out replacementIndex, out replacementLength); - return new CommandCompletion( - new Collection(results ?? EmptyCompletionResult), - -1, replacementIndex, replacementLength); + return s_emptyCommandCompletion; } } } @@ -204,7 +196,6 @@ public static CommandCompletion CompleteInput(string input, int cursorIndex, Has /// /// Invokes the script function TabExpansion2. - /// For legacy support, TabExpansion2 will indirectly call TabExpansion if it exists. /// /// The ast for pre-parsed input. /// @@ -235,6 +226,11 @@ public static CommandCompletion CompleteInput(Ast ast, Token[] tokens, IScriptPo throw PSTraceSource.NewArgumentNullException(nameof(powershell)); } + if (ast.Extent.Text.Length == 0) + { + return s_emptyCommandCompletion; + } + // If we are in a debugger stop, let the debugger do the command completion. var debugger = powershell.Runspace?.Debugger; if ((debugger != null) && debugger.InBreakpoint) @@ -255,31 +251,17 @@ public static CommandCompletion CompleteInput(Ast ast, Token[] tokens, IScriptPo if (!powershell.IsChild) { CheckScriptCallOnRemoteRunspace(remoteRunspace); + + // TabExpansion2 script is not available prior to PSv3. if (remoteRunspace.GetCapabilities().Equals(Runspaces.RunspaceCapability.Default)) { - // Capability: - // SupportsDisconnect (0x1) -> If remoteMachine is Win8 or later - // Default (0x0) -> If remoteMachine is Win7 - // Remoting to a Win7 machine. Use the legacy tab completion function from V1/V2 - int replacementIndex; - int replacementLength; - - // When call the win7 TabExpansion script, the input should be the single current line - powershell.Commands.Clear(); - var inputAndCursor = GetInputAndCursorFromAst(cursorPosition); - var results = InvokeLegacyTabExpansion(powershell, inputAndCursor.Item1, inputAndCursor.Item2, true, out replacementIndex, out replacementLength); - return new CommandCompletion( - new Collection(results ?? EmptyCompletionResult), - -1, replacementIndex + inputAndCursor.Item3, replacementLength); - } - else - { - // Call script on a remote win8 machine - // when call the win8 TabExpansion2 script, the input should be the whole script text - string input = ast.Extent.Text; - int cursorIndex = ((InternalScriptPosition)cursorPosition).Offset; - return CallScriptWithStringParameterSet(input, cursorIndex, options, powershell); + return s_emptyCommandCompletion; } + + // When calling the TabExpansion2 script, the input should be the whole script text + string input = ast.Extent.Text; + int cursorIndex = ((InternalScriptPosition)cursorPosition).Offset; + return CallScriptWithStringParameterSet(input, cursorIndex, options, powershell); } } @@ -526,810 +508,55 @@ private static CommandCompletion CompleteInputImpl(Ast ast, Token[] tokens, IScr { var context = LocalPipeline.GetExecutionContextFromTLS(); - bool cleanupModuleAnalysisAppDomain = context.TakeResponsibilityForModuleAnalysisAppDomain(); - - try - { - // First, check if a V1/V2 implementation of TabExpansion exists. If so, the user had overridden - // the built-in version, so we should continue to use theirs. - int replacementIndex = -1; - int replacementLength = -1; - List results = null; - - if (NeedToInvokeLegacyTabExpansion(powershell)) - { - var inputAndCursor = GetInputAndCursorFromAst(positionOfCursor); - results = InvokeLegacyTabExpansion(powershell, inputAndCursor.Item1, inputAndCursor.Item2, false, out replacementIndex, out replacementLength); - replacementIndex += inputAndCursor.Item3; - } - - if (results == null || results.Count == 0) - { - /* BROKEN code commented out, fix sometime - // If we were invoked from TabExpansion2, we want to "remove" TabExpansion2 and anything it calls - // from our results. We do this by faking out the session so that TabExpansion2 isn't anywhere to be found. - MutableTuple tupleForFrameToSkipPast = null; - foreach (var stackEntry in context.Debugger.GetCallStack()) - { - dynamic stackEntryAsPSObj = PSObject.AsPSObject(stackEntry); - if (stackEntryAsPSObj.Command.Equals("TabExpansion2", StringComparison.OrdinalIgnoreCase)) - { - tupleForFrameToSkipPast = stackEntry.FunctionContext._localsTuple; - break; - } - } + int replacementIndex = -1; + int replacementLength = -1; + List results = null; - SessionStateScope scopeToRestore = null; - if (tupleForFrameToSkipPast != null) - { - // Find this tuple in the scope stack. - scopeToRestore = context.EngineSessionState.CurrentScope; - var scope = context.EngineSessionState.CurrentScope; - while (scope != null && scope.LocalsTuple != tupleForFrameToSkipPast) - { - scope = scope.Parent; - } - - if (scope != null) - { - context.EngineSessionState.CurrentScope = scope.Parent; - } - } - - try - { - */ - var completionAnalysis = new CompletionAnalysis(ast, tokens, positionOfCursor, options); - results = completionAnalysis.GetResults(powershell, out replacementIndex, out replacementLength); - /* - } - finally - { - if (scopeToRestore != null) - { - context.EngineSessionState.CurrentScope = scopeToRestore; - } - } - */ - } - - var completionResults = results ?? EmptyCompletionResult; - -#if LEGACYTELEMETRY - // no telemetry here. We don't capture tab completion performance. - sw.Stop(); - TelemetryAPI.ReportTabCompletionTelemetry(sw.ElapsedMilliseconds, completionResults.Count, - completionResults.Count > 0 ? completionResults[0].ResultType : CompletionResultType.Text); -#endif - return new CommandCompletion( - new Collection(completionResults), - -1, - replacementIndex, - replacementLength); - } - finally { - if (cleanupModuleAnalysisAppDomain) + // If we were invoked from TabExpansion2, we want to "remove" TabExpansion2 and anything it calls + // from our results. We do this by faking out the session so that TabExpansion2 isn't anywhere to be found. + SessionStateScope scopeToRestore; + if (context.CurrentCommandProcessor is not null + && context.CurrentCommandProcessor.Command.CommandInfo.Name.Equals("TabExpansion2", StringComparison.OrdinalIgnoreCase) + && context.CurrentCommandProcessor.UseLocalScope + && context.EngineSessionState.CurrentScope.Parent is not null) { - context.ReleaseResponsibilityForModuleAnalysisAppDomain(); - } - } - } - } - - private static Tuple GetInputAndCursorFromAst(IScriptPosition cursorPosition) - { - var line = cursorPosition.Line; - var cursor = cursorPosition.ColumnNumber - 1; - var adjustment = cursorPosition.Offset - cursor; - return Tuple.Create(line.Substring(0, cursor), cursor, adjustment); - } - - private static bool NeedToInvokeLegacyTabExpansion(PowerShell powershell) - { - var executionContext = powershell.GetContextFromTLS(); - - // We don't want command discovery to search unloaded modules for TabExpansion. - var functionInfo = executionContext.EngineSessionState.GetFunction("TabExpansion"); - if (functionInfo != null) - return true; - - var aliasInfo = executionContext.EngineSessionState.GetAlias("TabExpansion"); - if (aliasInfo != null) - return true; - - return false; - } - - private static List InvokeLegacyTabExpansion(PowerShell powershell, string input, int cursorIndex, bool remoteToWin7, out int replacementIndex, out int replacementLength) - { - List results = null; - - var legacyInput = (cursorIndex != input.Length) ? input.Substring(0, cursorIndex) : input; - char quote; - var lastword = LastWordFinder.FindLastWord(legacyInput, out replacementIndex, out quote); - replacementLength = legacyInput.Length - replacementIndex; - var helper = new PowerShellExecutionHelper(powershell); - - powershell.AddCommand("TabExpansion").AddArgument(legacyInput).AddArgument(lastword); - - Exception exceptionThrown; - var oldResults = helper.ExecuteCurrentPowerShell(out exceptionThrown); - if (oldResults != null) - { - results = new List(); - foreach (var oldResult in oldResults) - { - var completionResult = PSObject.Base(oldResult) as CompletionResult; - if (completionResult == null) - { - var oldResultStr = oldResult.ToString(); - - // Add back the quotes we removed if the result isn't quoted - if (quote != '\0') - { - if (oldResultStr.Length > 2 && oldResultStr[0] != quote) - { - oldResultStr = quote + oldResultStr + quote; - } - } - - completionResult = new CompletionResult(oldResultStr); - } - - results.Add(completionResult); - } - } - - if (remoteToWin7 && (results == null || results.Count == 0)) - { - string quoteStr = quote == '\0' ? string.Empty : quote.ToString(); - results = PSv2CompletionCompleter.PSv2GenerateMatchSetOfFiles(helper, lastword, replacementIndex == 0, quoteStr); - var cmdletResults = PSv2CompletionCompleter.PSv2GenerateMatchSetOfCmdlets(helper, lastword, quoteStr, replacementIndex == 0); - - if (cmdletResults != null && cmdletResults.Count > 0) - { - results.AddRange(cmdletResults); - } - } - - return results; - } - - /// - /// PSv2CompletionCompleter implements the algorithm we use to complete cmdlet/file names in PowerShell v2. This class - /// exists for legacy purpose only. It is used only in a remote interactive session from Win8 to Win7. V3 and forward - /// uses completely different completers. - /// - /// - /// The implementation of file name completion is completely different on V2 and V3 for remote scenarios. On PSv3, the - /// CompletionResults are generated always on the target machine, and - /// - private static class PSv2CompletionCompleter - { - private static readonly Regex s_cmdletTabRegex = new Regex(@"^[\w\*\?]+-[\w\*\?]*"); - private static readonly char[] s_charsRequiringQuotedString = "`&@'#{}()$,;|<> \t".ToCharArray(); - - #region "Handle Command" - - /// - /// Used when remoting from a win8 machine to a win7 machine. - /// - /// - /// - /// - private static bool PSv2IsCommandLikeCmdlet(string lastWord, out bool isSnapinSpecified) - { - isSnapinSpecified = false; - - string[] cmdletParts = lastWord.Split(Utils.Separators.Backslash); - if (cmdletParts.Length == 1) - { - return s_cmdletTabRegex.IsMatch(lastWord); - } - - if (cmdletParts.Length == 2) - { - isSnapinSpecified = PSSnapInInfo.IsPSSnapinIdValid(cmdletParts[0]); - if (isSnapinSpecified) - { - return s_cmdletTabRegex.IsMatch(cmdletParts[1]); - } - } - - return false; - } - - private readonly struct CommandAndName - { - internal readonly PSObject Command; - internal readonly PSSnapinQualifiedName CommandName; - - internal CommandAndName(PSObject command, PSSnapinQualifiedName commandName) - { - this.Command = command; - this.CommandName = commandName; - } - } - - /// - /// Used when remoting from a win8 machine to a win7 machine. Complete command names. - /// - /// - /// - /// - /// - /// - internal static List PSv2GenerateMatchSetOfCmdlets(PowerShellExecutionHelper helper, string lastWord, string quote, bool completingAtStartOfLine) - { - var results = new List(); - bool isSnapinSpecified; - - if (!PSv2IsCommandLikeCmdlet(lastWord, out isSnapinSpecified)) - return results; - - helper.CurrentPowerShell - .AddCommand("Get-Command") - .AddParameter("Name", lastWord + "*") - .AddCommand("Sort-Object") - .AddParameter("Property", "Name"); - - Exception exceptionThrown; - Collection commands = helper.ExecuteCurrentPowerShell(out exceptionThrown); - - if (commands != null && commands.Count > 0) - { - // convert the PSObjects into strings - CommandAndName[] cmdlets = new CommandAndName[commands.Count]; - // if the command causes cmdlets from multiple mshsnapin is returned, - // append the mshsnapin name to disambiguate the cmdlets. - for (int i = 0; i < commands.Count; ++i) - { - PSObject command = commands[i]; - string cmdletFullName = CmdletInfo.GetFullName(command); - cmdlets[i] = new CommandAndName(command, PSSnapinQualifiedName.GetInstance(cmdletFullName)); - } - - if (isSnapinSpecified) - { - foreach (CommandAndName cmdlet in cmdlets) - { - AddCommandResult(cmdlet, true, completingAtStartOfLine, quote, results); - } + scopeToRestore = context.EngineSessionState.CurrentScope; + context.EngineSessionState.CurrentScope = scopeToRestore.Parent; } else { - PrependSnapInNameForSameCmdletNames(cmdlets, completingAtStartOfLine, quote, results); - } - } - - return results; - } - - private static void AddCommandResult(CommandAndName commandAndName, bool useFullName, bool completingAtStartOfLine, string quote, List results) - { - Diagnostics.Assert(results != null, "Caller needs to make sure the result list is not null"); - - string name = useFullName ? commandAndName.CommandName.FullName : commandAndName.CommandName.ShortName; - string quotedFileName = AddQuoteIfNecessary(name, quote, completingAtStartOfLine); - - var commandType = SafeGetProperty(commandAndName.Command, "CommandType"); - if (commandType == null) - { - return; - } - - string toolTip; - string displayName = SafeGetProperty(commandAndName.Command, "Name"); - - if (commandType.Value == CommandTypes.Cmdlet || commandType.Value == CommandTypes.Application) - { - toolTip = SafeGetProperty(commandAndName.Command, "Definition"); - } - else - { - toolTip = displayName; - } - - results.Add(new CompletionResult(quotedFileName, displayName, CompletionResultType.Command, toolTip)); - } - - private static void PrependSnapInNameForSameCmdletNames(CommandAndName[] cmdlets, bool completingAtStartOfLine, string quote, List results) - { - Diagnostics.Assert(cmdlets != null && cmdlets.Length > 0, - "HasMultiplePSSnapIns must be called with a non-empty collection of PSObject"); - - int i = 0; - bool previousMatched = false; - while (true) - { - CommandAndName commandAndName = cmdlets[i]; - - int lookAhead = i + 1; - if (lookAhead >= cmdlets.Length) - { - AddCommandResult(commandAndName, previousMatched, completingAtStartOfLine, quote, results); - break; + scopeToRestore = null; } - CommandAndName nextCommandAndName = cmdlets[lookAhead]; - - if (string.Equals( - commandAndName.CommandName.ShortName, - nextCommandAndName.CommandName.ShortName, - StringComparison.OrdinalIgnoreCase)) - { - AddCommandResult(commandAndName, true, completingAtStartOfLine, quote, results); - previousMatched = true; - } - else + try { - AddCommandResult(commandAndName, previousMatched, completingAtStartOfLine, quote, results); - previousMatched = false; + var completionAnalysis = new CompletionAnalysis(ast, tokens, positionOfCursor, options); + results = completionAnalysis.GetResults(powershell, out replacementIndex, out replacementLength); } - - i++; - } - } - - #endregion "Handle Command" - - #region "Handle File Names" - - internal static List PSv2GenerateMatchSetOfFiles(PowerShellExecutionHelper helper, string lastWord, bool completingAtStartOfLine, string quote) - { - var results = new List(); - - // lastWord is treated as an PSPath. The match set includes those items that match that - // path, namely, the union of: - // (S1) the sorted set of items matching the last word - // (S2) the sorted set of items matching the last word + * - // If the last word contains no wildcard characters, then S1 is the empty set. S1 is always - // a subset of S2, but we want to present S1 first, then (S2 - S1) next. The idea is that - // if the user typed some wildcard characters, they'd prefer to see those matches before - // all of the rest. - - // Determine if we need to quote the paths we parse - - lastWord ??= string.Empty; - bool isLastWordEmpty = string.IsNullOrEmpty(lastWord); - bool lastCharIsStar = !isLastWordEmpty && lastWord.EndsWith('*'); - bool containsGlobChars = WildcardPattern.ContainsWildcardCharacters(lastWord); - - string wildWord = lastWord + "*"; - bool shouldFullyQualifyPaths = PSv2ShouldFullyQualifyPathsPath(helper, lastWord); - - // NTRAID#Windows Out Of Band Releases-927933-2006/03/13-JeffJon - // Need to detect when the path is a provider-direct path and make sure - // to remove the provider-qualifier when the resolved path is returned. - bool isProviderDirectPath = lastWord.StartsWith(@"\\", StringComparison.Ordinal) || - lastWord.StartsWith("//", StringComparison.Ordinal); - - List s1 = null; - List s2 = null; - - if (containsGlobChars && !isLastWordEmpty) - { - s1 = PSv2FindMatches( - helper, - lastWord, - shouldFullyQualifyPaths); - } - - if (!lastCharIsStar) - { - s2 = PSv2FindMatches( - helper, - wildWord, - shouldFullyQualifyPaths); - } - - IEnumerable combinedMatches = CombineMatchSets(s1, s2); - - if (combinedMatches != null) - { - foreach (var combinedMatch in combinedMatches) + finally { - string combinedMatchPath = WildcardPattern.Escape(combinedMatch.Path); - string combinedMatchConvertedPath = WildcardPattern.Escape(combinedMatch.ConvertedPath); - string completionText = isProviderDirectPath ? combinedMatchConvertedPath : combinedMatchPath; - - completionText = AddQuoteIfNecessary(completionText, quote, completingAtStartOfLine); - - bool? isContainer = SafeGetProperty(combinedMatch.Item, "PSIsContainer"); - string childName = SafeGetProperty(combinedMatch.Item, "PSChildName"); - string toolTip = PowerShellExecutionHelper.SafeToString(combinedMatch.ConvertedPath); - - if (isContainer != null && childName != null && toolTip != null) + if (scopeToRestore != null) { - CompletionResultType resultType = isContainer.Value - ? CompletionResultType.ProviderContainer - : CompletionResultType.ProviderItem; - results.Add(new CompletionResult(completionText, childName, resultType, toolTip)); + context.EngineSessionState.CurrentScope = scopeToRestore; } } } - return results; - } - - private static string AddQuoteIfNecessary(string completionText, string quote, bool completingAtStartOfLine) - { - if (completionText.IndexOfAny(s_charsRequiringQuotedString) != -1) - { - bool needAmpersand = quote.Length == 0 && completingAtStartOfLine; - string quoteInUse = quote.Length == 0 ? "'" : quote; - completionText = quoteInUse == "'" ? completionText.Replace("'", "''") : completionText; - completionText = quoteInUse + completionText + quoteInUse; - completionText = needAmpersand ? "& " + completionText : completionText; - } - else - { - completionText = quote + completionText + quote; - } - - return completionText; - } - - private static IEnumerable CombineMatchSets(List s1, List s2) - { - if (s1 == null || s1.Count < 1) - { - // only s2 contains results; which may be null or empty - return s2; - } - - if (s2 == null || s2.Count < 1) - { - // only s1 contains results - return s1; - } - - // s1 and s2 contain results - Diagnostics.Assert(s1 != null && s1.Count > 0, "s1 should have results"); - Diagnostics.Assert(s2 != null && s2.Count > 0, "if s1 has results, s2 must also"); - Diagnostics.Assert(s1.Count <= s2.Count, "s2 should always be larger than s1"); - - var result = new List(); + var completionResults = results ?? EmptyCompletionResult; - // we need to remove from s2 those items in s1. Since the results from FindMatches will be sorted, - // just copy out the unique elements from s2 and s1. We know that every element of S1 will be in S2, - // so the result set will be S1 + (S2 - S1), which is the same size as S2. - result.AddRange(s1); - for (int i = 0, j = 0; i < s2.Count; ++i) - { - if (j < s1.Count && string.Equals(s2[i].Path, s1[j].Path, StringComparison.CurrentCultureIgnoreCase)) - { - ++j; - continue; - } - - result.Add(s2[i]); - } - -#if DEBUG - Diagnostics.Assert(result.Count == s2.Count, "result should be the same size as s2, see the size comment above"); - for (int i = 0; i < s1.Count; ++i) - { - string path = result[i].Path; - int j = result.FindLastIndex(item => item.Path == path); - Diagnostics.Assert(j == i, "elements of s1 should only come at the start of the results"); - } +#if LEGACYTELEMETRY + // no telemetry here. We don't capture tab completion performance. + sw.Stop(); + TelemetryAPI.ReportTabCompletionTelemetry(sw.ElapsedMilliseconds, completionResults.Count, + completionResults.Count > 0 ? completionResults[0].ResultType : CompletionResultType.Text); #endif - return result; - } - - private static T SafeGetProperty(PSObject psObject, string propertyName) - { - if (psObject == null) - { - return default(T); - } - - PSPropertyInfo property = psObject.Properties[propertyName]; - if (property == null) - { - return default(T); - } - - object propertyValue = property.Value; - if (propertyValue == null) - { - return default(T); - } - - T returnValue; - if (LanguagePrimitives.TryConvertTo(propertyValue, out returnValue)) - { - return returnValue; - } - - return default(T); - } - - private static bool PSv2ShouldFullyQualifyPathsPath(PowerShellExecutionHelper helper, string lastWord) - { - // These are special cases, as they represent cases where the user expects to - // see the full path. - if (lastWord.StartsWith('~') || - lastWord.StartsWith('\\') || - lastWord.StartsWith('/')) - { - return true; - } - - helper.CurrentPowerShell - .AddCommand("Split-Path") - .AddParameter("Path", lastWord) - .AddParameter("IsAbsolute", true); - - bool isAbsolute = helper.ExecuteCommandAndGetResultAsBool(); - return isAbsolute; - } - - private readonly struct PathItemAndConvertedPath - { - internal readonly string Path; - internal readonly PSObject Item; - internal readonly string ConvertedPath; - - internal PathItemAndConvertedPath(string path, PSObject item, string convertedPath) - { - this.Path = path; - this.Item = item; - this.ConvertedPath = convertedPath; - } - } - - private static List PSv2FindMatches(PowerShellExecutionHelper helper, string path, bool shouldFullyQualifyPaths) - { - Diagnostics.Assert(!string.IsNullOrEmpty(path), "path should have a value"); - var result = new List(); - - Exception exceptionThrown; - PowerShell powershell = helper.CurrentPowerShell; - - // It's OK to use script, since tab completion is useless when the remote Win7 machine is in nolanguage mode - if (!shouldFullyQualifyPaths) - { - powershell.AddScript(string.Format( - CultureInfo.InvariantCulture, - "& {{ trap {{ continue }} ; resolve-path {0} -Relative -WarningAction SilentlyContinue | ForEach-Object {{,($_,(get-item $_ -WarningAction SilentlyContinue),(convert-path $_ -WarningAction SilentlyContinue))}} }}", - path)); - } - else - { - powershell.AddScript(string.Format( - CultureInfo.InvariantCulture, - "& {{ trap {{ continue }} ; resolve-path {0} -WarningAction SilentlyContinue | ForEach-Object {{,($_,(get-item $_ -WarningAction SilentlyContinue),(convert-path $_ -WarningAction SilentlyContinue))}} }}", - path)); - } - - Collection paths = helper.ExecuteCurrentPowerShell(out exceptionThrown); - if (paths == null || paths.Count == 0) - { - return null; - } - - foreach (PSObject t in paths) - { - var pathsArray = t.BaseObject as IList; - if (pathsArray != null && pathsArray.Count == 3) - { - object objectPath = pathsArray[0]; - PSObject item = pathsArray[1] as PSObject; - object convertedPath = pathsArray[1]; - - if (objectPath == null || item == null || convertedPath == null) - { - continue; - } - - result.Add(new PathItemAndConvertedPath( - PowerShellExecutionHelper.SafeToString(objectPath), - item, - PowerShellExecutionHelper.SafeToString(convertedPath))); - } - } - - if (result.Count == 0) - { - return null; - } - - result.Sort((PathItemAndConvertedPath x, PathItemAndConvertedPath y) => - { - Diagnostics.Assert(x.Path != null && y.Path != null, "SafeToString always returns a non-null string"); - return string.Compare(x.Path, y.Path, StringComparison.CurrentCultureIgnoreCase); - }); - - return result; - } - - #endregion "Handle File Names" - } - - /// - /// LastWordFinder implements the algorithm we use to search for the last word in a line of input taken from the console. - /// This class exists for legacy purposes only - V3 and forward uses a slightly different interface. - /// - private class LastWordFinder - { - internal static string FindLastWord(string sentence, out int replacementIndexOut, out char closingQuote) - { - return (new LastWordFinder(sentence)).FindLastWord(out replacementIndexOut, out closingQuote); - } - - private LastWordFinder(string sentence) - { - _replacementIndex = 0; - Diagnostics.Assert(sentence != null, "need to provide an instance"); - _sentence = sentence; - } - - /// - /// Locates the last "word" in a string of text. A word is a conguous sequence of characters that are not - /// whitespace, or a contiguous set grouped by single or double quotes. Can be called by at most 1 thread at a time - /// per LastWordFinder instance. - /// - /// - /// Receives the character index (from the front of the string) of the starting point of the located word, or 0 if - /// the word starts at the beginning of the sentence. - /// - /// - /// Receives the quote character that would be needed to end the sentence with a balanced pair of quotes. For - /// instance, if sentence is "foo then " is returned, if sentence if "foo" then nothing is returned, if sentence is - /// 'foo then ' is returned, if sentence is 'foo' then nothing is returned. - /// - /// The last word located, or the empty string if no word could be found. - private string FindLastWord(out int replacementIndexOut, out char closingQuote) - { - bool inSingleQuote = false; - bool inDoubleQuote = false; - - ReplacementIndex = 0; - - for (_sentenceIndex = 0; _sentenceIndex < _sentence.Length; ++_sentenceIndex) - { - Diagnostics.Assert(!(inSingleQuote && inDoubleQuote), - "Can't be in both single and double quotes"); - - char c = _sentence[_sentenceIndex]; - - // there are 3 possibilities: - // 1) a new sequence is starting, - // 2) a sequence is ending, or - // 3) a sequence is due to end on the next matching quote, end-of-sentence, or whitespace - - if (c == '\'') - { - HandleQuote(ref inSingleQuote, ref inDoubleQuote, c); - } - else if (c == '"') - { - HandleQuote(ref inDoubleQuote, ref inSingleQuote, c); - } - else if (c == '`') - { - Consume(c); - if (++_sentenceIndex < _sentence.Length) - { - Consume(_sentence[_sentenceIndex]); - } - } - else if (IsWhitespace(c)) - { - if (_sequenceDueToEnd) - { - // we skipped a quote earlier, now end that sequence - - _sequenceDueToEnd = false; - if (inSingleQuote) - { - inSingleQuote = false; - } - - if (inDoubleQuote) - { - inDoubleQuote = false; - } - - ReplacementIndex = _sentenceIndex + 1; - } - else if (inSingleQuote || inDoubleQuote) - { - // a sequence is started and we're in quotes - - Consume(c); - } - else - { - // no sequence is started, so ignore c - - ReplacementIndex = _sentenceIndex + 1; - } - } - else - { - // a sequence is started and we're in it - - Consume(c); - } - } - - string result = new string(_wordBuffer, 0, _wordBufferIndex); - - closingQuote = inSingleQuote ? '\'' : inDoubleQuote ? '"' : '\0'; - replacementIndexOut = ReplacementIndex; - return result; + return new CommandCompletion( + new Collection(completionResults), + -1, + replacementIndex, + replacementLength); } - - private void HandleQuote(ref bool inQuote, ref bool inOppositeQuote, char c) - { - if (inOppositeQuote) - { - // a sequence is started, and we're in it. - Consume(c); - return; - } - - if (inQuote) - { - if (_sequenceDueToEnd) - { - // I've ended a sequence and am starting another; don't consume c, update replacementIndex - ReplacementIndex = _sentenceIndex + 1; - } - - _sequenceDueToEnd = !_sequenceDueToEnd; - } - else - { - // I'm starting a sequence; don't consume c, update replacementIndex - inQuote = true; - ReplacementIndex = _sentenceIndex; - } - } - - private void Consume(char c) - { - Diagnostics.Assert(_wordBuffer != null, "wordBuffer is not initialized"); - Diagnostics.Assert(_wordBufferIndex < _wordBuffer.Length, "wordBufferIndex is out of range"); - - _wordBuffer[_wordBufferIndex++] = c; - } - - private int ReplacementIndex - { - get - { - return _replacementIndex; - } - - set - { - Diagnostics.Assert(value >= 0 && value < _sentence.Length + 1, "value out of range"); - - // when we set the replacement index, that means we're also resetting our word buffer. we know wordBuffer - // will never be longer than sentence. - - _wordBuffer = new char[_sentence.Length]; - _wordBufferIndex = 0; - _replacementIndex = value; - } - } - - private static bool IsWhitespace(char c) - { - return (c == ' ') || (c == '\x0009'); - } - - private readonly string _sentence; - private char[] _wordBuffer; - private int _wordBufferIndex; - private int _replacementIndex; - private int _sentenceIndex; - private bool _sequenceDueToEnd; } #endregion private methods diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs index f8961956a31..42a60ab7e0e 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs @@ -9,6 +9,8 @@ using System.Reflection; using System.Text; using System.Text.RegularExpressions; +using System.Management.Automation.Subsystem; +using System.Management.Automation.Subsystem.DSC; namespace System.Management.Automation { @@ -153,6 +155,19 @@ internal static AstAnalysisContext ExtractAstContext(Ast inputAst, Token[] input ast => IsCursorWithinOrJustAfterExtent(positionForAstSearch, ast.Extent), searchNestedScriptBlocks: true).ToList(); + if (relatedAsts.Count == 0) + { + relatedAsts.Add(inputAst); + } + + // If the last ast is an unnamed block that starts with "param" the cursor is inside a param block. + // To avoid adding special handling to all the completers that look at the last ast, we remove it here because it's not useful for completion. + if (relatedAsts[^1].Extent.Text.StartsWith("param", StringComparison.OrdinalIgnoreCase) + && relatedAsts[^1] is NamedBlockAst namedBlock && namedBlock.Unnamed) + { + relatedAsts.RemoveAt(relatedAsts.Count - 1); + } + Diagnostics.Assert(tokenAtCursor == null || tokenBeforeCursor == null, "Only one of these tokens can be non-null"); return new AstAnalysisContext(tokenAtCursor, tokenBeforeCursor, relatedAsts, replacementIndex); @@ -173,10 +188,7 @@ private CompletionContext InitializeCompletionContext(TypeInferenceContext typeI { var astContext = ExtractAstContext(_ast, _tokens, _cursorPosition); - if (typeInferenceContext.CurrentTypeDefinitionAst == null) - { - typeInferenceContext.CurrentTypeDefinitionAst = Ast.GetAncestorTypeDefinitionAst(astContext.RelatedAsts.Last()); - } + typeInferenceContext.CurrentTypeDefinitionAst ??= Ast.GetAncestorTypeDefinitionAst(astContext.RelatedAsts.Last()); ExecutionContext executionContext = typeInferenceContext.ExecutionContext; @@ -375,7 +387,7 @@ internal List GetResults(PowerShell powerShell, out int replac completionContext.ExecutionContext.LanguageMode = PSLanguageMode.ConstrainedLanguage; } - return GetResultHelper(completionContext, out replacementIndex, out replacementLength, false); + return GetResultHelper(completionContext, out replacementIndex, out replacementLength); } finally { @@ -386,7 +398,7 @@ internal List GetResults(PowerShell powerShell, out int replac } } - internal List GetResultHelper(CompletionContext completionContext, out int replacementIndex, out int replacementLength, bool isQuotedString) + internal List GetResultHelper(CompletionContext completionContext, out int replacementIndex, out int replacementLength) { replacementIndex = -1; replacementLength = -1; @@ -414,14 +426,20 @@ internal List GetResultHelper(CompletionContext completionCont case TokenKind.Generic: case TokenKind.MinusMinus: // for native commands '--' case TokenKind.Identifier: - result = GetResultForIdentifier(completionContext, ref replacementIndex, ref replacementLength, isQuotedString); + if (!tokenAtCursor.TokenFlags.HasFlag(TokenFlags.TypeName)) + { + result = CompleteUsingKeywords(completionContext.CursorPosition.Offset, _tokens, ref replacementIndex, ref replacementLength); + if (result is not null) + { + return result; + } + + result = GetResultForIdentifier(completionContext, ref replacementIndex, ref replacementLength); + } + break; case TokenKind.Parameter: - // When it's the content of a quoted string, we only handle variable/member completion - if (isQuotedString) - break; - completionContext.WordToComplete = tokenAtCursor.Text; var cmdAst = lastAst.Parent as CommandAst; if (lastAst is StringConstantExpressionAst && cmdAst != null && cmdAst.CommandElements.Count == 1) @@ -464,16 +482,13 @@ internal List GetResultHelper(CompletionContext completionCont case TokenKind.QuestionDot: replacementIndex += tokenAtCursor.Text.Length; replacementLength = 0; - result = CompletionCompleters.CompleteMember(completionContext, @static: tokenAtCursor.Kind == TokenKind.ColonColon); + result = CompletionCompleters.CompleteMember(completionContext, @static: tokenAtCursor.Kind == TokenKind.ColonColon, ref replacementLength); + break; case TokenKind.Comment: - // When it's the content of a quoted string, we only handle variable/member completion - if (isQuotedString) - break; - completionContext.WordToComplete = tokenAtCursor.Text; - result = CompletionCompleters.CompleteComment(completionContext); + result = CompletionCompleters.CompleteComment(completionContext, ref replacementIndex, ref replacementLength); break; case TokenKind.StringExpandable: @@ -488,8 +503,23 @@ internal List GetResultHelper(CompletionContext completionCont return completions; } } + else if (lastAst.Parent is BinaryExpressionAst binaryExpression) + { + completionContext.WordToComplete = (tokenAtCursor as StringToken).Value; + result = CompletionCompleters.CompleteComparisonOperatorValues(completionContext, binaryExpression.Left); + if (result.Count > 0) + { + return result; + } + } + else if (lastAst.Parent is IndexExpressionAst indexExpressionAst) + { + // Handles quoted string inside index expression like: $PSVersionTable[""] + completionContext.WordToComplete = (tokenAtCursor as StringToken).Value; + return CompletionCompleters.CompleteIndexExpression(completionContext, indexExpressionAst.Target); + } - result = GetResultForString(completionContext, ref replacementIndex, ref replacementLength, isQuotedString); + result = GetResultForString(completionContext, ref replacementIndex, ref replacementLength); break; case TokenKind.RBracket: @@ -517,9 +547,14 @@ internal List GetResultHelper(CompletionContext completionCont break; case TokenKind.Comma: - // Handle array elements such as dir .\cd, || dir -Path: .\cd, - if (lastAst is ErrorExpressionAst && - (lastAst.Parent is CommandAst || lastAst.Parent is CommandParameterAst)) + // Handle array elements such as the followings: + // - `dir .\cd,` + // - `dir -Path: .\cd,` + // - `dir .\abc.txt, -File` + // - `dir -Path .\abc.txt, -File` + // - `dir -Path: .\abc.txt, -File` + if (lastAst is ErrorExpressionAst or ArrayLiteralAst && + lastAst.Parent is CommandAst or CommandParameterAst) { replacementIndex += replacementLength; replacementLength = 0; @@ -544,8 +579,7 @@ internal List GetResultHelper(CompletionContext completionCont // { // DependsOn=@('[user]x',|) // - bool unused; - result = GetResultForEnumPropertyValueOfDSCResource(completionContext, string.Empty, ref replacementIndex, ref replacementLength, out unused); + result = GetResultForEnumPropertyValueOfDSCResource(completionContext, string.Empty, ref replacementIndex, ref replacementLength, out _); } break; @@ -659,6 +693,19 @@ internal List 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 =' @@ -673,13 +720,82 @@ internal List GetResultHelper(CompletionContext completionCont // DependsOn=@(|) // DependsOn=(| // - bool unused; - result = GetResultForEnumPropertyValueOfDSCResource(completionContext, string.Empty, ref replacementIndex, ref replacementLength, out unused); + result = GetResultForEnumPropertyValueOfDSCResource(completionContext, string.Empty, ref replacementIndex, ref replacementLength, out _); } break; } + + case TokenKind.Format: + case TokenKind.Not: + case TokenKind.Bnot: + case TokenKind.And: + case TokenKind.Or: + case TokenKind.Xor: + case TokenKind.Band: + case TokenKind.Bor: + case TokenKind.Bxor: + case TokenKind.Join: + case TokenKind.Ieq: + case TokenKind.Ine: + case TokenKind.Ige: + case TokenKind.Igt: + case TokenKind.Ilt: + case TokenKind.Ile: + case TokenKind.Ilike: + case TokenKind.Inotlike: + case TokenKind.Imatch: + case TokenKind.Inotmatch: + case TokenKind.Ireplace: + case TokenKind.Icontains: + case TokenKind.Inotcontains: + case TokenKind.Iin: + case TokenKind.Inotin: + case TokenKind.Isplit: + case TokenKind.Ceq: + case TokenKind.Cne: + case TokenKind.Cge: + case TokenKind.Cgt: + case TokenKind.Clt: + case TokenKind.Cle: + case TokenKind.Clike: + case TokenKind.Cnotlike: + case TokenKind.Cmatch: + case TokenKind.Cnotmatch: + case TokenKind.Creplace: + case TokenKind.Ccontains: + case TokenKind.Cnotcontains: + case TokenKind.Cin: + case TokenKind.Cnotin: + case TokenKind.Csplit: + case TokenKind.Is: + case TokenKind.IsNot: + case TokenKind.As: + case TokenKind.Shl: + case TokenKind.Shr: + result = CompletionCompleters.CompleteOperator(tokenAtCursor.Text); + break; + + case TokenKind.LBracket: + if (lastAst.Parent is IndexExpressionAst indexExpression) + { + // Handles index expression with cursor right after lbracket like: $PSVersionTable[] + completionContext.WordToComplete = string.Empty; + result = CompletionCompleters.CompleteIndexExpression(completionContext, indexExpression.Target); + if (result.Count > 0) + { + replacementIndex++; + replacementLength--; + } + } + break; default: + result = CompleteUsingKeywords(completionContext.CursorPosition.Offset, _tokens, ref replacementIndex, ref replacementLength); + if (result is not null) + { + return result; + } + if ((tokenAtCursor.TokenFlags & TokenFlags.Keyword) != 0) { completionContext.WordToComplete = tokenAtCursor.Text; @@ -728,8 +844,7 @@ internal List GetResultHelper(CompletionContext completionCont bool skipAutoCompleteForCommandCall = isCursorLineEmpty && !isLineContinuationBeforeCursor; bool lastAstIsExpressionAst = lastAst is ExpressionAst; - if (!isQuotedString && - !skipAutoCompleteForCommandCall && + if (!skipAutoCompleteForCommandCall && (lastAst is CommandParameterAst || lastAst is CommandAst || (lastAstIsExpressionAst && lastAst.Parent is CommandAst) || (lastAstIsExpressionAst && lastAst.Parent is CommandParameterAst) || @@ -768,7 +883,7 @@ internal List GetResultHelper(CompletionContext completionCont replacementLength = completionContext.ReplacementLength; } } - else if (!isQuotedString) + else { // // Handle completion of empty line within configuration statement @@ -803,6 +918,75 @@ internal List GetResultHelper(CompletionContext completionCont result = GetResultForIdentifierInConfiguration(completionContext, configAst, keywordAst, out matched); } } + + // Handles following scenario where user is tab completing a member on an empty line: + // "Hello". + // + if ((result is null || result.Count == 0) && tokenBeforeCursor is not null) + { + switch (completionContext.TokenBeforeCursor.Kind) + { + + case TokenKind.Dot: + case TokenKind.ColonColon: + case TokenKind.QuestionDot: + replacementIndex = cursor.Offset; + replacementLength = 0; + result = CompletionCompleters.CompleteMember(completionContext, @static: completionContext.TokenBeforeCursor.Kind == TokenKind.ColonColon, ref replacementLength); + break; + + case TokenKind.LParen: + case TokenKind.Comma: + if (lastAst is AttributeAst) + { + 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: + case TokenKind.Ceq: + case TokenKind.Ine: + case TokenKind.Cne: + case TokenKind.Ilike: + case TokenKind.Clike: + case TokenKind.Inotlike: + case TokenKind.Cnotlike: + case TokenKind.Imatch: + case TokenKind.Cmatch: + case TokenKind.Inotmatch: + case TokenKind.Cnotmatch: + if (lastAst is BinaryExpressionAst binaryExpression) + { + completionContext.WordToComplete = string.Empty; + result = CompletionCompleters.CompleteComparisonOperatorValues(completionContext, binaryExpression.Left); + } + break; + + case TokenKind.LBracket: + if (lastAst.Parent is IndexExpressionAst indexExpression) + { + // Handles index expression where cursor is on a new line after the lbracket like: $PSVersionTable[\n] + completionContext.WordToComplete = string.Empty; + result = CompletionCompleters.CompleteIndexExpression(completionContext, indexExpression.Target); + } + break; + + default: + break; + } + } } else if (completionContext.TokenAtCursor == null) { @@ -836,7 +1020,7 @@ internal List GetResultHelper(CompletionContext completionCont break; } } - + if (lastAst is AttributeAst) { completionContext.ReplacementLength = replacementLength = 0; @@ -844,10 +1028,80 @@ internal List GetResultHelper(CompletionContext completionCont break; } - bool unused; - result = GetResultForEnumPropertyValueOfDSCResource(completionContext, string.Empty, ref replacementIndex, ref replacementLength, out unused); + 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; } + + case TokenKind.Break: + case TokenKind.Continue: + { + if ((lastAst is BreakStatementAst breakStatement && breakStatement.Label is null) + || (lastAst is ContinueStatementAst continueStatement && continueStatement.Label is null)) + { + result = CompleteLoopLabel(completionContext); + } + break; + } + + case TokenKind.Using: + return CompleteUsingKeywords(completionContext.CursorPosition.Offset, _tokens, ref replacementIndex, ref replacementLength); + + case TokenKind.Dot: + case TokenKind.ColonColon: + case TokenKind.QuestionDot: + // Handles following scenario with whitespace after member access token: "Hello". + replacementIndex = cursor.Offset; + replacementLength = 0; + result = CompletionCompleters.CompleteMember(completionContext, @static: tokenBeforeCursor.Kind == TokenKind.ColonColon, ref replacementLength); + if (result is not null && result.Count > 0) + { + return result; + } + break; + + case TokenKind.Ieq: + case TokenKind.Ceq: + case TokenKind.Ine: + case TokenKind.Cne: + case TokenKind.Ilike: + case TokenKind.Clike: + case TokenKind.Inotlike: + case TokenKind.Cnotlike: + case TokenKind.Imatch: + case TokenKind.Cmatch: + case TokenKind.Inotmatch: + case TokenKind.Cnotmatch: + if (lastAst is BinaryExpressionAst binaryExpression) + { + completionContext.WordToComplete = string.Empty; + result = CompletionCompleters.CompleteComparisonOperatorValues(completionContext, binaryExpression.Left); + } + break; + + case TokenKind.LBracket: + if (lastAst.Parent is IndexExpressionAst indexExpression) + { + // Handles index expression with whitespace between lbracket and cursor like: $PSVersionTable[ ] + completionContext.WordToComplete = string.Empty; + result = CompletionCompleters.CompleteIndexExpression(completionContext, indexExpression.Target); + } + break; + default: break; } @@ -902,6 +1156,11 @@ internal List GetResultHelper(CompletionContext completionCont } } + if (typeNameToComplete is null && tokenAtCursor?.TokenFlags.HasFlag(TokenFlags.TypeName) == true) + { + typeNameToComplete = new TypeName(tokenAtCursor.Extent, tokenAtCursor.Text); + } + if (typeNameToComplete != null) { // See if the typename to complete really is within the typename, and if so, which one, in the case of generics. @@ -909,13 +1168,19 @@ internal List GetResultHelper(CompletionContext completionCont replacementIndex = typeNameToComplete.Extent.StartOffset; replacementLength = typeNameToComplete.Extent.EndOffset - replacementIndex; completionContext.WordToComplete = typeNameToComplete.FullName; - result = CompletionCompleters.CompleteType(completionContext); + return CompletionCompleters.CompleteType(completionContext); } } if (result == null || result.Count == 0) { result = GetResultForHashtable(completionContext); + // Handles the following scenario: [ipaddress]@{Address=""; } + if (result?.Count > 0) + { + replacementIndex = completionContext.CursorPosition.Offset; + replacementLength = 0; + } } if (result == null || result.Count == 0) @@ -938,59 +1203,56 @@ internal List GetResultHelper(CompletionContext completionCont // Helper method to auto complete hashtable key private static List GetResultForHashtable(CompletionContext completionContext) { - var lastAst = completionContext.RelatedAsts.Last(); - HashtableAst tempHashtableAst = null; - IScriptPosition cursor = completionContext.CursorPosition; - var hashTableAst = lastAst as HashtableAst; - if (hashTableAst != null) + Ast lastRelatedAst = null; + var cursorPosition = completionContext.CursorPosition; + + // Enumeration is used over the LastAst pattern because empty lines following a key-value pair will set LastAst to the value. + // Example: + // @{ + // Key1="Value1" + // + // } + // In this case the last 3 Asts will be StringConstantExpression, CommandExpression, and Pipeline instead of the expected Hashtable + for (int i = completionContext.RelatedAsts.Count - 1; i >= 0; i--) + { + Ast ast = completionContext.RelatedAsts[i]; + if (cursorPosition.Offset >= ast.Extent.StartOffset && cursorPosition.Offset <= ast.Extent.EndOffset) + { + lastRelatedAst = ast; + break; + } + } + + if (lastRelatedAst is HashtableAst hashtableAst) { - // Check if the cursor within the hashtable - if (cursor.Offset < hashTableAst.Extent.EndOffset) + // Cursor is just after the hashtable: @{} + if (completionContext.TokenAtCursor is not null && completionContext.TokenAtCursor.Kind == TokenKind.RCurly) { - tempHashtableAst = hashTableAst; + return null; } - else if (cursor.Offset == hashTableAst.Extent.EndOffset) + + bool cursorIsWithinOrOnSameLineAsKeypair = false; + foreach (var pair in hashtableAst.KeyValuePairs) { - // Exclude the scenario that cursor at the end of hashtable, i.e. after '}' - if (completionContext.TokenAtCursor == null || - completionContext.TokenAtCursor.Kind != TokenKind.RCurly) + if (cursorPosition.Offset >= pair.Item1.Extent.StartOffset + && (cursorPosition.Offset <= pair.Item2.Extent.EndOffset || cursorPosition.LineNumber == pair.Item2.Extent.EndLineNumber)) { - tempHashtableAst = hashTableAst; + cursorIsWithinOrOnSameLineAsKeypair = true; + break; } } - } - else - { - // Handle property completion on a blank line for DynamicKeyword statement - Ast lastChildofHashtableAst; - hashTableAst = Ast.GetAncestorHashtableAst(lastAst, out lastChildofHashtableAst); - // Check if the hashtable within a DynamicKeyword statement - if (hashTableAst != null) + if (cursorIsWithinOrOnSameLineAsKeypair) { - var keywordAst = Ast.GetAncestorAst(hashTableAst); - if (keywordAst != null) + var tokenBeforeOrAtCursor = completionContext.TokenBeforeCursor ?? completionContext.TokenAtCursor; + if (tokenBeforeOrAtCursor.Kind != TokenKind.Semi) { - // Handle only empty line - if (string.IsNullOrWhiteSpace(cursor.Line)) - { - // Check if the cursor outside of last child of hashtable and within the hashtable - if (cursor.Offset > lastChildofHashtableAst.Extent.EndOffset && - cursor.Offset <= hashTableAst.Extent.EndOffset) - { - tempHashtableAst = hashTableAst; - } - } + return null; } } - } - - hashTableAst = tempHashtableAst; - if (hashTableAst != null) - { completionContext.ReplacementIndex = completionContext.CursorPosition.Offset; completionContext.ReplacementLength = 0; - return CompletionCompleters.CompleteHashtableKey(completionContext, hashTableAst); + return CompletionCompleters.CompleteHashtableKey(completionContext, hashtableAst); } return null; @@ -1054,7 +1316,7 @@ private static string GetFirstLineSubString(string stringToComplete, out bool ha hasNewLine = false; if (!string.IsNullOrEmpty(stringToComplete)) { - var index = stringToComplete.IndexOfAny(Utils.Separators.CrLf); + var index = stringToComplete.AsSpan().IndexOfAny('\r', '\n'); if (index >= 0) { stringToComplete = stringToComplete.Substring(0, index); @@ -1195,6 +1457,121 @@ private static bool TryGetTypeConstraintOnVariable( return typeConstraint != null || setConstraint != null; } + private static List CompletePropertyAssignment(MemberExpressionAst memberExpression, CompletionContext context) + { + if (SafeExprEvaluator.TrySafeEval(memberExpression, context.ExecutionContext, out var evalValue)) + { + if (evalValue is not null) + { + Type type = evalValue.GetType(); + if (type.IsEnum) + { + return GetResultForEnum(type, context); + } + + return null; + } + } + + _ = TryGetInferredCompletionsForAssignment(memberExpression, context, out List result); + return result; + } + + private static bool TryGetInferredCompletionsForAssignment(Ast expression, CompletionContext context, out List result) + { + result = null; + IList 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 false; + } + + inferredTypes = new PSTypeName[] { typeConstraint }; + } + else + { + return false; + } + + if (inferredTypes.Count == 0) + { + return false; + } + + var values = new SortedSet(); + 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(); + 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( CompletionContext completionContext, AssignmentStatementAst assignmentAst, @@ -1226,6 +1603,12 @@ bool TryGetResultForSet(Type typeConstraint, ValidateSetAttribute setConstraint, return false; } + if (assignmentAst.Left is MemberExpressionAst member) + { + completions = CompletePropertyAssignment(member, completionContext); + return completions is not null; + } + completions = null; // Try to get the variable from the assignment, plus any type constraint on it @@ -1255,7 +1638,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 @@ -1441,7 +1824,7 @@ private static List GetResultForEnumPropertyValueOfDSCResource Diagnostics.Assert(isCursorInString || (!hasNewLine), "hasNoQuote and hasNewLine cannot be true at the same time"); if (property.ValueMap != null && property.ValueMap.Count > 0) { - IEnumerable orderedValues = property.ValueMap.Keys.OrderBy(x => x).Where(v => !existingValues.Contains(v, StringComparer.OrdinalIgnoreCase)); + IEnumerable orderedValues = property.ValueMap.Keys.Order().Where(v => !existingValues.Contains(v, StringComparer.OrdinalIgnoreCase)); var matchedResults = orderedValues.Where(v => wildcardPattern.IsMatch(v)); if (matchedResults == null || !matchedResults.Any()) { @@ -1523,111 +1906,50 @@ private static List GetResultForEnumPropertyValueOfDSCResource return result; } - private List GetResultForString(CompletionContext completionContext, ref int replacementIndex, ref int replacementLength, bool isQuotedString) + private static List GetResultForString(CompletionContext completionContext, ref int replacementIndex, ref int replacementLength) { - // When it's the content of a quoted string, we only handle variable/member completion - if (isQuotedString) { return null; } - - var tokenAtCursor = completionContext.TokenAtCursor; var lastAst = completionContext.RelatedAsts.Last(); - - List result = null; var expandableString = lastAst as ExpandableStringExpressionAst; var constantString = lastAst as StringConstantExpressionAst; if (constantString == null && expandableString == null) { return null; } string strValue = constantString != null ? constantString.Value : expandableString.Value; - StringConstantType strType = constantString != null ? constantString.StringConstantType : expandableString.StringConstantType; - string subInput = null; bool shouldContinue; - result = GetResultForEnumPropertyValueOfDSCResource(completionContext, strValue, ref replacementIndex, ref replacementLength, out shouldContinue); + List result = GetResultForEnumPropertyValueOfDSCResource(completionContext, strValue, ref replacementIndex, ref replacementLength, out shouldContinue); if (!shouldContinue || (result != null && result.Count > 0)) { return result; } - if (strType == StringConstantType.DoubleQuoted) - { - var match = Regex.Match(strValue, @"(\$[\w\d]+\.[\w\d\*]*)$"); - if (match.Success) - { - subInput = match.Groups[1].Value; - } - else if ((match = Regex.Match(strValue, @"(\[[\w\d\.]+\]::[\w\d\*]*)$")).Success) - { - subInput = match.Groups[1].Value; - } - } + var commandElementAst = lastAst as CommandElementAst; + string wordToComplete = + CompletionCompleters.ConcatenateStringPathArguments(commandElementAst, string.Empty, completionContext); - // Handle variable/member completion - if (subInput != null) + if (wordToComplete != null) { - int stringStartIndex = tokenAtCursor.Extent.StartScriptPosition.Offset; - int cursorIndexInString = _cursorPosition.Offset - stringStartIndex - 1; - if (cursorIndexInString >= strValue.Length) - cursorIndexInString = strValue.Length; + completionContext.WordToComplete = wordToComplete; - var analysis = new CompletionAnalysis(_ast, _tokens, _cursorPosition, _options); - var subContext = analysis.CreateCompletionContext(completionContext.TypeInferenceContext); - - var subResult = analysis.GetResultHelper(subContext, out int subReplaceIndex, out _, true); - - if (subResult != null && subResult.Count > 0) + // Handle scenarios like this: cd 'c:\windows\win' + if (lastAst.Parent is CommandAst || lastAst.Parent is CommandParameterAst) { - result = new List(); - replacementIndex = stringStartIndex + 1 + (cursorIndexInString - subInput.Length); - replacementLength = subInput.Length; - ReadOnlySpan prefix = subInput.AsSpan(0, subReplaceIndex); - - foreach (CompletionResult entry in subResult) - { - string completionText = string.Concat(prefix, entry.CompletionText.AsSpan()); - if (entry.ResultType == CompletionResultType.Property) - { - completionText = TokenKind.DollarParen.Text() + completionText + TokenKind.RParen.Text(); - } - else if (entry.ResultType == CompletionResultType.Method) - { - completionText = TokenKind.DollarParen.Text() + completionText; - } - - completionText += "\""; - result.Add(new CompletionResult(completionText, entry.ListItemText, entry.ResultType, entry.ToolTip)); - } + result = CompletionCompleters.CompleteCommandArgument(completionContext); + replacementIndex = completionContext.ReplacementIndex; + replacementLength = completionContext.ReplacementLength; } - } - else - { - var commandElementAst = lastAst as CommandElementAst; - string wordToComplete = - CompletionCompleters.ConcatenateStringPathArguments(commandElementAst, string.Empty, completionContext); - - if (wordToComplete != null) + // Handle scenarios like this: "c:\wind". Treat the StringLiteral/StringExpandable as path/command + else { - completionContext.WordToComplete = wordToComplete; + // Handle path/commandname completion for quoted string + result = new List(CompletionCompleters.CompleteFilename(completionContext)); - // Handle scenarios like this: cd 'c:\windows\win' - if (lastAst.Parent is CommandAst || lastAst.Parent is CommandParameterAst) - { - result = CompletionCompleters.CompleteCommandArgument(completionContext); - replacementIndex = completionContext.ReplacementIndex; - replacementLength = completionContext.ReplacementLength; - } - // Handle scenarios like this: "c:\wind". Treat the StringLiteral/StringExpandable as path/command - else + // Try command name completion only if the text contains '-' + if (wordToComplete.Contains('-')) { - // Handle path/commandname completion for quoted string - result = new List(CompletionCompleters.CompleteFilename(completionContext)); - - // Try command name completion only if the text contains '-' - if (wordToComplete.Contains('-')) + var commandNameResult = CompletionCompleters.CompleteCommand(completionContext); + if (commandNameResult != null && commandNameResult.Count > 0) { - var commandNameResult = CompletionCompleters.CompleteCommand(completionContext); - if (commandNameResult != null && commandNameResult.Count > 0) - { - result.AddRange(commandNameResult); - } + result.AddRange(commandNameResult); } } } @@ -1721,15 +2043,19 @@ private static List GetResultForIdentifierInConfiguration( foreach (var keyword in matchedResults) { - string usageString = Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache.NewApiIsUsed - ? Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache.GetDSCResourceUsageString(keyword) - : Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache.GetDSCResourceUsageString(keyword); - - if (results == null) + string usageString = string.Empty; + ICrossPlatformDsc dscSubsystem = SubsystemManager.GetSubsystem(); + if (dscSubsystem != null) + { + usageString = dscSubsystem.GetDSCResourceUsageString(keyword); + } + else { - results = new List(); + usageString = Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache.GetDSCResourceUsageString(keyword); } + results ??= new List(); + results.Add(new CompletionResult( keyword.Keyword, keyword.Keyword, @@ -1741,15 +2067,20 @@ private static List GetResultForIdentifierInConfiguration( return results; } - private List GetResultForIdentifier(CompletionContext completionContext, ref int replacementIndex, ref int replacementLength, bool isQuotedString) + private static List GetResultForIdentifier(CompletionContext completionContext, ref int replacementIndex, ref int replacementLength) { + List result = null; var tokenAtCursor = completionContext.TokenAtCursor; var lastAst = completionContext.RelatedAsts.Last(); - List result = null; var tokenAtCursorText = tokenAtCursor.Text; completionContext.WordToComplete = tokenAtCursorText; + if (lastAst.Parent is BreakStatementAst || lastAst.Parent is ContinueStatementAst) + { + return CompleteLoopLabel(completionContext); + } + var strConst = lastAst as StringConstantExpressionAst; if (strConst != null) { @@ -1769,7 +2100,12 @@ private List GetResultForIdentifier(CompletionContext completi switch (usingState.UsingStatementKind) { case UsingStatementKind.Assembly: - break; + HashSet assemblyExtensions = new(StringComparer.OrdinalIgnoreCase) + { + StringLiterals.PowerShellILAssemblyExtension + }; + return CompletionCompleters.CompleteFilename(completionContext, containerOnly: false, assemblyExtensions).ToList(); + case UsingStatementKind.Command: break; case UsingStatementKind.Module: @@ -1805,76 +2141,101 @@ private List GetResultForIdentifier(CompletionContext completi } } - result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); - if (result != null) return result; + if (completionContext.TokenAtCursor.TokenFlags == TokenFlags.MemberName) + { + 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; + } + } if ((tokenAtCursor.TokenFlags & TokenFlags.CommandName) != 0) { // Handle completion for a path with variable, such as: $PSHOME\ty if (completionContext.RelatedAsts.Count > 0 && completionContext.RelatedAsts[0] is ScriptBlockAst) { - Ast cursorAst = null; - var cursorPosition = (InternalScriptPosition)_cursorPosition; - int offsetBeforeCmdName = cursorPosition.Offset - tokenAtCursorText.Length; - if (offsetBeforeCmdName >= 0) - { - var cursorBeforeCmdName = cursorPosition.CloneWithNewOffset(offsetBeforeCmdName); - var scriptBlockAst = (ScriptBlockAst)completionContext.RelatedAsts[0]; - cursorAst = GetLastAstAtCursor(scriptBlockAst, cursorBeforeCmdName); - } + Ast cursorAst = completionContext.RelatedAsts[0].FindAll( + ast => ast.Extent.EndOffset <= tokenAtCursor.Extent.StartOffset + && ast.Extent is not EmptyScriptExtent, + searchNestedScriptBlocks: true).LastOrDefault(); - if (cursorAst != null && - cursorAst.Extent.EndLineNumber == tokenAtCursor.Extent.StartLineNumber && - cursorAst.Extent.EndColumnNumber == tokenAtCursor.Extent.StartColumnNumber) + if (cursorAst is not null) { - if (tokenAtCursorText.IndexOfAny(Utils.Separators.Directory) == 0) + if (cursorAst.Extent.EndOffset == tokenAtCursor.Extent.StartOffset) { - string wordToComplete = - CompletionCompleters.ConcatenateStringPathArguments(cursorAst as CommandElementAst, tokenAtCursorText, completionContext); - if (wordToComplete != null) + if (tokenAtCursorText.AsSpan().IndexOfAny('\\', '/') == 0) { - completionContext.WordToComplete = wordToComplete; - result = new List(CompletionCompleters.CompleteFilename(completionContext)); - if (result.Count > 0) + string wordToComplete = + CompletionCompleters.ConcatenateStringPathArguments(cursorAst as CommandElementAst, tokenAtCursorText, completionContext); + if (wordToComplete != null) + { + completionContext.WordToComplete = wordToComplete; + result = new List(CompletionCompleters.CompleteFilename(completionContext)); + if (result.Count > 0) + { + replacementIndex = cursorAst.Extent.StartScriptPosition.Offset; + replacementLength += cursorAst.Extent.Text.Length; + } + + return result; + } + else { + var variableAst = cursorAst as VariableExpressionAst; + string fullPath = variableAst != null + ? CompletionCompleters.CombineVariableWithPartialPath( + variableAst: variableAst, + extraText: tokenAtCursorText, + executionContext: completionContext.ExecutionContext) + : null; + + if (fullPath == null) { return result; } + + // Continue trying the filename/commandname completion for scenarios like this: $aa\d + completionContext.WordToComplete = fullPath; replacementIndex = cursorAst.Extent.StartScriptPosition.Offset; replacementLength += cursorAst.Extent.Text.Length; - } - return result; + completionContext.ReplacementIndex = replacementIndex; + completionContext.ReplacementLength = replacementLength; + } } - else + // Continue trying the filename/commandname completion for scenarios like this: $aa[get- + else if (cursorAst is not ErrorExpressionAst || cursorAst.Parent is not IndexExpressionAst) { - var variableAst = cursorAst as VariableExpressionAst; - string fullPath = variableAst != null - ? CompletionCompleters.CombineVariableWithPartialPath( - variableAst: variableAst, - extraText: tokenAtCursorText, - executionContext: completionContext.ExecutionContext) - : null; - - if (fullPath == null) { return result; } - - // Continue trying the filename/commandname completion for scenarios like this: $aa\d - completionContext.WordToComplete = fullPath; - replacementIndex = cursorAst.Extent.StartScriptPosition.Offset; - replacementLength += cursorAst.Extent.Text.Length; - - completionContext.ReplacementIndex = replacementIndex; - completionContext.ReplacementLength = replacementLength; + return result; } } - // Continue trying the filename/commandname completion for scenarios like this: $aa[get- - else if (cursorAst is not ErrorExpressionAst || cursorAst.Parent is not IndexExpressionAst) + + if (cursorAst.Parent is IndexExpressionAst indexExpression && indexExpression.Index is ErrorExpressionAst) { - return result; + if (completionContext.WordToComplete.EndsWith(']')) + { + completionContext.WordToComplete = completionContext.WordToComplete.Remove(completionContext.WordToComplete.Length - 1); + } + + // Handles index expression with unquoted word like: $PSVersionTable[psver] + return CompletionCompleters.CompleteIndexExpression(completionContext, indexExpression.Target); } } } - // When it's the content of a quoted string, we only handle variable/member completion - if (isQuotedString) { return result; } - // Handle the StringExpandableToken; var strToken = tokenAtCursor as StringExpandableToken; if (strToken != null && strToken.NestedTokens != null && strConst != null) @@ -1944,8 +2305,6 @@ private List GetResultForIdentifier(CompletionContext completi // When it's the content of a quoted string, we only handle variable/member completion if (isSingleDash) { - if (isQuotedString) { return result; } - var res = CompletionCompleters.CompleteCommandParameter(completionContext); if (res.Count != 0) { @@ -1957,8 +2316,24 @@ private List GetResultForIdentifier(CompletionContext completi } TokenKind memberOperator = TokenKind.Unknown; - bool isMemberCompletion = (lastAst.Parent is MemberExpressionAst); - bool isStatic = isMemberCompletion && ((MemberExpressionAst)lastAst.Parent).Static; + bool isMemberCompletion = lastAst.Parent is MemberExpressionAst; + bool isStatic = false; + if (isMemberCompletion) + { + var currentExpression = (MemberExpressionAst)lastAst.Parent; + // Handles following scenario with an incomplete member access token at the end of the statement: + // [System.IO.FileInfo]::new().Directory.BaseName.Length. + // Traverses up the expressions until it finds one under at the cursor + while (currentExpression.Extent.EndOffset >= completionContext.CursorPosition.Offset + && currentExpression.Expression is MemberExpressionAst memberExpression + && memberExpression.Member.Extent.EndOffset >= completionContext.CursorPosition.Offset) + { + currentExpression = memberExpression; + } + + isStatic = currentExpression.Static; + } + bool isWildcard = false; if (!isMemberCompletion) @@ -2009,7 +2384,7 @@ private List GetResultForIdentifier(CompletionContext completi if (isMemberCompletion) { - result = CompletionCompleters.CompleteMember(completionContext, @static: (isStatic || memberOperator == TokenKind.ColonColon)); + result = CompletionCompleters.CompleteMember(completionContext, @static: (isStatic || memberOperator == TokenKind.ColonColon), ref replacementLength); // If the last token was just a '.', we tried to complete members. That may // have failed because it wasn't really an attempt to complete a member, in @@ -2035,9 +2410,6 @@ private List GetResultForIdentifier(CompletionContext completi } } - // When it's the content of a quoted string, we only handle variable/member completion - if (isQuotedString) { return result; } - bool needFileCompletion = false; if (lastAst.Parent is FileRedirectionAst || CompleteAgainstSwitchFile(lastAst, completionContext.TokenBeforeCursor)) { @@ -2049,7 +2421,7 @@ private List GetResultForIdentifier(CompletionContext completi completionContext.WordToComplete = wordToComplete; } } - else if (tokenAtCursorText.IndexOfAny(Utils.Separators.Directory) == 0) + else if (tokenAtCursorText.AsSpan().IndexOfAny('\\', '/') == 0) { var command = lastAst.Parent as CommandBaseAst; if (command != null && command.Redirections.Count > 0) @@ -2093,6 +2465,7 @@ private List GetResultForIdentifier(CompletionContext completi result = CompletionCompleters.CompleteCommandArgument(completionContext); replacementIndex = completionContext.ReplacementIndex; replacementLength = completionContext.ReplacementLength; + return result; } @@ -2101,33 +2474,47 @@ private static List GetResultForAttributeArgument(CompletionCo // Attribute member arguments Type attributeType = null; string argName = string.Empty; - Ast argAst = completionContext.RelatedAsts.Find(ast => ast is NamedAttributeArgumentAst); - NamedAttributeArgumentAst namedArgAst = argAst as NamedAttributeArgumentAst; - if (argAst != null && namedArgAst != null) + Ast argAst = completionContext.RelatedAsts.Find(static ast => ast is NamedAttributeArgumentAst); + AttributeAst attAst; + if (argAst is NamedAttributeArgumentAst namedArgAst) { - attributeType = ((AttributeAst)namedArgAst.Parent).TypeName.GetReflectionAttributeType(); + attAst = (AttributeAst)namedArgAst.Parent; + attributeType = attAst.TypeName.GetReflectionAttributeType(); argName = namedArgAst.ArgumentName; replacementIndex = namedArgAst.Extent.StartOffset; replacementLength = argName.Length; } else { - Ast astAtt = completionContext.RelatedAsts.Find(ast => ast is AttributeAst); - AttributeAst attAst = astAtt as AttributeAst; - if (astAtt != null && attAst != null) + Ast astAtt = completionContext.RelatedAsts.Find(static ast => ast is AttributeAst); + attAst = astAtt as AttributeAst; + if (attAst is not null) { attributeType = attAst.TypeName.GetReflectionAttributeType(); } } - if (attributeType != null) + if (attributeType is not null) { + int cursorPosition = completionContext.CursorPosition.Offset; + var existingArguments = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var namedArgument in attAst.NamedArguments) + { + if (cursorPosition < namedArgument.Extent.StartOffset || cursorPosition > namedArgument.Extent.EndOffset) + { + existingArguments.Add(namedArgument.ArgumentName); + } + } + PropertyInfo[] propertyInfos = attributeType.GetProperties(BindingFlags.Public | BindingFlags.Instance); List result = new List(); foreach (PropertyInfo property in propertyInfos) { - // Ignore getter-only properties, including 'TypeId' (all attributes inherit it). - if (!property.CanWrite) { continue; } + // Ignore getter-only properties and properties that have already been set. + if (!property.CanWrite || existingArguments.Contains(property.Name)) + { + continue; + } if (property.Name.StartsWith(argName, StringComparison.OrdinalIgnoreCase)) { @@ -2191,5 +2578,116 @@ private static List CompleteFileNameAsCommand(CompletionContex return result; } + + /// + /// Complete loop labels after labeled control flow statements such as Break and Continue. + /// + private static List CompleteLoopLabel(CompletionContext completionContext) + { + var result = new List(); + foreach (Ast ast in completionContext.RelatedAsts) + { + if (ast is LabeledStatementAst labeledStatement + && labeledStatement.Label is not null + && (completionContext.WordToComplete is null || labeledStatement.Label.StartsWith(completionContext.WordToComplete, StringComparison.OrdinalIgnoreCase))) + { + result.Add(new CompletionResult(labeledStatement.Label, labeledStatement.Label, CompletionResultType.Text, labeledStatement.Extent.Text)); + } + else if (ast is ErrorStatementAst errorStatement) + { + // Handles incomplete do/switch loops (other labeled statements do not need this special treatment) + // The regex looks for the loopLabel of errorstatements that look like do/switch loops + // For example in ":Label do " it will find "Label". + var labelMatch = Regex.Match(errorStatement.Extent.Text, @"(?<=^:)\w+(?=\s+(do|switch)\b(?!-))", RegexOptions.IgnoreCase); + if (labelMatch.Success) + { + result.Add(new CompletionResult(labelMatch.Value, labelMatch.Value, CompletionResultType.Text, errorStatement.Extent.Text)); + } + } + } + + if (result.Count == 0) + { + return null; + } + + return result; + } + + private static List CompleteUsingKeywords(int cursorOffset, Token[] tokens, ref int replacementIndex, ref int replacementLength) + { + var result = new List(); + Token tokenBeforeCursor = null; + Token tokenAtCursor = null; + + for (int i = tokens.Length - 1; i >= 0; i--) + { + if (tokens[i].Extent.EndOffset < cursorOffset && tokens[i].Kind != TokenKind.LineContinuation) + { + tokenBeforeCursor = tokens[i]; + break; + } + else if (tokens[i].Extent.StartOffset <= cursorOffset && tokens[i].Extent.EndOffset >= cursorOffset && tokens[i].Kind != TokenKind.LineContinuation) + { + tokenAtCursor = tokens[i]; + } + } + + if (tokenBeforeCursor is not null && tokenBeforeCursor.Kind == TokenKind.Using) + { + string wordToComplete = null; + if (tokenAtCursor is not null) + { + replacementIndex = tokenAtCursor.Extent.StartOffset; + replacementLength = tokenAtCursor.Extent.Text.Length; + wordToComplete = tokenAtCursor.Text; + } + else + { + replacementIndex = cursorOffset; + replacementLength = 0; + } + + foreach (var keyword in s_usingKeywords) + { + if (string.IsNullOrEmpty(wordToComplete) || keyword.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)) + { + result.Add(new CompletionResult(keyword, keyword, CompletionResultType.Keyword, GetUsingKeywordToolTip(keyword))); + } + } + } + + if (result.Count > 0) + { + return result; + } + + return null; + } + + private static string GetUsingKeywordToolTip(string keyword) + { + switch (keyword) + { + case "assembly": + return TabCompletionStrings.AssemblyKeywordDescription; + case "module": + return TabCompletionStrings.ModuleKeywordDescription; + case "namespace": + return TabCompletionStrings.NamespaceKeywordDescription; + case "type": + return TabCompletionStrings.TypeKeywordDescription; + default: + return null; + } + } + + private static readonly string[] s_usingKeywords = new string[] + { + "assembly", + "module", + "namespace", + "type" + }; } } diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 853b79e7843..e25586fa55d 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; @@ -12,6 +13,7 @@ using System.Linq; using System.Management.Automation.Internal; using System.Management.Automation.Language; +using System.Management.Automation.Provider; using System.Management.Automation.Runspaces; using System.Reflection; using System.Runtime.InteropServices; @@ -24,6 +26,7 @@ using Microsoft.PowerShell; using Microsoft.PowerShell.Cim; using Microsoft.PowerShell.Commands; +using Microsoft.PowerShell.Commands.Internal.Format; namespace System.Management.Automation { @@ -86,7 +89,7 @@ private static List CompleteCommand(CompletionContext context, var addAmpersandIfNecessary = IsAmpersandNeeded(context, false); string commandName = context.WordToComplete; - string quote = HandleDoubleAndSingleQuote(ref commandName); + string quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref commandName); List commandResults = null; @@ -194,7 +197,7 @@ List ExecuteGetCommandCommand(bool useModulePrefix) if (commandInfos != null && commandInfos.Count > 1) { // OrderBy is using stable sorting - var sortedCommandInfos = commandInfos.OrderBy(a => a, new CommandNameComparer()); + var sortedCommandInfos = commandInfos.Order(new CommandNameComparer()); completionResults = MakeCommandsUnique(sortedCommandInfos, useModulePrefix, addAmpersandIfNecessary, quote); } else @@ -231,7 +234,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; @@ -326,53 +329,72 @@ internal static List MakeCommandsUnique(IEnumerable } } - List endResults = null; foreach (var keyValuePair in commandTable) { - var commandList = keyValuePair.Value as List; - if (commandList != null) + if (keyValuePair.Value is List commandList) { - if (endResults == null) + var modulesWithCommand = new HashSet(StringComparer.OrdinalIgnoreCase); + var importedModules = new HashSet(StringComparer.OrdinalIgnoreCase); + var commandInfoList = new List(commandList.Count); + for (int i = 0; i < commandList.Count; i++) { - endResults = new List(); - } + if (commandList[i] is not CommandInfo commandInfo) + { + continue; + } - // 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 commandInfo = commandList[0] as CommandInfo; - if (commandInfo != null && !string.IsNullOrEmpty(commandInfo.Prefix)) + commandInfoList.Add(commandInfo); + if (commandInfo.CommandType == CommandTypes.Application) { - 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; + } + + 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) { - var commandInfo = commandList[index] as CommandInfo; - Diagnostics.Assert(commandInfo != null, "Elements should always be CommandInfo"); + // 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 + { + 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)); } } } @@ -399,15 +421,10 @@ internal static List MakeCommandsUnique(IEnumerable } } - if (endResults != null && endResults.Count > 0) - { - results.AddRange(endResults); - } - return results; } - private class FindFunctionsVisitor : AstVisitor + private sealed class FindFunctionsVisitor : AstVisitor { internal readonly List FunctionDefinitions = new List(); @@ -424,16 +441,34 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun internal static List CompleteModuleName(CompletionContext context, bool loadedModulesOnly, bool skipEditionCheck = false) { - var moduleName = context.WordToComplete ?? string.Empty; + var wordToComplete = context.WordToComplete ?? string.Empty; var result = new List(); - var quote = HandleDoubleAndSingleQuote(ref moduleName); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); - if (!moduleName.EndsWith('*')) + // Indicates if we should search for modules where the last part of the name matches the input text + // eg: Host finds Microsoft.PowerShell.Host + // If the user has entered a manual wildcard, or a module name that contains a "." we assume they only want results that matches the input exactly. + bool shortNameSearch = wordToComplete.Length > 0 && !WildcardPattern.ContainsWildcardCharacters(wordToComplete) && !wordToComplete.Contains('.'); + + if (!wordToComplete.EndsWith('*')) + { + wordToComplete += "*"; + } + + string[] moduleNames; + WildcardPattern shortNamePattern; + if (shortNameSearch) + { + 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); @@ -445,32 +480,58 @@ internal static List CompleteModuleName(CompletionContext cont } } - Exception exceptionThrown; - var psObjects = context.Helper.ExecuteCurrentPowerShell(out exceptionThrown); + Collection 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 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(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; + } + + if (nestedModulesToFilterOut is not null + && nestedModulesToFilterOut.Contains(moduleInfo)) + { + continue; } - result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, toolTip)); + 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)); } } @@ -494,8 +555,7 @@ internal static List CompleteCommandParameter(CompletionContex DynamicKeywordStatementAst keywordAst = null; for (int i = context.RelatedAsts.Count - 1; i >= 0; i--) { - if (keywordAst == null) - keywordAst = context.RelatedAsts[i] as DynamicKeywordStatementAst; + keywordAst ??= context.RelatedAsts[i] as DynamicKeywordStatementAst; parameterAst = (context.RelatedAsts[i] as CommandParameterAst); if (parameterAst != null) break; } @@ -513,7 +573,7 @@ internal static List CompleteCommandParameter(CompletionContex var lastAst = context.RelatedAsts.Last(); var wordToMatch = string.Concat(context.WordToComplete.AsSpan(1), "*"); var pattern = WildcardPattern.Get(wordToMatch, WildcardOptions.IgnoreCase); - var parameterNames = keywordAst.CommandElements.Where(ast => ast is CommandParameterAst).Select(ast => (ast as CommandParameterAst).ParameterName); + var parameterNames = keywordAst.CommandElements.Where(static ast => ast is CommandParameterAst).Select(static ast => (ast as CommandParameterAst).ParameterName); foreach (var parameterName in s_parameterNamesOfImportDSCResource) { if (pattern.IsMatch(parameterName) && !parameterNames.Contains(parameterName, StringComparer.OrdinalIgnoreCase)) @@ -532,6 +592,7 @@ internal static List CompleteCommandParameter(CompletionContex return result; } + bool bindPositionalParameters = true; if (parameterAst != null) { // Parent must be a command @@ -550,10 +611,24 @@ internal static List CompleteCommandParameter(CompletionContex // Parent must be a command commandAst = (CommandAst)dashAst.Parent; partialName = string.Empty; + + // If the user tries to tab complete a new parameter in front of a positional argument like: dir - C:\ + // the user may want to add the parameter name so we don't want to bind positional arguments + if (commandAst is not null) + { + foreach (var element in commandAst.CommandElements) + { + if (element.Extent.StartOffset > context.TokenAtCursor.Extent.StartOffset) + { + bindPositionalParameters = element is CommandParameterAst; + break; + } + } + } } PseudoBindingInfo pseudoBinding = new PseudoParameterBinder() - .DoPseudoParameterBinding(commandAst, null, parameterAst, PseudoParameterBinder.BindingType.ParameterCompletion); + .DoPseudoParameterBinding(commandAst, null, parameterAst, PseudoParameterBinder.BindingType.ParameterCompletion, bindPositionalParameters); // The command cannot be found or it's not a cmdlet, not a script cmdlet, not a function. // Try completing as if it the parameter is a command argument for native command completion. if (pseudoBinding == null) @@ -695,7 +770,18 @@ private static List GetParameterCompletionResults(string param break; } - Diagnostics.Assert(matchedParameterName != null, "we should find matchedParameterName from the BoundArguments"); + if (matchedParameterName is null) + { + // The pseudo binder has skipped a parameter + // This will happen when completing parameters for commands with dynamic parameters. + result = GetParameterCompletionResults( + parameterName, + bindingInfo.ValidParameterSetsFlags, + bindingInfo.UnboundParameters, + withColon); + return result; + } + MergedCompiledCommandParameter param = bindingInfo.BoundParameters[matchedParameterName]; WildcardPattern pattern = WildcardPattern.Get(parameterName + "*", WildcardOptions.IgnoreCase); @@ -707,13 +793,21 @@ private static List GetParameterCompletionResults(string param string tooltip = parameterType + matchedParameterName; result.Add(new CompletionResult(completionText, matchedParameterName, CompletionResultType.ParameterName, tooltip)); } - - // Process alias when there is partial input - result.AddRange(from alias in param.Parameter.Aliases - where pattern.IsMatch(alias) - select - new CompletionResult("-" + alias + colonSuffix, alias, CompletionResultType.ParameterName, - parameterType + alias)); + else + { + // Process alias when there is partial input + foreach (var alias in param.Parameter.Aliases) + { + if (pattern.IsMatch(alias)) + { + result.Add(new CompletionResult( + $"-{alias}{colonSuffix}", + alias, + CompletionResultType.ParameterName, + parameterType + alias)); + } + } + } return result; } @@ -779,15 +873,20 @@ private static List GetParameterCompletionResults( tooltip)); } } - - if (parameterName != string.Empty) + else if (parameterName != string.Empty) { // Process alias when there is partial input - listInUse.AddRange(from alias in param.Parameter.Aliases - where pattern.IsMatch(alias) - select - new CompletionResult("-" + alias + colonSuffix, alias, CompletionResultType.ParameterName, - type + alias)); + foreach (var alias in param.Parameter.Aliases) + { + if (pattern.IsMatch(alias)) + { + listInUse.Add(new CompletionResult( + $"-{alias}{colonSuffix}", + alias, + CompletionResultType.ParameterName, + type + alias)); + } + } } } @@ -878,7 +977,7 @@ internal static List CompleteCommandArgument(CompletionContext partialPathAst.StringConstantType == StringConstantType.BareWord && secondToLastAst.Extent.EndLineNumber == partialPathAst.Extent.StartLineNumber && secondToLastAst.Extent.EndColumnNumber == partialPathAst.Extent.StartColumnNumber && - partialPathAst.Value.IndexOfAny(Utils.Separators.Directory) == 0) + partialPathAst.Value.AsSpan().IndexOfAny('\\', '/') == 0) { var secondToLastStringConstantAst = secondToLastAst as StringConstantExpressionAst; var secondToLastExpandableStringAst = secondToLastAst as ExpandableStringExpressionAst; @@ -1209,7 +1308,7 @@ internal static List CompleteCommandArgument(CompletionContext if (ret != null && ret.Count > 0) { - var prefix = TokenKind.LParen.Text() + input.Substring(0, fakeReplacementIndex); + string prefix = string.Concat(TokenKind.LParen.Text(), input.AsSpan(0, fakeReplacementIndex)); foreach (CompletionResult entry in ret) { string completionText = prefix + entry.CompletionText; @@ -1258,7 +1357,7 @@ internal static List CompleteCommandArgument(CompletionContext // Treat it as the file name completion // Handle this scenario: & 'c:\a b'\ string fileName = pathAst.Value; - if (commandAst.InvocationOperator != TokenKind.Unknown && fileName.IndexOfAny(Utils.Separators.Directory) == 0 && + if (commandAst.InvocationOperator != TokenKind.Unknown && fileName.AsSpan().IndexOfAny('\\', '/') == 0 && commandAst.CommandElements.Count == 2 && commandAst.CommandElements[0] is StringConstantExpressionAst && commandAst.CommandElements[0].Extent.EndLineNumber == expressionAst.Extent.StartLineNumber && commandAst.CommandElements[0].Extent.EndColumnNumber == expressionAst.Extent.StartColumnNumber) @@ -1328,7 +1427,8 @@ internal static List CompleteCommandArgument(CompletionContext context.Options.Remove("LiteralPaths"); } - if (context.WordToComplete != string.Empty && context.WordToComplete.Contains('-')) + // The word to complete contains a dash and it's not the first character. We try command names in this case. + if (context.WordToComplete.IndexOf('-') > 0) { var commandResults = CompleteCommand(context); if (commandResults != null) @@ -1573,13 +1673,20 @@ private static void CompletePositionalArgument( (defaultParameterSetFlag & validParameterSetFlags) != 0; MergedCompiledCommandParameter positionalParam = null; + MergedCompiledCommandParameter bestMatchParam = null; + ParameterSetSpecificMetadata bestMatchSet = null; + + // Finds the parameter with the position closest to the specified position foreach (MergedCompiledCommandParameter param in parameters) { bool isInParameterSet = (param.Parameter.ParameterSetFlags & validParameterSetFlags) != 0 || param.Parameter.IsInAllSets; if (!isInParameterSet) + { continue; + } var parameterSetDataCollection = param.Parameter.GetMatchingParameterSetData(validParameterSetFlags); + foreach (ParameterSetSpecificMetadata parameterSetData in parameterSetDataCollection) { // in the first pass, we skip the remaining argument ones @@ -1591,36 +1698,45 @@ private static void CompletePositionalArgument( // Check the position int positionInParameterSet = parameterSetData.Position; - if (positionInParameterSet == int.MinValue || positionInParameterSet != position) + if (positionInParameterSet < position) { - // The parameter is not positional, or its position is not what we want + // The parameter is not positional (position == int.MinValue), or its position is lower than what we want. continue; } - if (isDefaultParameterSetValid) + if (bestMatchSet is null + || bestMatchSet.Position > positionInParameterSet + || (isDefaultParameterSetValid && positionInParameterSet == bestMatchSet.Position && defaultParameterSetFlag == parameterSetData.ParameterSetFlag)) { - if (parameterSetData.ParameterSetFlag == defaultParameterSetFlag) + bestMatchParam = param; + bestMatchSet = parameterSetData; + if (positionInParameterSet == position) { - ProcessParameter(commandName, commandAst, context, result, param, boundArguments); - isProcessedAsPositional = result.Count > 0; break; } - else - { - if (positionalParam == null) - positionalParam = param; - } + } + } + } + + if (bestMatchParam is not null) + { + if (isDefaultParameterSetValid) + { + if (bestMatchSet.ParameterSetFlag == defaultParameterSetFlag) + { + ProcessParameter(commandName, commandAst, context, result, bestMatchParam, boundArguments); + isProcessedAsPositional = result.Count > 0; } else { - isProcessedAsPositional = true; - ProcessParameter(commandName, commandAst, context, result, param, boundArguments); - break; + positionalParam ??= bestMatchParam; } } - - if (isProcessedAsPositional) - break; + else + { + isProcessedAsPositional = true; + ProcessParameter(commandName, commandAst, context, result, bestMatchParam, boundArguments); + } } if (!isProcessedAsPositional && positionalParam != null) @@ -1656,9 +1772,9 @@ private static void CompletePositionalArgument( /// /// /// If the argument completion falls into these pre-defined cases: - /// 1. The matching parameter is of type Enum - /// 2. The matching parameter is of type SwitchParameter - /// 3. The matching parameter is declared with ValidateSetAttribute + /// 1. The matching parameter is declared with ValidateSetAttribute + /// 2. The matching parameter is of type Enum + /// 3. The matching parameter is of type SwitchParameter /// 4. Falls into the native command argument completion /// a null instance of CompletionResult is added to the end of the /// "result" list, to indicate that this particular argument completion @@ -1681,78 +1797,24 @@ private static void ProcessParameter( parameterType = parameterType.GetElementType(); } - if (parameterType.IsEnum) - { - RemoveLastNullCompletionResult(result); - - string enumString = LanguagePrimitives.EnumSingleTypeConverter.EnumValues(parameterType); - string separator = CultureInfo.CurrentUICulture.TextInfo.ListSeparator; - string[] enumArray = enumString.Split(separator, StringSplitOptions.RemoveEmptyEntries); - - string wordToComplete = context.WordToComplete; - string quote = HandleDoubleAndSingleQuote(ref wordToComplete); - - var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); - var enumList = new List(); - - foreach (string value in enumArray) - { - if (wordToComplete.Equals(value, StringComparison.OrdinalIgnoreCase)) - { - string completionText = quote == string.Empty ? value : quote + value + quote; - fullMatch = new CompletionResult(completionText, value, CompletionResultType.ParameterValue, value); - continue; - } - - if (pattern.IsMatch(value)) - { - enumList.Add(value); - } - } - - if (fullMatch != null) - { - result.Add(fullMatch); - } - - enumList.Sort(); - result.AddRange(from entry in enumList - let completionText = quote == string.Empty ? entry : quote + entry + quote - select new CompletionResult(completionText, entry, CompletionResultType.ParameterValue, entry)); - - result.Add(CompletionResult.Null); - return; - } - - if (parameterType.Equals(typeof(SwitchParameter))) - { - RemoveLastNullCompletionResult(result); - - if (context.WordToComplete == string.Empty || context.WordToComplete.Equals("$", StringComparison.Ordinal)) - { - result.Add(new CompletionResult("$true", "$true", CompletionResultType.ParameterValue, "$true")); - result.Add(new CompletionResult("$false", "$false", CompletionResultType.ParameterValue, "$false")); - } - - result.Add(CompletionResult.Null); - return; - } - foreach (ValidateArgumentsAttribute att in parameter.Parameter.ValidationAttributes) { if (att is ValidateSetAttribute setAtt) { RemoveLastNullCompletionResult(result); - string wordToComplete = context.WordToComplete; - string quote = HandleDoubleAndSingleQuote(ref wordToComplete); + string wordToComplete = context.WordToComplete ?? string.Empty; + string quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); var setList = new List(); foreach (string value in setAtt.ValidValues) { - if (value == string.Empty) { continue; } + if (value == string.Empty) + { + continue; + } if (wordToComplete.Equals(value, StringComparison.OrdinalIgnoreCase)) { @@ -1777,23 +1839,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)); } @@ -1803,6 +1850,71 @@ private static void ProcessParameter( } } + if (parameterType.IsEnum) + { + RemoveLastNullCompletionResult(result); + + IEnumerable enumValues = LanguagePrimitives.EnumSingleTypeConverter.GetEnumValues(parameterType); + + // Exclude values not accepted by ValidateRange-attributes + foreach (ValidateArgumentsAttribute att in parameter.Parameter.ValidationAttributes) + { + if (att is ValidateRangeAttribute rangeAtt) + { + enumValues = rangeAtt.GetValidatedElements(enumValues); + } + } + + string wordToComplete = context.WordToComplete ?? string.Empty; + string quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); + + var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); + var enumList = new List(); + + foreach (Enum value in enumValues) + { + string name = value.ToString(); + if (wordToComplete.Equals(name, StringComparison.OrdinalIgnoreCase)) + { + string completionText = quote == string.Empty ? name : quote + name + quote; + fullMatch = new CompletionResult(completionText, name, CompletionResultType.ParameterValue, name); + continue; + } + + if (pattern.IsMatch(name)) + { + enumList.Add(name); + } + } + + if (fullMatch != null) + { + result.Add(fullMatch); + } + + enumList.Sort(); + result.AddRange(from entry in enumList + let completionText = quote == string.Empty ? entry : quote + entry + quote + select new CompletionResult(completionText, entry, CompletionResultType.ParameterValue, entry)); + + result.Add(CompletionResult.Null); + return; + } + + if (parameterType.Equals(typeof(SwitchParameter))) + { + RemoveLastNullCompletionResult(result); + + if (context.WordToComplete == string.Empty || context.WordToComplete.Equals("$", StringComparison.Ordinal)) + { + result.Add(new CompletionResult("$true", "$true", CompletionResultType.ParameterValue, "$true")); + result.Add(new CompletionResult("$false", "$false", CompletionResultType.ParameterValue, "$false")); + } + + result.Add(CompletionResult.Null); + return; + } + NativeCommandArgumentCompletion(commandName, parameter.Parameter, result, commandAst, context, boundArguments); } @@ -1996,7 +2108,7 @@ private static void NativeCommandArgumentCompletion( string parameterName = parameter.Name; // Fall back to the commandAst command name if a command name is not found. This can be caused by a script block or AST with the matching function definition being passed to CompleteInput - // This allows for editors and other tools using CompleteInput with Script/AST definations to get values from RegisteredArgumentCompleters to better match the console experience. + // This allows for editors and other tools using CompleteInput with Script/AST definitions to get values from RegisteredArgumentCompleters to better match the console experience. // See issue https://github.com/PowerShell/PowerShell/issues/10567 string actualCommandName = string.IsNullOrEmpty(commandName) ? commandAst.GetCommandName() @@ -2030,19 +2142,17 @@ private static void NativeCommandArgumentCompletion( { try { - if (argumentCompleterAttribute.Type != null) + var completer = argumentCompleterAttribute.CreateArgumentCompleter(); + + if (completer != null) { - var completer = Activator.CreateInstance(argumentCompleterAttribute.Type) as IArgumentCompleter; - if (completer != null) + var customResults = completer.CompleteArgument(commandName, parameterName, + context.WordToComplete, commandAst, GetBoundArgumentsAsHashtable(context)); + if (customResults != null) { - var customResults = completer.CompleteArgument(commandName, parameterName, - context.WordToComplete, commandAst, GetBoundArgumentsAsHashtable(context)); - if (customResults != null) - { - result.AddRange(customResults); - result.Add(CompletionResult.Null); - return; - } + result.AddRange(customResults); + result.Add(CompletionResult.Null); + return; } } else @@ -2084,6 +2194,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"); @@ -2122,6 +2238,22 @@ private static void NativeCommandArgumentCompletion( NativeCompletionGetHelpCommand(context, parameterName, /* isHelpRelated: */ true, result); break; } + case "Save-Help": + { + if (parameterName.Equals("Module", StringComparison.OrdinalIgnoreCase)) + { + CompleteModule(context, result); + } + break; + } + case "Update-Help": + { + if (parameterName.Equals("Module", StringComparison.OrdinalIgnoreCase)) + { + CompleteModule(context, result); + } + break; + } case "Invoke-Expression": { if (parameterName.Equals("Command", StringComparison.OrdinalIgnoreCase)) @@ -2275,7 +2407,7 @@ private static void NativeCommandArgumentCompletion( { if (parameterName.Equals("MemberName", StringComparison.OrdinalIgnoreCase)) { - NativeCompletionMemberName(context, result, commandAst); + NativeCompletionMemberName(context, result, commandAst, boundArguments?[parameterName], propertiesOnly: false); } break; @@ -2284,6 +2416,19 @@ private static void NativeCommandArgumentCompletion( case "Measure-Object": case "Sort-Object": case "Where-Object": + { + if (parameterName.Equals("Property", StringComparison.OrdinalIgnoreCase)) + { + NativeCompletionMemberName(context, result, commandAst, boundArguments?[parameterName]); + } + else if (parameterName.Equals("Value", StringComparison.OrdinalIgnoreCase) + && boundArguments?["Property"] is AstPair pair && pair.Argument is StringConstantExpressionAst stringAst) + { + NativeCompletionMemberValue(context, result, commandAst, stringAst.Value); + } + + break; + } case "Format-Custom": case "Format-List": case "Format-Table": @@ -2291,7 +2436,11 @@ private static void NativeCommandArgumentCompletion( { if (parameterName.Equals("Property", StringComparison.OrdinalIgnoreCase)) { - NativeCompletionMemberName(context, result, commandAst); + NativeCompletionMemberName(context, result, commandAst, boundArguments?[parameterName]); + } + else if (parameterName.Equals("View", StringComparison.OrdinalIgnoreCase)) + { + NativeCompletionFormatViewName(context, boundArguments, result, commandAst, commandName); } break; @@ -2302,7 +2451,7 @@ private static void NativeCommandArgumentCompletion( || parameterName.Equals("ExcludeProperty", StringComparison.OrdinalIgnoreCase) || parameterName.Equals("ExpandProperty", StringComparison.OrdinalIgnoreCase)) { - NativeCompletionMemberName(context, result, commandAst); + NativeCompletionMemberName(context, result, commandAst, boundArguments?[parameterName]); } break; @@ -2324,10 +2473,24 @@ private static void NativeCommandArgumentCompletion( case "Invoke-CimMethod": case "New-CimInstance": case "Register-CimIndicationEvent": + case "Set-CimInstance": { - NativeCompletionCimCommands(parameterName, boundArguments, result, commandAst, context); - break; - } + // Avoids completion for parameters that expect a hashtable. + if (parameterName.Equals("Arguments", StringComparison.OrdinalIgnoreCase) + || (parameterName.Equals("Property", StringComparison.OrdinalIgnoreCase) && !commandName.Equals("Get-CimInstance"))) + { + break; + } + + HashSet excludedValues = null; + if (parameterName.Equals("Property", StringComparison.OrdinalIgnoreCase) && boundArguments["Property"] is AstPair pair) + { + excludedValues = GetParameterValues(pair, context.CursorPosition.Offset); + } + + NativeCompletionCimCommands(parameterName, boundArguments, result, commandAst, context, excludedValues, commandName); + break; + } default: { @@ -2496,7 +2659,9 @@ private static void NativeCompletionCimCommands( Dictionary boundArguments, List result, CommandAst commandAst, - CompletionContext context) + CompletionContext context, + HashSet excludedValues, + string commandName) { if (boundArguments != null) { @@ -2517,6 +2682,7 @@ private static void NativeCompletionCimCommands( } } + RemoveLastNullCompletionResult(result); if (parameter.Equals("Namespace", StringComparison.OrdinalIgnoreCase)) { NativeCompletionCimNamespace(result, context); @@ -2562,6 +2728,16 @@ private static void NativeCompletionCimCommands( { NativeCompletionCimMethodName(pseudoboundCimNamespace, pseudoboundClassName, !gotInstance, result, context); } + else if (parameter.Equals("Arguments", StringComparison.OrdinalIgnoreCase)) + { + string pseudoboundMethodName = NativeCommandArgumentCompletion_ExtractSecondaryArgument(boundArguments, "MethodName").FirstOrDefault(); + NativeCompletionCimMethodArgumentName(pseudoboundCimNamespace, pseudoboundClassName, pseudoboundMethodName, excludedValues, result, context); + } + else if (parameter.Equals("Property", StringComparison.OrdinalIgnoreCase)) + { + bool includeReadOnly = !commandName.Equals("Set-CimInstance", StringComparison.OrdinalIgnoreCase); + NativeCompletionCimPropertyName(pseudoboundCimNamespace, pseudoboundClassName, includeReadOnly, excludedValues, result, context); + } } } @@ -2598,7 +2774,7 @@ private static IEnumerable NativeCompletionCimAssociationResultClassName resultClassNames.AddRange( cimSession.QueryInstances(cimNamespaceOfSource ?? "root/cimv2", "WQL", query) - .Select(associationInstance => associationInstance.CimSystemProperties.ClassName)); + .Select(static associationInstance => associationInstance.CimSystemProperties.ClassName)); cimClass = cimClass.CimSuperClass; } @@ -2627,7 +2803,7 @@ private static void NativeCompletionCimAssociationResultClassName( WildcardPattern resultClassNamePattern = WildcardPattern.Get(context.WordToComplete + "*", WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); result.AddRange(resultClassNames .Where(resultClassNamePattern.IsMatch) - .Select(x => new CompletionResult(x, x, CompletionResultType.Type, string.Format(CultureInfo.InvariantCulture, "{0} -> {1}", pseudoboundClassName, x)))); + .Select(x => new CompletionResult(x, x, CompletionResultType.Type, string.Create(CultureInfo.InvariantCulture, $"{pseudoboundClassName} -> {x}")))); } private static void NativeCompletionCimMethodName( @@ -2658,7 +2834,7 @@ private static void NativeCompletionCimMethodName( continue; } - bool currentMethodIsStatic = methodDeclaration.Qualifiers.Any(q => q.Name.Equals("Static", StringComparison.OrdinalIgnoreCase)); + bool currentMethodIsStatic = methodDeclaration.Qualifiers.Any(static q => q.Name.Equals("Static", StringComparison.OrdinalIgnoreCase)); if ((currentMethodIsStatic && !staticMethod) || (!currentMethodIsStatic && staticMethod)) { continue; @@ -2670,7 +2846,7 @@ private static void NativeCompletionCimMethodName( bool gotFirstParameter = false; foreach (var methodParameter in methodDeclaration.Parameters) { - bool outParameter = methodParameter.Qualifiers.Any(q => q.Name.Equals("Out", StringComparison.OrdinalIgnoreCase)); + bool outParameter = methodParameter.Qualifiers.Any(static q => q.Name.Equals("Out", StringComparison.OrdinalIgnoreCase)); if (!gotFirstParameter) { @@ -2701,7 +2877,83 @@ private static void NativeCompletionCimMethodName( localResults.Add(new CompletionResult(methodName, methodName, CompletionResultType.Method, tooltipText.ToString())); } - result.AddRange(localResults.OrderBy(x => x.ListItemText, StringComparer.OrdinalIgnoreCase)); + result.AddRange(localResults.OrderBy(static x => x.ListItemText, StringComparer.OrdinalIgnoreCase)); + } + + private static void NativeCompletionCimMethodArgumentName( + string pseudoboundNamespace, + string pseudoboundClassName, + string pseudoboundMethodName, + HashSet excludedParameters, + List result, + CompletionContext context) + { + if (string.IsNullOrWhiteSpace(pseudoboundClassName) || string.IsNullOrWhiteSpace(pseudoboundMethodName)) + { + return; + } + + CimClass cimClass; + using (var cimSession = CimSession.Create(null)) + { + using var options = new CimOperationOptions(); + options.Flags |= CimOperationFlags.LocalizedQualifiers; + cimClass = cimSession.GetClass(pseudoboundNamespace ?? "root/cimv2", pseudoboundClassName, options); + } + + var methodParameters = cimClass.CimClassMethods[pseudoboundMethodName]?.Parameters; + if (methodParameters is null) + { + return; + } + + foreach (var parameter in methodParameters) + { + if ((string.IsNullOrEmpty(context.WordToComplete) || parameter.Name.StartsWith(context.WordToComplete, StringComparison.OrdinalIgnoreCase)) + && (excludedParameters is null || !excludedParameters.Contains(parameter.Name)) + && parameter.Qualifiers["In"]?.Value is true) + { + string parameterDescription = parameter.Qualifiers["Description"]?.Value as string ?? string.Empty; + string toolTip = $"[{CimInstanceAdapter.CimTypeToTypeNameDisplayString(parameter.CimType)}] {parameterDescription}"; + result.Add(new CompletionResult(parameter.Name, parameter.Name, CompletionResultType.Property, toolTip)); + } + } + } + + private static void NativeCompletionCimPropertyName( + string pseudoboundNamespace, + string pseudoboundClassName, + bool includeReadOnly, + HashSet excludedProperties, + List result, + CompletionContext context) + { + if (string.IsNullOrWhiteSpace(pseudoboundClassName)) + { + return; + } + + CimClass cimClass; + using (var cimSession = CimSession.Create(null)) + { + using var options = new CimOperationOptions(); + options.Flags |= CimOperationFlags.LocalizedQualifiers; + cimClass = cimSession.GetClass(pseudoboundNamespace ?? "root/cimv2", pseudoboundClassName, options); + } + + foreach (var property in cimClass.CimClassProperties) + { + bool isReadOnly = (property.Flags & CimFlags.ReadOnly) != 0; + if ((!isReadOnly || (isReadOnly && includeReadOnly)) + && (string.IsNullOrEmpty(context.WordToComplete) || property.Name.StartsWith(context.WordToComplete, StringComparison.OrdinalIgnoreCase)) + && (excludedProperties is null || !excludedProperties.Contains(property.Name))) + { + string propertyDescription = property.Qualifiers["Description"]?.Value as string ?? string.Empty; + string accessString = isReadOnly ? "{ get; }" : "{ get; set; }"; + string toolTip = $"[{CimInstanceAdapter.CimTypeToTypeNameDisplayString(property.CimType)}] {accessString} {propertyDescription}"; + result.Add(new CompletionResult(property.Name, property.Name, CompletionResultType.Property, toolTip)); + } + } } private static readonly ConcurrentDictionary> s_cimNamespaceToClassNames = @@ -2776,7 +3028,7 @@ private static void NativeCompletionCimNamespace( string prefixOfChildNamespace = string.Empty; if (!string.IsNullOrEmpty(context.WordToComplete)) { - int lastSlashOrBackslash = context.WordToComplete.LastIndexOfAny(Utils.Separators.Directory); + int lastSlashOrBackslash = context.WordToComplete.AsSpan().LastIndexOfAny('\\', '/'); if (lastSlashOrBackslash != (-1)) { containerNamespace = context.WordToComplete.Substring(0, lastSlashOrBackslash); @@ -2820,7 +3072,7 @@ private static void NativeCompletionCimNamespace( } } - result.AddRange(namespaceResults.OrderBy(x => x.ListItemText, StringComparer.OrdinalIgnoreCase)); + result.AddRange(namespaceResults.OrderBy(static x => x.ListItemText, StringComparer.OrdinalIgnoreCase)); } private static void NativeCompletionGetCommand(CompletionContext context, string moduleName, string paramName, List result) @@ -2847,39 +3099,46 @@ 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))) { - RemoveLastNullCompletionResult(result); + CompleteModule(context, result); + } + } + + private static void CompleteModule(CompletionContext context, List result) + { + RemoveLastNullCompletionResult(result); - var modules = new HashSet(StringComparer.OrdinalIgnoreCase); - var moduleResults = CompleteModuleName(context, loadedModulesOnly: true); - if (moduleResults != null) + var modules = new HashSet(StringComparer.OrdinalIgnoreCase); + var moduleResults = CompleteModuleName(context, loadedModulesOnly: true); + if (moduleResults != null) + { + foreach (CompletionResult moduleResult in moduleResults) { - foreach (CompletionResult moduleResult in moduleResults) + if (!modules.Contains(moduleResult.ToolTip)) { - if (!modules.Contains(moduleResult.ToolTip)) - { - modules.Add(moduleResult.ToolTip); - result.Add(moduleResult); - } + modules.Add(moduleResult.ToolTip); + result.Add(moduleResult); } } + } - moduleResults = CompleteModuleName(context, loadedModulesOnly: false); - if (moduleResults != null) + moduleResults = CompleteModuleName(context, loadedModulesOnly: false); + if (moduleResults != null) + { + foreach (CompletionResult moduleResult in moduleResults) { - foreach (CompletionResult moduleResult in moduleResults) + if (!modules.Contains(moduleResult.ToolTip)) { - if (!modules.Contains(moduleResult.ToolTip)) - { - modules.Add(moduleResult.ToolTip); - result.Add(moduleResult); - } + modules.Add(moduleResult.ToolTip); + result.Add(moduleResult); } } - - result.Add(CompletionResult.Null); } + + result.Add(CompletionResult.Null); } private static void NativeCompletionGetHelpCommand(CompletionContext context, string paramName, bool isHelpRelated, List result) @@ -2919,7 +3178,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('*')) { @@ -2941,17 +3200,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)) { @@ -2970,7 +3219,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('*')) { @@ -3032,17 +3281,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)); } @@ -3057,7 +3296,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('*')) { @@ -3107,17 +3346,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)); } @@ -3191,7 +3420,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('*')) { @@ -3247,17 +3476,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)) @@ -3282,7 +3501,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('*')) { @@ -3300,17 +3519,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)); } @@ -3326,7 +3535,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('*')) { @@ -3348,17 +3557,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)); } @@ -3373,7 +3572,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('*')) { @@ -3399,17 +3598,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)); } @@ -3430,17 +3619,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)); } @@ -3460,7 +3639,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 += "*"; @@ -3486,7 +3665,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 == "'") @@ -3519,7 +3698,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('*')) { @@ -3536,17 +3715,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)); } @@ -3580,7 +3749,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('*')) { @@ -3599,17 +3768,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)); } @@ -3787,44 +3946,132 @@ private static void NativeCompletionPathArgument(CompletionContext context, stri result.Add(CompletionResult.Null); } - private static void NativeCompletionMemberName(CompletionContext context, List result, CommandAst commandAst) + private static IEnumerable GetInferenceTypes(CompletionContext context, CommandAst commandAst) { // Command is something like where-object/foreach-object/format-list/etc. where there is a parameter that is a property name // and we want member names based on the input object, which is either the parameter InputObject, or comes from the pipeline. - if (!(commandAst.Parent is PipelineAst pipelineAst)) - return; + if (commandAst.Parent is not PipelineAst pipelineAst) + { + return null; + } int i; for (i = 0; i < pipelineAst.PipelineElements.Count; i++) { if (pipelineAst.PipelineElements[i] == commandAst) + { break; + } } IEnumerable prevType = null; if (i == 0) { + // based on a type of the argument which is binded to 'InputObject' parameter. AstParameterArgumentPair pair; if (!context.PseudoBindingInfo.BoundArguments.TryGetValue("InputObject", out pair) || !pair.ArgumentSpecified) { - return; + return null; } var astPair = pair as AstPair; if (astPair == null || astPair.Argument == null) { - return; + return null; } prevType = AstTypeInference.InferTypeOf(astPair.Argument, context.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval); } else { + // based on OutputTypeAttribute() of the first cmdlet in pipeline. prevType = AstTypeInference.InferTypeOf(pipelineAst.PipelineElements[i - 1], context.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval); } - CompleteMemberByInferredType(context.TypeInferenceContext, prevType, result, context.WordToComplete + "*", filter: IsPropertyMember, isStatic: false); + return prevType; + } + + private static void NativeCompletionMemberName(CompletionContext context, List result, CommandAst commandAst, AstParameterArgumentPair parameterInfo, bool propertiesOnly = true) + { + IEnumerable prevType = TypeInferenceVisitor.GetInferredEnumeratedTypes(GetInferenceTypes(context, commandAst)); + if (prevType is not null) + { + HashSet excludedMembers = null; + if (parameterInfo is AstPair pair) + { + excludedMembers = GetParameterValues(pair, context.CursorPosition.Offset); + } + + Func filter = propertiesOnly ? IsPropertyMember : null; + CompleteMemberByInferredType(context.TypeInferenceContext, prevType, result, context.WordToComplete + "*", filter, isStatic: false, excludedMembers, addMethodParenthesis: false); + } + + result.Add(CompletionResult.Null); + } + + private static void NativeCompletionMemberValue(CompletionContext context, List result, CommandAst commandAst, string propertyName) + { + string wordToComplete = context.WordToComplete.Trim('"', '\''); + IEnumerable prevTypes = GetInferenceTypes(context, commandAst); + if (prevTypes is not null) + { + foreach (var type in prevTypes) + { + if (type.Type is null) + { + continue; + } + + PropertyInfo property = type.Type.GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); + if (property is not null && property.PropertyType.IsEnum) + { + foreach (var value in property.PropertyType.GetEnumNames()) + { + if (value.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)) + { + result.Add(new CompletionResult(value, value, CompletionResultType.ParameterValue, value)); + } + } + + break; + } + } + } + + result.Add(CompletionResult.Null); + } + + /// + /// Returns all string values bound to a parameter except the one the cursor is currently at. + /// + private static HashSetGetParameterValues(AstPair parameter, int cursorOffset) + { + var result = new HashSet(StringComparer.OrdinalIgnoreCase); + var parameterValues = parameter.Argument.FindAll(ast => !(cursorOffset >= ast.Extent.StartOffset && cursorOffset <= ast.Extent.EndOffset) && ast is StringConstantExpressionAst, searchNestedScriptBlocks: false); + foreach (Ast ast in parameterValues) + { + result.Add(ast.Extent.Text); + } + + return result; + } + + private static void NativeCompletionFormatViewName( + CompletionContext context, + Dictionary boundArguments, + List result, + CommandAst commandAst, + string commandName) + { + IEnumerable prevType = NativeCommandArgumentCompletion_InferTypesOfArgument(boundArguments, commandAst, context, "InputObject"); + + if (prevType is not null) + { + string[] inferTypeNames = prevType.Select(t => t.Name).ToArray(); + CompleteFormatViewByInferredType(context, inferTypeNames, result, commandName); + } + result.Add(CompletionResult.Null); } @@ -3952,9 +4199,11 @@ private static ArgumentLocation FindTargetArgumentLocation(Collection token.Extent.StartOffset) + if ((token.Kind == TokenKind.Parameter && token.Extent.StartOffset == arg.Parameter.Extent.StartOffset) + || (token.Extent.StartOffset > arg.Argument.Extent.StartOffset && token.Extent.EndOffset < arg.Argument.Extent.EndOffset)) { - // case: Get-Cmdlet -Param abc + // case 1: Get-Cmdlet -Param abc + // case 2: dir -Path .\abc.txt, -File return new ArgumentLocation() { Argument = arg, IsPositional = false, Position = -1 }; } } @@ -4124,870 +4373,2111 @@ internal static IEnumerable CompleteFilename(CompletionContext internal static IEnumerable CompleteFilename(CompletionContext context, bool containerOnly, HashSet extension) { var wordToComplete = context.WordToComplete; - var quote = HandleDoubleAndSingleQuote(ref wordToComplete); - var results = new List(); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); - // First, try to match \\server\share - 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) + { + return CommandCompletion.EmptyCompletionResult; + } + + var shareResults = new List(shares.Count); foreach (var share in shares) { 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}"; } - results.Add(new CompletionResult(shareFullPath, shareFullPath, CompletionResultType.ProviderContainer, shareFullPath)); + shareResults.Add(new CompletionResult(completionText, share, CompletionResultType.ProviderContainer, sharePath)); } } + + return shareResults; + } + + string filter; + string basePath; + int providerSeparatorIndex = -1; + bool defaultRelativePath = false; + bool inputUsedHomeChar = false; + + if (string.IsNullOrEmpty(wordToComplete)) + { + filter = "*"; + basePath = "."; + defaultRelativePath = true; } else { - // We want to prefer relative paths in a completion result unless the user has already - // specified a drive or portion of the path. - var executionContext = context.ExecutionContext; - var defaultRelative = string.IsNullOrWhiteSpace(wordToComplete) - || (wordToComplete.IndexOfAny(Utils.Separators.Directory) != 0 && - !Regex.Match(wordToComplete, @"^~[\\/]+.*").Success && - !executionContext.LocationGlobber.IsAbsolutePath(wordToComplete, out _)); - var relativePaths = context.GetOption("RelativePaths", @default: defaultRelative); - var useLiteralPath = context.GetOption("LiteralPaths", @default: false); - - if (useLiteralPath && LocationGlobber.StringContainsGlobCharacters(wordToComplete)) - { - wordToComplete = WildcardPattern.Escape(wordToComplete, Utils.Separators.StarOrQuestion); - } + providerSeparatorIndex = wordToComplete.IndexOf("::", StringComparison.Ordinal); + int pathStartOffset = providerSeparatorIndex == -1 ? 0 : providerSeparatorIndex + 2; + inputUsedHomeChar = pathStartOffset + 2 <= wordToComplete.Length + && wordToComplete[pathStartOffset] is '~' + && wordToComplete[pathStartOffset + 1] is '/' or '\\'; - if (!defaultRelative && wordToComplete.Length >= 2 && wordToComplete[1] == ':' && char.IsLetter(wordToComplete[0]) && executionContext != null) + // This simple analysis is quick but doesn't handle scenarios where a separator character is not actually a separator + // For example "\" or ":" in *nix filenames. This is only a problem if it appears to be the last separator though. + int lastSeparatorIndex = wordToComplete.LastIndexOfAny(Utils.Separators.DirectoryOrDrive); + if (lastSeparatorIndex == -1) { - // We don't actually need the drive, but the drive must be "mounted" in PowerShell before completion - // can succeed. This call will mount the drive if it wasn't already. - executionContext.SessionState.Drive.GetAtScope(wordToComplete.Substring(0, 1), "global"); + // Input is a simple word with no path separators like: "Program Files" + filter = $"{wordToComplete}*"; + basePath = "."; + defaultRelativePath = true; } - - var powerShellExecutionHelper = context.Helper; - powerShellExecutionHelper - .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Resolve-Path") - .AddParameter("Path", wordToComplete + "*"); - - Exception exceptionThrown; - var psobjs = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown); - - if (psobjs != null) + else { - var isFileSystem = false; - var wordContainsProviderId = ProviderSpecified(wordToComplete); - - if (psobjs.Count > 0) + if (lastSeparatorIndex + 1 == wordToComplete.Length) { - dynamic firstObj = psobjs[0]; - var provider = firstObj.Provider as ProviderInfo; - isFileSystem = provider != null && - provider.Name.Equals(FileSystemProvider.ProviderName, - StringComparison.OrdinalIgnoreCase); + // Input ends with a separator like: "./", "filesystem::" or "C:" + filter = "*"; + basePath = wordToComplete; } else { - try - { - ProviderInfo provider; - if (defaultRelative) - { - provider = executionContext.EngineSessionState.CurrentDrive.Provider; - } - else - { - executionContext.LocationGlobber.GetProviderPath(wordToComplete, out provider); - } - - isFileSystem = provider != null && - provider.Name.Equals(FileSystemProvider.ProviderName, - StringComparison.OrdinalIgnoreCase); - } - catch (Exception) - { - } + // Input contains a separator, but doesn't end with one like: "C:\Program Fil" or "Registry::HKEY_LOC" + filter = $"{wordToComplete.Substring(lastSeparatorIndex + 1)}*"; + basePath = wordToComplete.Substring(0, lastSeparatorIndex + 1); } - if (isFileSystem) + if (!inputUsedHomeChar && basePath[0] is not '/' and not '\\') { - bool hiddenFilesAreHandled = false; + defaultRelativePath = !context.ExecutionContext.LocationGlobber.IsAbsolutePath(wordToComplete, out _); + } + } + } - if (psobjs.Count > 0 && !LocationGlobber.StringContainsGlobCharacters(wordToComplete)) - { - string leaf = null; - string pathWithoutProvider = wordContainsProviderId - ? wordToComplete.Substring(wordToComplete.IndexOf(':') + 2) - : wordToComplete; + StringConstantType stringType; + switch (quote) + { + case "": + stringType = StringConstantType.BareWord; + break; - try - { - leaf = Path.GetFileName(pathWithoutProvider); - } - catch (Exception) - { - } + case "\"": + stringType = StringConstantType.DoubleQuoted; + break; - var notHiddenEntries = new HashSet(StringComparer.OrdinalIgnoreCase); - string providerPath = null; + default: + stringType = StringConstantType.SingleQuoted; + break; + } - foreach (dynamic entry in psobjs) - { - providerPath = entry.ProviderPath; - if (string.IsNullOrEmpty(providerPath)) - { - // This is unexpected. ProviderPath should never be null or an empty string - leaf = null; - break; - } + var useLiteralPath = context.GetOption("LiteralPaths", @default: false); + if (useLiteralPath) + { + basePath = EscapePath(basePath, stringType, useLiteralPath, out _); + } - if (!notHiddenEntries.Contains(providerPath)) - { - notHiddenEntries.Add(providerPath); - } - } + PowerShell currentPS = context.Helper + .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Resolve-Path") + .AddParameter("Path", basePath); - if (leaf != null) + string relativeBasePath; + var useRelativePath = context.GetOption("RelativePaths", @default: defaultRelativePath); + if (useRelativePath) + { + 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 resolvedPaths = context.Helper.ExecuteCurrentPowerShell(out _); + if (resolvedPaths is null || resolvedPaths.Count == 0) + { + return CommandCompletion.EmptyCompletionResult; + } + + var resolvedProvider = ((PathInfo)resolvedPaths[0].BaseObject).Provider; + string providerPrefix; + if (providerSeparatorIndex == -1) + { + providerPrefix = string.Empty; + } + else if (providerSeparatorIndex == resolvedProvider.Name.Length) + { + providerPrefix = $"{resolvedProvider.Name}::"; + } + else + { + providerPrefix = $"{resolvedProvider.ModuleName}\\{resolvedProvider.Name}::"; + } + + List results; + switch (resolvedProvider.Name) + { + case FileSystemProvider.ProviderName: + results = GetFileSystemProviderResults( + context, + resolvedProvider, + resolvedPaths, + filter, + extension, + containerOnly, + useRelativePath, + useLiteralPath, + inputUsedHomeChar, + providerPrefix, + stringType, + relativeBasePath); + break; + + default: + results = GetDefaultProviderResults( + context, + resolvedProvider, + resolvedPaths, + filter, + containerOnly, + useRelativePath, + useLiteralPath, + inputUsedHomeChar, + providerPrefix, + stringType); + break; + } + + return results.OrderBy(x => x.ToolTip); + } + + /// + /// Helper method for generating path completion results for the file system provider. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + private static List GetFileSystemProviderResults( + CompletionContext context, + ProviderInfo provider, + Collection resolvedPaths, + string filterText, + HashSet includedExtensions, + bool containersOnly, + bool relativePaths, + bool literalPaths, + bool inputUsedHome, + string providerPrefix, + StringConstantType stringType, + string relativeBasePath) + { +#if DEBUG + Diagnostics.Assert(provider.Name.Equals(FileSystemProvider.ProviderName), "Provider should be filesystem provider."); +#endif + var enumerationOptions = _enumerationOptions; + var results = new List(); + string homePath = inputUsedHome && !string.IsNullOrEmpty(provider.Home) ? provider.Home : null; + + WildcardPattern wildcardFilter; + if (WildcardPattern.ContainsRangeWildcard(filterText)) + { + wildcardFilter = WildcardPattern.Get(filterText, WildcardOptions.IgnoreCase); + filterText = "*"; + } + else + { + wildcardFilter = null; + } + + foreach (var item in resolvedPaths) + { + var pathInfo = (PathInfo)item.BaseObject; + var dirInfo = new DirectoryInfo(pathInfo.ProviderPath); + + bool baseQuotesNeeded = false; + string basePath; + if (!relativePaths) + { + if (pathInfo.Drive is null) + { + basePath = dirInfo.FullName; + } + else + { + int stringStartIndex = pathInfo.Drive.Root.EndsWith(provider.ItemSeparator) && pathInfo.Drive.Root.Length > 1 + ? pathInfo.Drive.Root.Length - 1 + : pathInfo.Drive.Root.Length; + + basePath = pathInfo.Drive.VolumeSeparatedByColon + ? string.Concat(pathInfo.Drive.Name, ":", dirInfo.FullName.AsSpan(stringStartIndex)) + : string.Concat(pathInfo.Drive.Name, dirInfo.FullName.AsSpan(stringStartIndex)); + } + + basePath = basePath.EndsWith(provider.ItemSeparator) + ? providerPrefix + basePath + : providerPrefix + basePath + provider.ItemSeparator; + basePath = RebuildPathWithVars(basePath, homePath, stringType, literalPaths, out baseQuotesNeeded); + } + else + { + basePath = null; + } + IEnumerable fileSystemObjects = containersOnly + ? dirInfo.EnumerateDirectories(filterText, enumerationOptions) + : dirInfo.EnumerateFileSystemInfos(filterText, enumerationOptions); + + foreach (var entry in fileSystemObjects) + { + bool isContainer = entry.Attributes.HasFlag(FileAttributes.Directory); + if (!isContainer && includedExtensions is not null && !includedExtensions.Contains(entry.Extension)) + { + continue; + } + + var entryName = entry.Name; + if (wildcardFilter is not null && !wildcardFilter.IsMatch(entryName)) + { + continue; + } + + if (basePath is null) + { + basePath = context.ExecutionContext.EngineSessionState.NormalizeRelativePath( + entry.FullName, + relativeBasePath); + if (!basePath.StartsWith($"..{provider.ItemSeparator}", StringComparison.Ordinal)) + { + basePath = $".{provider.ItemSeparator}{basePath}"; + } + + basePath = basePath.Remove(basePath.Length - entry.Name.Length); + basePath = RebuildPathWithVars(basePath, homePath, stringType, literalPaths, out baseQuotesNeeded); + } + + var resultType = isContainer + ? CompletionResultType.ProviderContainer + : CompletionResultType.ProviderItem; + + bool leafQuotesNeeded; + var completionText = NewPathCompletionText( + basePath, + EscapePath(entryName, stringType, literalPaths, out leafQuotesNeeded), + stringType, + containsNestedExpressions: false, + forceQuotes: baseQuotesNeeded || leafQuotesNeeded, + addAmpersand: false); + results.Add(new CompletionResult(completionText, entryName, resultType, entry.FullName)); + } + } + + return results; + } + + /// + /// Helper method for generating path completion results standard providers that don't need any special treatment. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + private static List GetDefaultProviderResults( + CompletionContext context, + ProviderInfo provider, + Collection resolvedPaths, + string filterText, + bool containersOnly, + bool relativePaths, + bool literalPaths, + bool inputUsedHome, + string providerPrefix, + StringConstantType stringType) + { + string homePath = inputUsedHome && !string.IsNullOrEmpty(provider.Home) + ? provider.Home + : null; + + var pattern = WildcardPattern.Get(filterText, WildcardOptions.IgnoreCase); + var results = new List(); + + foreach (var item in resolvedPaths) + { + var pathInfo = (PathInfo)item.BaseObject; + string baseTooltip = pathInfo.ProviderPath.Equals(string.Empty, StringComparison.Ordinal) + ? pathInfo.Path + : pathInfo.ProviderPath; + if (baseTooltip[^1] is not '\\' and not '/' and not ':') + { + baseTooltip += provider.ItemSeparator; + } + + _ = context.Helper.CurrentPowerShell + .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-ChildItem") + .AddParameter("LiteralPath", pathInfo.Path); + + 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(childItemOutput.Count); + var childNameList = new List(childItemOutput.Count); + + if (hadErrors) + { + // Get-ChildItem failed to get some items (Access denied or something) + // Save relevant info and try again to get just the names. + foreach (dynamic child in childItemOutput) + { + childrenInfoTable.Add(GetChildNameFromPsObject(child, provider.ItemSeparator), child.PSIsContainer); + } + + _ = context.Helper.CurrentPowerShell + .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-ChildItem") + .AddParameter("LiteralPath", pathInfo.Path) + .AddParameter("Name"); + childItemOutput = context.Helper.ExecuteCurrentPowerShell(out _); + foreach (var child in childItemOutput) + { + var childName = (string)child.BaseObject; + childNameList.Add(childName); + } + } + else + { + foreach (dynamic child in childItemOutput) + { + var childName = GetChildNameFromPsObject(child, provider.ItemSeparator); + childrenInfoTable.Add(childName, child.PSIsContainer); + childNameList.Add(childName); + } + } + + if (childNameList.Count == 0) + { + continue; + } + + string basePath = providerPrefix.Length > 0 + ? string.Concat(providerPrefix, pathInfo.Path.AsSpan(providerPrefix.Length)) + : pathInfo.Path; + if (basePath[^1] is not '\\' and not '/' and not ':') + { + basePath += provider.ItemSeparator; + } + + if (relativePaths) + { + basePath = context.ExecutionContext.EngineSessionState.NormalizeRelativePath( + basePath + childNameList[0], context.ExecutionContext.SessionState.Internal.CurrentLocation.ProviderPath); + if (!basePath.StartsWith($"..{provider.ItemSeparator}", StringComparison.Ordinal)) + { + basePath = $".{provider.ItemSeparator}{basePath}"; + } + + basePath = basePath.Remove(basePath.Length - childNameList[0].Length); + } + + bool baseQuotesNeeded; + basePath = RebuildPathWithVars(basePath, homePath, stringType, literalPaths, out baseQuotesNeeded); + + foreach (var childName in childNameList) + { + if (!pattern.IsMatch(childName)) + { + continue; + } + + CompletionResultType resultType; + if (childrenInfoTable.TryGetValue(childName, out bool isContainer)) + { + if (containersOnly && !isContainer) + { + continue; + } + + resultType = isContainer + ? CompletionResultType.ProviderContainer + : CompletionResultType.ProviderItem; + } + else + { + resultType = CompletionResultType.Text; + } + + bool leafQuotesNeeded; + var completionText = NewPathCompletionText( + basePath, + EscapePath(childName, stringType, literalPaths, out leafQuotesNeeded), + stringType, + containsNestedExpressions: false, + forceQuotes: baseQuotesNeeded || leafQuotesNeeded, + addAmpersand: false); + results.Add(new CompletionResult(completionText, childName, resultType, baseTooltip + childName)); + } + } + + return results; + } + + private static string GetChildNameFromPsObject(dynamic psObject, char separator) + { + if (((PSObject)psObject).BaseObject is string result) + { + // The "Get-ChildItem" call for this provider returned a string that we assume is the child name. + // This is what the SCCM provider returns. + return result; + } + + string childName = psObject.PSChildName; + if (childName is not null) + { + return childName; + } + + // Some providers (Like the variable provider) don't include a PSChildName property + // so we get the child name from the path instead. + childName = psObject.PSPath ?? string.Empty; + int ProviderSeparatorIndex = childName.IndexOf("::", StringComparison.Ordinal); + childName = childName.Substring(ProviderSeparatorIndex + 2); + int indexOfName = childName.LastIndexOf(separator); + if (indexOfName == -1 || indexOfName + 1 == childName.Length) + { + return childName; + } + + return childName.Substring(indexOfName + 1); + } + + /// + /// Takes a path and rebuilds it with the specified variable replacements. + /// Also escapes special characters as needed. + /// + private static string RebuildPathWithVars( + string path, + string homePath, + StringConstantType stringType, + bool literalPath, + out bool quotesAreNeeded) + { + var sb = new StringBuilder(path.Length); + int homeIndex = string.IsNullOrEmpty(homePath) + ? -1 + : path.IndexOf(homePath, StringComparison.OrdinalIgnoreCase); + quotesAreNeeded = false; + bool useSingleQuoteEscapeRules = stringType is StringConstantType.SingleQuoted or StringConstantType.BareWord; + + 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 (i == homeIndex) + { + _ = sb.Append('~'); + i += homePath.Length - 1; + continue; + } +#endif + + EscapeCharIfNeeded(sb, path, i, stringType, literalPath, useSingleQuoteEscapeRules, ref quotesAreNeeded); + _ = sb.Append(path[i]); + } + + return sb.ToString(); + } + + private static string EscapePath(string path, StringConstantType stringType, bool literalPath, out bool quotesAreNeeded) + { + var sb = new StringBuilder(path.Length); + bool useSingleQuoteEscapeRules = stringType is StringConstantType.SingleQuoted or StringConstantType.BareWord; + quotesAreNeeded = false; + + for (int i = 0; i < path.Length; i++) + { + EscapeCharIfNeeded(sb, path, i, stringType, literalPath, useSingleQuoteEscapeRules, ref quotesAreNeeded); + _ = sb.Append(path[i]); + } + + return sb.ToString(); + } + + private static void EscapeCharIfNeeded( + StringBuilder sb, + string path, + int index, + StringConstantType stringType, + bool literalPath, + bool useSingleQuoteEscapeRules, + ref bool quotesAreNeeded) + { + switch (path[index]) + { + case '#': + case '-': + case '@': + if (index == 0 && stringType == StringConstantType.BareWord) + { + // Chars that would start a new token when used as the first char in a bareword argument. + quotesAreNeeded = true; + } + break; + + case ' ': + case ',': + case ';': + case '(': + case ')': + case '{': + case '}': + case '|': + case '&': + if (stringType == StringConstantType.BareWord) + { + // Chars that would start a new token when used anywhere in a bareword argument. + quotesAreNeeded = true; + } + break; + + case '[': + case ']': + if (!literalPath) + { + // Wildcard characters that need to be escaped. + int backtickCount; + if (useSingleQuoteEscapeRules) + { + backtickCount = 1; + } + else + { + backtickCount = sb[^1] == '`' ? 4 : 2; + } + + _ = sb.Append('`', backtickCount); + quotesAreNeeded = true; + } + break; + + case '`': + // Literal backtick needs to be escaped to not be treated as an escape character + if (useSingleQuoteEscapeRules) + { + if (!literalPath) + { + _ = sb.Append('`'); + } + } + else + { + int backtickCount = !literalPath && sb[^1] == '`' ? 3 : 1; + _ = sb.Append('`', backtickCount); + } + + if (stringType is StringConstantType.BareWord or StringConstantType.DoubleQuoted) + { + quotesAreNeeded = true; + } + break; + + case '$': + // $ needs to be escaped so following chars are not parsed as a variable/subexpression + if (!useSingleQuoteEscapeRules) + { + _ = sb.Append('`'); + } + + if (stringType is StringConstantType.BareWord or StringConstantType.DoubleQuoted) + { + quotesAreNeeded = true; + } + break; + + default: + if (useSingleQuoteEscapeRules) + { + // 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 (path[index].IsDoubleQuote()) + { + // Double quoted or bareword with variables input string. Need to escape double quotes. + _ = sb.Append('`'); + quotesAreNeeded = true; + } + break; + } + } + + private static string NewPathCompletionText(string parent, string leaf, StringConstantType stringType, bool containsNestedExpressions, bool forceQuotes, bool addAmpersand) + { + string result; + if (stringType == StringConstantType.SingleQuoted) + { + result = addAmpersand ? $"& '{parent}{leaf}'" : $"'{parent}{leaf}'"; + } + else if (stringType == StringConstantType.DoubleQuoted) + { + result = addAmpersand ? $"& \"{parent}{leaf}\"" : $"\"{parent}{leaf}\""; + } + else + { + if (forceQuotes) + { + if (containsNestedExpressions) + { + result = addAmpersand ? $"& \"{parent}{leaf}\"" : $"\"{parent}{leaf}\""; + } + else + { + result = addAmpersand ? $"& '{parent}{leaf}'" : $"'{parent}{leaf}'"; + } + } + else + { + result = string.Concat(parent, leaf); + } + } + + 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 + { + public string netname; + public int type; + public string remark; + } + + private static readonly System.IO.EnumerationOptions _enumerationOptions = new System.IO.EnumerationOptions + { + MatchCasing = MatchCasing.CaseInsensitive, + AttributesToSkip = 0 // Default is to skip Hidden and System files, so we clear this to retain existing behavior + }; + + internal static List GetFileShares(string machine, bool ignoreHidden) + { +#if UNIX + return new List(); +#else + nint shBuf = nint.Zero; + 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(); + if (result == Interop.Windows.ERROR_SUCCESS || result == Interop.Windows.ERROR_MORE_DATA) + { + for (int i = 0; i < numEntries; ++i) + { + nint curInfoPtr = shBuf + (Marshal.SizeOf() * i); + SHARE_INFO_1 shareInfo = Marshal.PtrToStructure(curInfoPtr); + + if ((shareInfo.type & Interop.Windows.STYPE_MASK) != Interop.Windows.STYPE_DISKTREE) + { + continue; + } + + if (ignoreHidden && shareInfo.netname.EndsWith('$')) + { + continue; + } + + shares.Add(shareInfo.netname); + } + } + + return shares; +#endif + } + + private static bool CheckFileExtension(string path, HashSet extension) + { + if (extension == null || extension.Count == 0) + return true; + + var ext = System.IO.Path.GetExtension(path); + return ext == null || extension.Contains(ext); + } + + #endregion Filenames + + #region Variable + + /// + /// + /// + /// + public static IEnumerable CompleteVariable(string variableName) + { + var runspace = Runspace.DefaultRunspace; + if (runspace == null) + { + // No runspace, just return no results. + return CommandCompletion.EmptyCompletionResult; + } + + var helper = new PowerShellExecutionHelper(PowerShell.Create(RunspaceMode.CurrentRunspace)); + var executionContext = helper.CurrentPowerShell.Runspace.ExecutionContext; + return CompleteVariable(new CompletionContext { WordToComplete = variableName, Helper = helper, ExecutionContext = executionContext }); + } + + private static readonly string[] s_variableScopes = new string[] { "Global:", "Local:", "Script:", "Private:", "Using:" }; + + private static readonly SearchValues s_charactersRequiringQuotes = SearchValues.Create("-`&@'\"#{}()$,;|<> .\\/ \t^"); + + private static bool ContainsCharactersRequiringQuotes(ReadOnlySpan text) + => text.ContainsAny(s_charactersRequiringQuotes); + + internal static List CompleteVariable(CompletionContext context) + { + HashSet hashedResults = new(StringComparer.OrdinalIgnoreCase); + List results = new(); + List 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; + if (lastAst is PropertyMemberAst || + (lastAst is not null && lastAst.Parent is ParameterAst parameter && parameter.DefaultValue != lastAst)) + { + // User is adding a new parameter or a class member, variable tab completion is not useful. + return results; + } + var prefix = variableAst != null && variableAst.Splatted ? "@" : "$"; + bool tokenAtCursorUsedBraces = context.TokenAtCursor is not null && context.TokenAtCursor.Text.StartsWith("${"); + + // Look for variables in the input (e.g. parameters, etc.) before checking session state - these + // variables might not exist in session state yet. + var wildcardPattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); + if (lastAst is not null) + { + Ast parent = lastAst.Parent; + var findVariablesVisitor = new FindVariablesVisitor + { + CompletionVariableAst = lastAst, + StopSearchOffset = lastAst.Extent.StartOffset, + Context = context.TypeInferenceContext + }; + while (parent != null) + { + if (parent is IParameterMetadataProvider) + { + findVariablesVisitor.Top = parent; + parent.Visit(findVariablesVisitor); + } + + parent = parent.Parent; + } + + foreach (string varName in findVariablesVisitor.FoundVariables) + { + if (!wildcardPattern.IsMatch(varName)) + { + continue; + } + + 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 && !ContainsCharactersRequiringQuotes(varName) + ? prefix + scopePrefix + varName + : prefix + "{" + scopePrefix + varName + "}"; + AddUniqueVariable(hashedResults, results, completionText, varName, toolTip); + } + } + + if (colon == -1) + { + var allVariables = context.ExecutionContext.SessionState.Internal.GetVariableTable(); + foreach (var key in allVariables.Keys) + { + if (wildcardPattern.IsMatch(key)) + { + var variable = allVariables[key]; + var name = variable.Name; + var value = variable.Value; + var toolTip = value is null + ? key + : StringUtil.Format("[{0}]${1}", ToStringCodeMethods.Type(value.GetType(), dropNamespaces: true), key); + if (!string.IsNullOrEmpty(variable.Description)) + { + toolTip += $" - {variable.Description}"; + } + + var completionText = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(name) + ? prefix + name + : prefix + "{" + name + "}"; + AddUniqueVariable(hashedResults, tempResults, completionText, key, toolTip); + } + } + + if (tempResults.Count > 0) + { + results.AddRange(tempResults.OrderBy(item => item.ListItemText, StringComparer.OrdinalIgnoreCase)); + tempResults.Clear(); + } + } + else + { + string pattern; + if (s_variableScopes.Contains(scopePrefix, StringComparer.OrdinalIgnoreCase)) + { + pattern = string.Concat("variable:", wordToComplete, "*"); + } + else + { + pattern = scopePrefix + wordToComplete + "*"; + } + + var powerShellExecutionHelper = context.Helper; + powerShellExecutionHelper + .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-Item").AddParameter("Path", pattern) + .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Utility\\Sort-Object").AddParameter("Property", "Name"); + + var psobjs = powerShellExecutionHelper.ExecuteCurrentPowerShell(out _); + + if (psobjs is not null) + { + foreach (dynamic psobj in psobjs) + { + var name = psobj.Name as string; + if (!string.IsNullOrEmpty(name)) + { + var tooltip = name; + var variable = PSObject.Base(psobj) as PSVariable; + if (variable != null) { - leaf += "*"; - var parentPath = Path.GetDirectoryName(providerPath); + var value = variable.Value; + if (value != null) + { + tooltip = StringUtil.Format("[{0}]${1}", + ToStringCodeMethods.Type(value.GetType(), + dropNamespaces: true), name); + } + + if (!string.IsNullOrEmpty(variable.Description)) + { + tooltip += $" - {variable.Description}"; + } + } + + var completedName = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(name) + ? prefix + scopePrefix + name + : prefix + "{" + scopePrefix + name + "}"; + AddUniqueVariable(hashedResults, results, completedName, name, tooltip); + } + } + } + } + + if (colon == -1 && "env".StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)) + { + var envVars = Environment.GetEnvironmentVariables(); + foreach (var key in envVars.Keys) + { + var name = "env:" + key; + var completedName = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(name) + ? prefix + name + : prefix + "{" + name + "}"; + AddUniqueVariable(hashedResults, tempResults, completedName, name, "[string]" + name); + } + + results.AddRange(tempResults.OrderBy(item => item.ListItemText, StringComparer.OrdinalIgnoreCase)); + tempResults.Clear(); + } + + if (colon == -1) + { + // 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 (wildcardPattern.IsMatch(specialVariable)) + { + var completedName = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(specialVariable) + ? prefix + specialVariable + : prefix + "{" + specialVariable + "}"; + + AddUniqueVariable(hashedResults, results, completedName, specialVariable, specialVariable); + } + } + + var allDrives = context.ExecutionContext.SessionState.Drive.GetAll(); + foreach (var drive in allDrives) + { + if (drive.Name.Length < 2 + || !wildcardPattern.IsMatch(drive.Name) + || !drive.Provider.ImplementingType.IsAssignableTo(typeof(IContentCmdletProvider))) + { + continue; + } + + var completedName = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(drive.Name) + ? prefix + drive.Name + ":" + : prefix + "{" + drive.Name + ":}"; + var tooltip = string.IsNullOrEmpty(drive.Description) + ? drive.Name + : drive.Description; + AddUniqueVariable(hashedResults, tempResults, completedName, drive.Name, tooltip); + } + + if (tempResults.Count > 0) + { + results.AddRange(tempResults.OrderBy(item => item.ListItemText, StringComparer.OrdinalIgnoreCase)); + } + + foreach (var scope in s_variableScopes) + { + if (wildcardPattern.IsMatch(scope)) + { + var completedName = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(scope) + ? prefix + scope + : prefix + "{" + scope + "}"; + AddUniqueVariable(hashedResults, results, completedName, scope, scope); + } + } + } - // ProviderPath should be absolute path for FileSystem entries - if (!string.IsNullOrEmpty(parentPath)) - { - string[] entries = null; - try - { - entries = Directory.GetFileSystemEntries(parentPath, leaf, _enumerationOptions); - } - catch (Exception) - { - } + return results; + } - if (entries != null) - { - hiddenFilesAreHandled = true; + private static void AddUniqueVariable(HashSet hashedResults, List results, string completionText, string listItemText, string tooltip) + { + if (hashedResults.Add(completionText)) + { + results.Add(new CompletionResult(completionText, listItemText, CompletionResultType.Variable, tooltip)); + } + } - if (entries.Length > notHiddenEntries.Count) - { - // Do the iteration only if there are hidden files - foreach (var entry in entries) - { - if (notHiddenEntries.Contains(entry)) - continue; - - var fileInfo = new FileInfo(entry); - try - { - if ((fileInfo.Attributes & FileAttributes.Hidden) != 0) - { - PSObject wrapper = PSObject.AsPSObject(entry); - psobjs.Add(wrapper); - } - } - catch - { - // do nothing if can't get file attributes - } - } - } - } - } - } - } + internal static readonly HashSet s_varModificationCommands = new(StringComparer.OrdinalIgnoreCase) + { + "New-Variable", + "nv", + "Set-Variable", + "set", + "sv" + }; - if (!hiddenFilesAreHandled) - { - powerShellExecutionHelper - .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-ChildItem") - .AddParameter("Path", wordToComplete + "*") - .AddParameter("Hidden", true); + internal static readonly string[] s_varModificationParameters = new string[] + { + "Name", + "Value" + }; - var hiddenItems = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown); - if (hiddenItems != null && hiddenItems.Count > 0) - { - foreach (var hiddenItem in hiddenItems) - { - psobjs.Add(hiddenItem); - } - } - } + internal static readonly string[] s_outVarParameters = new string[] + { + "ErrorVariable", + "ev", + "WarningVariable", + "wv", + "InformationVariable", + "iv", + "OutVariable", + "ov", + + }; + + internal static readonly string[] s_pipelineVariableParameters = new string[] + { + "PipelineVariable", + "pv" + }; + + internal static readonly HashSet s_localScopeCommandNames = new(StringComparer.OrdinalIgnoreCase) + { + "Microsoft.PowerShell.Core\\ForEach-Object", + "ForEach-Object", + "foreach", + "%", + "Microsoft.PowerShell.Core\\Where-Object", + "Where-Object", + "where", + "?", + "BeforeAll", + "BeforeEach" + }; + + private sealed class VariableInfo + { + internal PSTypeName LastDeclaredConstraint; + internal PSTypeName LastAssignedType; + } + + private sealed class FindVariablesVisitor : AstVisitor2 + { + internal Ast Top; + internal Ast CompletionVariableAst; + internal readonly List FoundVariables = new(); + internal readonly Dictionary VariableInfoTable = new(StringComparer.OrdinalIgnoreCase); + internal int StopSearchOffset; + internal TypeInferenceContext Context; + + private static PSTypeName GetInferredVarTypeFromAst(Ast ast) + { + PSTypeName type; + switch (ast) + { + case ConstantExpressionAst constant: + type = new PSTypeName(constant.StaticType); + break; + + case ExpandableStringExpressionAst: + type = new PSTypeName(typeof(string)); + break; + + case ConvertExpressionAst convertExpression: + type = new PSTypeName(convertExpression.Type.TypeName); + break; + + case HashtableAst: + type = new PSTypeName(typeof(Hashtable)); + break; + + case ArrayExpressionAst: + case ArrayLiteralAst: + type = new PSTypeName(typeof(object[])); + break; + + case ScriptBlockExpressionAst: + type = new PSTypeName(typeof(ScriptBlock)); + break; + + default: + type = null; + break; + } + + return type; + } + + private void SaveVariableInfo(string variableName, PSTypeName variableType, bool isConstraint) + { + if (VariableInfoTable.TryGetValue(variableName, out VariableInfo varInfo)) + { + if (isConstraint) + { + varInfo.LastDeclaredConstraint = variableType; } + else + { + varInfo.LastAssignedType = variableType; + } + } + else + { + varInfo = isConstraint + ? new VariableInfo() { LastDeclaredConstraint = variableType } + : new VariableInfo() { LastAssignedType = variableType }; + VariableInfoTable.Add(variableName, varInfo); + FoundVariables.Add(variableName); + } + } + + public override AstVisitAction DefaultVisit(Ast ast) + { + 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. + return ast is PipelineBaseAst && ast.Parent is DoUntilStatementAst or DoWhileStatementAst + ? AstVisitAction.SkipChildren + : AstVisitAction.StopVisit; + } - // Sorting the results by the path - var sortedPsobjs = psobjs.OrderBy(a => a, new ItemPathComparer()); + return AstVisitAction.Continue; + } + + public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) + { + if (assignmentStatementAst.Extent.StartOffset > StopSearchOffset) + { + return assignmentStatementAst.Parent is DoUntilStatementAst or DoWhileStatementAst ? + AstVisitAction.SkipChildren + : AstVisitAction.StopVisit; + } + + ProcessAssignmentLeftSide(assignmentStatementAst.Left, assignmentStatementAst.Right); + return AstVisitAction.Continue; + } - foreach (PSObject psobj in sortedPsobjs) + private void ProcessAssignmentLeftSide(ExpressionAst left, StatementAst right) + { + if (left is AttributedExpressionAst attributedExpression) + { + var firstConvertExpression = attributedExpression as ConvertExpressionAst; + ExpressionAst child = attributedExpression.Child; + while (child is AttributedExpressionAst attributeChild) { - object baseObj = PSObject.Base(psobj); - string path = null, providerPath = null; + 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; + } - // Get the path, the PSObject could be: - // 1. a PathInfo object -- results of Resolve-Path - // 2. a FileSystemInfo Object -- results of Get-ChildItem - // 3. a string -- the path results return by the direct .NET API invocation - var baseObjAsPathInfo = baseObj as PathInfo; - if (baseObjAsPathInfo != null) + if (child is VariableExpressionAst variableExpression) + { + if (variableExpression == CompletionVariableAst || s_specialVariablesCache.Value.Contains(variableExpression.VariablePath.UserPath)) { - path = baseObjAsPathInfo.Path; - providerPath = baseObjAsPathInfo.ProviderPath; + return; } - else if (baseObj is FileSystemInfo) + + if (firstConvertExpression is not null) { - // The target provider is the FileSystem - dynamic dirResult = psobj; - providerPath = dirResult.FullName; - path = wordContainsProviderId ? dirResult.PSPath : providerPath; + SaveVariableInfo(variableExpression.VariablePath.UnqualifiedPath, new PSTypeName(firstConvertExpression.Type.TypeName), isConstraint: true); } else { - var baseObjAsString = baseObj as string; - if (baseObjAsString != null) - { - // The target provider is the FileSystem - providerPath = baseObjAsString; - path = wordContainsProviderId - ? FileSystemProvider.ProviderName + "::" + baseObjAsString - : providerPath; - } + PSTypeName lastAssignedType = right is CommandExpressionAst commandExpression + ? GetInferredVarTypeFromAst(commandExpression.Expression) + : null; + SaveVariableInfo(variableExpression.VariablePath.UnqualifiedPath, lastAssignedType, isConstraint: false); } + } + } + else if (left is VariableExpressionAst variableExpression) + { + if (variableExpression == CompletionVariableAst || s_specialVariablesCache.Value.Contains(variableExpression.VariablePath.UserPath)) + { + return; + } - if (path == null) continue; - if (isFileSystem && providerPath == null) continue; + PSTypeName lastAssignedType; + if (right is CommandExpressionAst commandExpression) + { + lastAssignedType = GetInferredVarTypeFromAst(commandExpression.Expression); + } + else + { + lastAssignedType = null; + } - string completionText; - if (relativePaths) + 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); + } + } + } + + public override AstVisitAction VisitCommand(CommandAst commandAst) + { + if (commandAst.Extent.StartOffset > StopSearchOffset) + { + return AstVisitAction.StopVisit; + } + + var commandName = commandAst.GetCommandName(); + if (commandName is not null && s_varModificationCommands.Contains(commandName)) + { + StaticBindingResult bindingResult = StaticParameterBinder.BindCommand(commandAst, resolve: false, s_varModificationParameters); + if (bindingResult is not null + && bindingResult.BoundParameters.TryGetValue("Name", out ParameterBindingResult variableName)) + { + var nameValue = variableName.ConstantValue as string; + if (nameValue is not null) { - try + PSTypeName variableType; + if (bindingResult.BoundParameters.TryGetValue("Value", out ParameterBindingResult variableValue)) { - var sessionStateInternal = executionContext.EngineSessionState; - completionText = sessionStateInternal.NormalizeRelativePath(path, sessionStateInternal.CurrentLocation.ProviderPath); - string parentDirectory = ".." + StringLiterals.DefaultPathSeparator; - if (!completionText.StartsWith(parentDirectory, StringComparison.Ordinal)) - completionText = Path.Combine(".", completionText); + variableType = GetInferredVarTypeFromAst(variableValue.Value); } - catch (Exception) + else { - // The object at the specified path is not accessable, such as c:\hiberfil.sys (for hibernation) or c:\pagefile.sys (for paging) - // We ignore those files - continue; + variableType = null; } - } - else - { - completionText = path; - } - if (ProviderSpecified(completionText) && !wordContainsProviderId) - { - // Remove the provider id from the path: cd \\scratch2\scratch\dongbw - var index = completionText.IndexOf(':'); - completionText = completionText.Substring(index + 2); + SaveVariableInfo(nameValue, variableType, isConstraint: false); } + } + } - if (CompletionRequiresQuotes(completionText, !useLiteralPath)) + var bindResult = StaticParameterBinder.BindCommand(commandAst, resolve: false); + if (bindResult is not null) + { + foreach (var parameterName in s_outVarParameters) + { + if (bindResult.BoundParameters.TryGetValue(parameterName, out ParameterBindingResult outVarBind)) { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - { - completionText = completionText.Replace("'", "''"); - } - else + var varName = outVarBind.ConstantValue as string; + if (varName is not null) { - // When double quote is in use, we have to escape the backtip and '$' even when using literal path - // Get-Content -LiteralPath ".\a``g.txt" - completionText = completionText.Replace("`", "``"); - completionText = completionText.Replace("$", "`$"); + SaveVariableInfo(varName, new PSTypeName(typeof(ArrayList)), isConstraint: false); } + } + } - if (!useLiteralPath) + if (commandAst.Parent is PipelineAst pipeline && pipeline.Extent.EndOffset > CompletionVariableAst.Extent.StartOffset) + { + foreach (var parameterName in s_pipelineVariableParameters) + { + if (bindResult.BoundParameters.TryGetValue(parameterName, out ParameterBindingResult pipeVarBind)) { - if (quoteInUse == "'") - { - completionText = completionText.Replace("[", "`["); - completionText = completionText.Replace("]", "`]"); - } - else + var varName = pipeVarBind.ConstantValue as string; + if (varName is not null) { - completionText = completionText.Replace("[", "``["); - completionText = completionText.Replace("]", "``]"); + var inferredTypes = AstTypeInference.InferTypeOf(commandAst, Context, TypeInferenceRuntimePermissions.AllowSafeEval); + PSTypeName varType = inferredTypes.Count == 0 + ? null + : inferredTypes[0]; + SaveVariableInfo(varName, varType, isConstraint: false); } } - - completionText = quoteInUse + completionText + quoteInUse; - } - else if (quote != string.Empty) - { - completionText = quote + completionText + quote; } + } + } - if (isFileSystem) + 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) { - // Use .NET APIs directly to reduce the time overhead - var isContainer = Directory.Exists(providerPath); - if (containerOnly && !isContainer) - continue; + case RedirectionStream.Error: + varType = new PSTypeName(typeof(ErrorRecord)); + break; - if (!containerOnly && !isContainer && !CheckFileExtension(providerPath, extension)) - continue; + case RedirectionStream.Warning: + varType = new PSTypeName(typeof(WarningRecord)); + break; - string tooltip = providerPath, listItemText = Path.GetFileName(providerPath); - results.Add(new CompletionResult(completionText, listItemText, - isContainer ? CompletionResultType.ProviderContainer : CompletionResultType.ProviderItem, - tooltip)); - } - else - { - powerShellExecutionHelper - .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-Item") - .AddParameter("LiteralPath", path); - var items = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown); - if (items != null && items.Count == 1) - { - dynamic item = items[0]; - var isContainer = LanguagePrimitives.ConvertTo(item.PSIsContainer); - - if (containerOnly && !isContainer) - continue; - - powerShellExecutionHelper - .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Convert-Path") - .AddParameter("LiteralPath", item.PSPath); - var tooltips = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown); - string tooltip = null, listItemText = item.PSChildName; - if (tooltips != null && tooltips.Count == 1) - { - tooltip = PSObject.Base(tooltips[0]) as string; - } + case RedirectionStream.Verbose: + varType = new PSTypeName(typeof(VerboseRecord)); + break; - if (string.IsNullOrEmpty(listItemText)) - { - // For provider items that don't have PSChildName values, such as variable::error - listItemText = item.Name; - } + case RedirectionStream.Debug: + varType = new PSTypeName(typeof(DebugRecord)); + break; - results.Add(new CompletionResult(completionText, listItemText, - isContainer ? CompletionResultType.ProviderContainer : CompletionResultType.ProviderItem, - tooltip ?? path)); - } - else - { - // We can get here when get-item fails, perhaps due an acl or whatever. - results.Add(new CompletionResult(completionText)); - } + case RedirectionStream.Information: + varType = new PSTypeName(typeof(InformationRecord)); + break; + + default: + varType = null; + break; } + + SaveVariableInfo(varName, varType, isConstraint: false); } } + + return AstVisitAction.Continue; } - return results; - } + public override AstVisitAction VisitParameter(ParameterAst parameterAst) + { + if (parameterAst.Extent.StartOffset > StopSearchOffset) + { + return AstVisitAction.StopVisit; + } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - private struct SHARE_INFO_1 - { - public string netname; - public int type; - public string remark; - } + VariableExpressionAst variableExpression = parameterAst.Name; + if (variableExpression == CompletionVariableAst) + { + return AstVisitAction.Continue; + } - private const int MAX_PREFERRED_LENGTH = -1; - private const int NERR_Success = 0; - private const int ERROR_MORE_DATA = 234; - private const int STYPE_DISKTREE = 0; - private const int STYPE_MASK = 0x000000FF; + SaveVariableInfo(variableExpression.VariablePath.UnqualifiedPath, new PSTypeName(parameterAst.StaticType), isConstraint: true); - private static readonly System.IO.EnumerationOptions _enumerationOptions = new System.IO.EnumerationOptions - { - MatchCasing = MatchCasing.CaseInsensitive, - AttributesToSkip = 0 // Default is to skip Hidden and System files, so we clear this to retain existing behavior - }; + return AstVisitAction.Continue; + } - [DllImport("Netapi32.dll", CharSet = CharSet.Unicode)] - private static extern int NetShareEnum(string serverName, int level, out IntPtr bufptr, int prefMaxLen, - out uint entriesRead, out uint totalEntries, ref uint resumeHandle); + public override AstVisitAction VisitForEachStatement(ForEachStatementAst forEachStatementAst) + { + if (forEachStatementAst.Extent.StartOffset > StopSearchOffset || forEachStatementAst.Variable == CompletionVariableAst) + { + return AstVisitAction.StopVisit; + } - internal static List GetFileShares(string machine, bool ignoreHidden) - { -#if UNIX - return new List(); -#else - IntPtr shBuf; - uint numEntries; - uint totalEntries; - uint resumeHandle = 0; - int result = NetShareEnum(machine, 1, out shBuf, - MAX_PREFERRED_LENGTH, out numEntries, out totalEntries, - ref resumeHandle); + SaveVariableInfo(forEachStatementAst.Variable.VariablePath.UnqualifiedPath, variableType: null, isConstraint: false); + return AstVisitAction.Continue; + } - var shares = new List(); - if (result == NERR_Success || result == ERROR_MORE_DATA) + public override AstVisitAction VisitAttribute(AttributeAst attributeAst) { - for (int i = 0; i < numEntries; ++i) + // Attributes can't assign values to variables so they aren't interesting. + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) + { + return functionDefinitionAst != Top ? AstVisitAction.SkipChildren : AstVisitAction.Continue; + } + + public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst) + { + if (scriptBlockExpressionAst == Top) { - IntPtr curInfoPtr = (IntPtr)((long)shBuf + (Marshal.SizeOf() * i)); - SHARE_INFO_1 shareInfo = Marshal.PtrToStructure(curInfoPtr); + return AstVisitAction.Continue; + } - if ((shareInfo.type & STYPE_MASK) != STYPE_DISKTREE) - continue; - if (ignoreHidden && shareInfo.netname.EndsWith('$')) - continue; - shares.Add(shareInfo.netname); + Ast parent = scriptBlockExpressionAst.Parent; + // This loop checks if the scriptblock is used as a command, or an argument for a command, eg: ForEach-Object -Process {$Var1 = "Hello"}, {Var2 = $true} + while (true) + { + if (parent is CommandAst cmdAst) + { + string cmdName = cmdAst.GetCommandName(); + return s_localScopeCommandNames.Contains(cmdName) + || (cmdAst.CommandElements[0] is ScriptBlockExpressionAst && cmdAst.InvocationOperator == TokenKind.Dot) + ? AstVisitAction.Continue + : AstVisitAction.SkipChildren; + } + + if (parent is not CommandExpressionAst and not PipelineAst and not StatementBlockAst and not ArrayExpressionAst and not ArrayLiteralAst) + { + return AstVisitAction.SkipChildren; + } + + parent = parent.Parent; + } + } + + public override AstVisitAction VisitDataStatement(DataStatementAst dataStatementAst) + { + if (dataStatementAst.Extent.StartOffset >= StopSearchOffset) + { + return AstVisitAction.StopVisit; + } + + if (dataStatementAst.Variable is not null) + { + SaveVariableInfo(dataStatementAst.Variable, variableType: null, isConstraint: false); } - } - return shares; -#endif + return AstVisitAction.SkipChildren; + } } - private static bool CheckFileExtension(string path, HashSet extension) + private static readonly Lazy> s_specialVariablesCache = new(BuildSpecialVariablesCache); + + private static SortedSet BuildSpecialVariablesCache() { - if (extension == null || extension.Count == 0) - return true; + var result = new SortedSet(StringComparer.OrdinalIgnoreCase); + foreach (var member in typeof(SpecialVariables).GetFields(BindingFlags.NonPublic | BindingFlags.Static)) + { + if (member.FieldType.Equals(typeof(string))) + { + result.Add((string)member.GetValue(null)); + } + } - var ext = System.IO.Path.GetExtension(path); - return ext == null || extension.Contains(ext); + return result; } - #endregion Filenames - - #region Variable - - /// - /// - /// - /// - public static IEnumerable CompleteVariable(string variableName) + internal static PSTypeName GetLastDeclaredTypeConstraint(VariableExpressionAst variableAst, TypeInferenceContext typeInferenceContext) { - var runspace = Runspace.DefaultRunspace; - if (runspace == null) + Ast parent = variableAst.Parent; + var findVariablesVisitor = new FindVariablesVisitor() { - // No runspace, just return no results. - return CommandCompletion.EmptyCompletionResult; + 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; } - var helper = new PowerShellExecutionHelper(PowerShell.Create(RunspaceMode.CurrentRunspace)); - var executionContext = helper.CurrentPowerShell.Runspace.ExecutionContext; - return CompleteVariable(new CompletionContext { WordToComplete = variableName, Helper = helper, ExecutionContext = executionContext }); + return null; } - private static readonly string[] s_variableScopes = new string[] { "Global:", "Local:", "Script:", "Private:" }; + #endregion Variables - private static readonly char[] s_charactersRequiringQuotes = new char[] { - '-', '`', '&', '@', '\'', '"', '#', '{', '}', '(', ')', '$', ',', ';', '|', '<', '>', ' ', '.', '\\', '/', '\t', '^', - }; + #region Comments - internal static List CompleteVariable(CompletionContext context) + internal static List CompleteComment(CompletionContext context, ref int replacementIndex, ref int replacementLength) { - HashSet hashedResults = new HashSet(StringComparer.OrdinalIgnoreCase); - List results = new List(); + if (context.WordToComplete.StartsWith("<#", StringComparison.Ordinal)) + { + return CompleteCommentHelp(context, ref replacementIndex, ref replacementLength); + } - var wordToComplete = context.WordToComplete; - var colon = wordToComplete.IndexOf(':'); + // Complete #requires statements + if (context.WordToComplete.StartsWith("#requires ", StringComparison.OrdinalIgnoreCase)) + { + return CompleteRequires(context, ref replacementIndex, ref replacementLength); + } - var lastAst = context.RelatedAsts?.Last(); - var variableAst = lastAst as VariableExpressionAst; - var prefix = variableAst != null && variableAst.Splatted ? "@" : "$"; + var results = new List(); - // Look for variables in the input (e.g. parameters, etc.) before checking session state - these - // variables might not exist in session state yet. - var wildcardPattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); - if (lastAst != null) + // Complete the history entries + Match matchResult = Regex.Match(context.WordToComplete, @"^#([\w\-]*)$"); + if (!matchResult.Success) { - Ast parent = lastAst.Parent; - var findVariablesVisitor = new FindVariablesVisitor { CompletionVariableAst = lastAst }; - while (parent != null) - { - if (parent is IParameterMetadataProvider) - { - findVariablesVisitor.Top = parent; - parent.Visit(findVariablesVisitor); - } + return results; + } - parent = parent.Parent; - } + string wordToComplete = matchResult.Groups[1].Value; + Collection psobjs; - foreach (Tuple varAst in findVariablesVisitor.VariableSources) - { - Ast astTarget = null; - string userPath = null; + int entryId; + if (Regex.IsMatch(wordToComplete, @"^[0-9]+$") && LanguagePrimitives.TryConvertTo(wordToComplete, out entryId)) + { + context.Helper.AddCommandWithPreferenceSetting("Get-History", typeof(GetHistoryCommand)).AddParameter("Id", entryId); + psobjs = context.Helper.ExecuteCurrentPowerShell(out _); - VariableExpressionAst variableDefinitionAst = varAst.Item2 as VariableExpressionAst; - if (variableDefinitionAst != null) - { - userPath = varAst.Item1; - astTarget = varAst.Item2.Parent; - } - else + if (psobjs != null && psobjs.Count == 1) + { + var historyInfo = PSObject.Base(psobjs[0]) as HistoryInfo; + if (historyInfo != null) { - CommandAst commandParameterAst = varAst.Item2 as CommandAst; - if (commandParameterAst != null) + var commandLine = historyInfo.CommandLine; + if (!string.IsNullOrEmpty(commandLine)) { - userPath = varAst.Item1; - astTarget = varAst.Item2; + // var tooltip = "Id: " + historyInfo.Id + "\n" + + // "ExecutionStatus: " + historyInfo.ExecutionStatus + "\n" + + // "StartExecutionTime: " + historyInfo.StartExecutionTime + "\n" + + // "EndExecutionTime: " + historyInfo.EndExecutionTime + "\n"; + // Use the commandLine as the Tooltip in case the commandLine is multiple lines of scripts + results.Add(new CompletionResult(commandLine, commandLine, CompletionResultType.History, commandLine)); } } + } + + return results; + } + + wordToComplete = "*" + wordToComplete + "*"; + context.Helper.AddCommandWithPreferenceSetting("Get-History", typeof(GetHistoryCommand)); + + psobjs = context.Helper.ExecuteCurrentPowerShell(out _); + var pattern = WildcardPattern.Get(wordToComplete, WildcardOptions.IgnoreCase); + + if (psobjs != null) + { + for (int index = psobjs.Count - 1; index >= 0; index--) + { + var psobj = psobjs[index]; + if (!(PSObject.Base(psobj) is HistoryInfo historyInfo)) continue; - if (string.IsNullOrEmpty(userPath)) + var commandLine = historyInfo.CommandLine; + if (!string.IsNullOrEmpty(commandLine) && pattern.IsMatch(commandLine)) { - Diagnostics.Assert(false, "Found a variable source but it was an unknown AST type."); + // var tooltip = "Id: " + historyInfo.Id + "\n" + + // "ExecutionStatus: " + historyInfo.ExecutionStatus + "\n" + + // "StartExecutionTime: " + historyInfo.StartExecutionTime + "\n" + + // "EndExecutionTime: " + historyInfo.EndExecutionTime + "\n"; + // Use the commandLine as the Tooltip in case the commandLine is multiple lines of scripts + results.Add(new CompletionResult(commandLine, commandLine, CompletionResultType.History, commandLine)); } + } + } - if (wildcardPattern.IsMatch(userPath)) - { - var completedName = (userPath.IndexOfAny(s_charactersRequiringQuotes) == -1) - ? prefix + userPath - : prefix + "{" + userPath + "}"; - var tooltip = userPath; - var ast = astTarget; + return results; + } - while (ast != null) - { - var parameterAst = ast as ParameterAst; - if (parameterAst != null) - { - var typeConstraint = parameterAst.Attributes.OfType().FirstOrDefault(); - if (typeConstraint != null) - { - tooltip = StringUtil.Format("{0}${1}", typeConstraint.Extent.Text, userPath); - } + private static List CompleteRequires(CompletionContext context, ref int replacementIndex, ref int replacementLength) + { + var results = new List(); - break; - } + int cursorIndex = context.CursorPosition.ColumnNumber - 1; + string lineToCursor = context.CursorPosition.Line.Substring(0, cursorIndex); - var assignmentAst = ast.Parent as AssignmentStatementAst; - if (assignmentAst != null) - { - if (assignmentAst.Left == ast) - { - tooltip = ast.Extent.Text; - } + // RunAsAdministrator must be the last parameter in a Requires statement so no completion if the cursor is after the parameter. + if (lineToCursor.Contains(" -RunAsAdministrator", StringComparison.OrdinalIgnoreCase)) + { + return results; + } - break; - } + // Regex to find parameter like " -Parameter1" or " -" + MatchCollection hashtableKeyMatches = Regex.Matches(lineToCursor, @"\s+-([A-Za-z]+|$)"); + if (hashtableKeyMatches.Count == 0) + { + return results; + } - var commandAst = ast as CommandAst; - if (commandAst != null) - { - PSTypeName discoveredType = AstTypeInference.InferTypeOf(ast, context.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval).FirstOrDefault(); - if (discoveredType != null) - { - tooltip = StringUtil.Format("[{0}]${1}", discoveredType.Name, userPath); - } + Group currentParameterMatch = hashtableKeyMatches[^1].Groups[1]; - break; - } + // Complete the parameter if the cursor is at a parameter + if (currentParameterMatch.Index + currentParameterMatch.Length == cursorIndex) + { + string currentParameterPrefix = currentParameterMatch.Value; - ast = ast.Parent; - } + replacementIndex = context.CursorPosition.Offset - currentParameterPrefix.Length; + replacementLength = currentParameterPrefix.Length; - AddUniqueVariable(hashedResults, results, completedName, userPath, tooltip); + // 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 (string parameter in s_requiresParameters) + { + if (parameter.StartsWith(currentParameterPrefix, StringComparison.OrdinalIgnoreCase) + && !context.CursorPosition.Line.Contains($" -{parameter}", StringComparison.OrdinalIgnoreCase)) + { + string toolTip = GetRequiresParametersToolTip(parameter); + results.Add(new CompletionResult(parameter, parameter, CompletionResultType.ParameterName, toolTip)); } } + + return results; } - string pattern; - string provider; - if (colon == -1) + // Regex to find parameter values (any text that appears after various delimiters) + hashtableKeyMatches = Regex.Matches(lineToCursor, @"(\s+|,|;|{|\""|'|=)(\w+|$)"); + string currentValue; + if (hashtableKeyMatches.Count == 0) { - pattern = "variable:" + wordToComplete + "*"; - provider = string.Empty; + currentValue = string.Empty; } else { - provider = wordToComplete.Substring(0, colon + 1); - if (s_variableScopes.Contains(provider, StringComparer.OrdinalIgnoreCase)) + currentValue = hashtableKeyMatches[^1].Groups[2].Value; + } + + replacementIndex = context.CursorPosition.Offset - currentValue.Length; + replacementLength = currentValue.Length; + + // Complete PSEdition parameter values + if (currentParameterMatch.Value.Equals("PSEdition", StringComparison.OrdinalIgnoreCase)) + { + foreach (string psEditionEntry in s_requiresPSEditions) { - pattern = string.Concat("variable:", wordToComplete.AsSpan(colon + 1), "*"); + if (psEditionEntry.StartsWith(currentValue, StringComparison.OrdinalIgnoreCase)) + { + string toolTip = GetRequiresPsEditionsToolTip(psEditionEntry); + results.Add(new CompletionResult(psEditionEntry, psEditionEntry, CompletionResultType.ParameterValue, toolTip)); + } } - else + + return results; + } + + // Complete Modules module specification values + if (currentParameterMatch.Value.Equals("Modules", StringComparison.OrdinalIgnoreCase)) + { + int hashtableStart = lineToCursor.LastIndexOf("@{"); + int hashtableEnd = lineToCursor.LastIndexOf('}'); + + bool insideHashtable = hashtableStart != -1 && (hashtableEnd == -1 || hashtableEnd < hashtableStart); + + // If not inside a hashtable, try to complete a module simple name + if (!insideHashtable) { - pattern = wordToComplete + "*"; + context.WordToComplete = currentValue; + return CompleteModuleName(context, true); } - } - var powerShellExecutionHelper = context.Helper; - powerShellExecutionHelper - .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-Item").AddParameter("Path", pattern) - .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Utility\\Sort-Object").AddParameter("Property", "Name"); + string hashtableString = lineToCursor.Substring(hashtableStart); - Exception exceptionThrown; - var psobjs = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown); - if (psobjs != null) - { - foreach (dynamic psobj in psobjs) + // Regex to find hashtable keys with or without quotes + 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(s_requiresModuleSpecKeys); + bool sawModuleNameLast = false; + foreach (Match existingHashtableKeyMatch in hashtableKeyMatches) { - var name = psobj.Name as string; - if (!string.IsNullOrEmpty(name)) + string existingHashtableKey = existingHashtableKeyMatch.Value.TrimStart(s_hashtableKeyPrefixes); + + if (string.IsNullOrEmpty(existingHashtableKey)) { - var tooltip = name; - var variable = PSObject.Base(psobj) as PSVariable; - if (variable != null) - { - var value = variable.Value; - if (value != null) - { - tooltip = StringUtil.Format("[{0}]${1}", - ToStringCodeMethods.Type(value.GetType(), - dropNamespaces: true), name); - } - } + continue; + } + + // Remove the existing key we just saw + moduleSpecKeysToComplete.Remove(existingHashtableKey); + + // We need to remember later if we saw "ModuleName" as the last hashtable key, for completions + if (sawModuleNameLast = existingHashtableKey.Equals("ModuleName", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + // "RequiredVersion" is mutually exclusive with "ModuleVersion" and "MaximumVersion" + if (existingHashtableKey.Equals("ModuleVersion", StringComparison.OrdinalIgnoreCase) + || existingHashtableKey.Equals("MaximumVersion", StringComparison.OrdinalIgnoreCase)) + { + moduleSpecKeysToComplete.Remove("RequiredVersion"); + continue; + } - var completedName = (name.IndexOfAny(s_charactersRequiringQuotes) == -1) - ? prefix + provider + name - : prefix + "{" + provider + name + "}"; - AddUniqueVariable(hashedResults, results, completedName, name, tooltip); + if (existingHashtableKey.Equals("RequiredVersion", StringComparison.OrdinalIgnoreCase)) + { + moduleSpecKeysToComplete.Remove("ModuleVersion"); + moduleSpecKeysToComplete.Remove("MaximumVersion"); + continue; } } - } - if (colon == -1 && "env".StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)) - { - powerShellExecutionHelper - .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-Item").AddParameter("Path", "env:*") - .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Utility\\Sort-Object").AddParameter("Property", "Key"); + Group lastHashtableKeyPrefixGroup = hashtableKeyMatches[^1].Groups[0]; - psobjs = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown); - if (psobjs != null) + // If we're not completing a key for the hashtable, try to complete module names, but nothing else + bool completingHashtableKey = lastHashtableKeyPrefixGroup.Index + lastHashtableKeyPrefixGroup.Length == hashtableString.Length; + if (!completingHashtableKey) { - foreach (dynamic psobj in psobjs) + if (sawModuleNameLast) { - var name = psobj.Name as string; - if (!string.IsNullOrEmpty(name)) - { - name = "env:" + name; - var completedName = (name.IndexOfAny(s_charactersRequiringQuotes) == -1) - ? prefix + name - : prefix + "{" + name + "}"; - AddUniqueVariable(hashedResults, results, completedName, name, "[string]" + name); - } + context.WordToComplete = currentValue; + return CompleteModuleName(context, true); + } + + return results; + } + + // Now try to complete hashtable keys + foreach (string moduleSpecKey in moduleSpecKeysToComplete) + { + if (moduleSpecKey.StartsWith(currentValue, StringComparison.OrdinalIgnoreCase)) + { + string toolTip = GetRequiresModuleSpecKeysToolTip(moduleSpecKey); + results.Add(new CompletionResult(moduleSpecKey, moduleSpecKey, CompletionResultType.ParameterValue, toolTip)); } } } - // 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) + return results; + } + + 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[] + { + "Core", + "Desktop" + }; + + private static string GetRequiresPsEditionsToolTip(string name) => name switch + { + "Core" => TabCompletionStrings.RequiresPsEditionCoreDescription, + "Desktop" => TabCompletionStrings.RequiresPsEditionDesktopDescription, + _ => string.Empty + }; + + private static readonly string[] s_requiresModuleSpecKeys = new string[] + { + "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[] + { + '@', + '{', + ';', + '"', + '\'', + ' ', + }; + + private static List CompleteCommentHelp(CompletionContext context, ref int replacementIndex, ref int replacementLength) + { + // Finds comment keywords like ".DESCRIPTION" + MatchCollection usedKeywords = Regex.Matches(context.TokenAtCursor.Text, @"(?<=^\s*\.)\w*", RegexOptions.Multiline); + if (usedKeywords.Count == 0) + { + return null; + } + + // Last keyword at or before the cursor + Match lineKeyword = null; + for (int i = usedKeywords.Count - 1; i >= 0; i--) { - if (wildcardPattern.IsMatch(specialVariable)) + Match keyword = usedKeywords[i]; + if (context.CursorPosition.Offset >= keyword.Index + context.TokenAtCursor.Extent.StartOffset) { - var completedName = (specialVariable.IndexOfAny(s_charactersRequiringQuotes) == -1) - ? prefix + specialVariable - : prefix + "{" + specialVariable + "}"; - - AddUniqueVariable(hashedResults, results, completedName, specialVariable, specialVariable); + lineKeyword = keyword; + break; } } - if (colon == -1) + if (lineKeyword is null) { - // If no drive was specified, then look for matching drives/scopes - pattern = wordToComplete + "*"; - powerShellExecutionHelper - .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-PSDrive").AddParameter("Name", pattern) - .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Utility\\Sort-Object").AddParameter("Property", "Name"); - psobjs = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown); - if (psobjs != null) + return null; + } + + // Cursor is within or at the start/end of the keyword + if (context.CursorPosition.Offset <= lineKeyword.Index + lineKeyword.Length + context.TokenAtCursor.Extent.StartOffset) + { + replacementIndex = context.TokenAtCursor.Extent.StartOffset + lineKeyword.Index; + replacementLength = lineKeyword.Value.Length; + + var validKeywords = new HashSet(s_commentHelpKeywords, StringComparer.OrdinalIgnoreCase); + foreach (Match keyword in usedKeywords) { - foreach (var psobj in psobjs) + if (keyword == lineKeyword || s_commentHelpAllowedDuplicateKeywords.Contains(keyword.Value)) { - var driveInfo = PSObject.Base(psobj) as PSDriveInfo; - if (driveInfo != null) - { - var name = driveInfo.Name; - if (name != null && !string.IsNullOrWhiteSpace(name) && name.Length > 1) - { - var completedName = (name.IndexOfAny(s_charactersRequiringQuotes) == -1) - ? prefix + name + ":" - : prefix + "{" + name + ":}"; - - var tooltip = string.IsNullOrEmpty(driveInfo.Description) ? name : driveInfo.Description; - AddUniqueVariable(hashedResults, results, completedName, name, tooltip); - } - } + continue; } + + validKeywords.Remove(keyword.Value); } - var scopePattern = WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase); - foreach (var scope in s_variableScopes) + var result = new List(); + foreach (string keyword in validKeywords) { - if (scopePattern.IsMatch(scope)) + if (keyword.StartsWith(lineKeyword.Value, StringComparison.OrdinalIgnoreCase)) { - var completedName = (scope.IndexOfAny(s_charactersRequiringQuotes) == -1) - ? prefix + scope - : prefix + "{" + scope + "}"; - AddUniqueVariable(hashedResults, results, completedName, scope, scope); + string toolTip = GetCommentHelpKeywordsToolTip(keyword); + result.Add(new CompletionResult(keyword, keyword, CompletionResultType.Keyword, toolTip)); } } + + return result.Count > 0 ? result : null; } - return results; - } + // Finds the argument for the keyword (any characters following the keyword, ignoring leading/trailing whitespace). For example "C:\New folder" + Match keywordArgument = Regex.Match(context.CursorPosition.Line, @"(?<=^\s*\.\w+\s+)\S.*(?<=\S)"); + int lineStartIndex = lineKeyword.Index - context.CursorPosition.Line.IndexOf(lineKeyword.Value) + context.TokenAtCursor.Extent.StartOffset; + int argumentIndex = keywordArgument.Success ? keywordArgument.Index : context.CursorPosition.ColumnNumber - 1; - private static void AddUniqueVariable(HashSet hashedResults, List results, string completionText, string listItemText, string tooltip) - { - if (!hashedResults.Contains(completionText)) + replacementIndex = lineStartIndex + argumentIndex; + replacementLength = keywordArgument.Value.Length; + + if (lineKeyword.Value.Equals("PARAMETER", StringComparison.OrdinalIgnoreCase)) { - hashedResults.Add(completionText); - results.Add(new CompletionResult(completionText, listItemText, CompletionResultType.Variable, tooltip)); + return CompleteCommentParameterValue(context, keywordArgument.Value); } - } - - private class FindVariablesVisitor : AstVisitor - { - internal Ast Top; - internal Ast CompletionVariableAst; - internal readonly List> VariableSources = new List>(); - public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) + if (lineKeyword.Value.Equals("FORWARDHELPTARGETNAME", StringComparison.OrdinalIgnoreCase)) { - if (variableExpressionAst != CompletionVariableAst) - { - VariableSources.Add(new Tuple(variableExpressionAst.VariablePath.UserPath, variableExpressionAst)); - } - - return AstVisitAction.Continue; + var result = new List(CompleteCommand(keywordArgument.Value, "*", CommandTypes.All)); + return result.Count > 0 ? result : null; } - public override AstVisitAction VisitCommand(CommandAst commandAst) + if (lineKeyword.Value.Equals("FORWARDHELPCATEGORY", StringComparison.OrdinalIgnoreCase)) { - // MSFT: 784739 Stack overflow during tab completion of pipeline variable - // $null | % -pv p { $p -> In this case $p is pipelinevariable - // and is used in the same command. PipelineVariables are not available - // in the command they are assigned in. Hence the following code ignores - // if the variable being completed is in the command extent. - if ((commandAst != CompletionVariableAst) && (!CompletionVariableAst.Extent.IsWithin(commandAst.Extent))) + var result = new List(); + foreach (string category in s_commentHelpForwardCategories) { - string[] desiredParameters = new string[] { "PV", "PipelineVariable", "OV", "OutVariable" }; - - StaticBindingResult bindingResult = StaticParameterBinder.BindCommand(commandAst, false, desiredParameters); - if (bindingResult != null) + if (category.StartsWith(keywordArgument.Value, StringComparison.OrdinalIgnoreCase)) { - ParameterBindingResult parameterBindingResult; - - foreach (string commandVariableParameter in desiredParameters) - { - if (bindingResult.BoundParameters.TryGetValue(commandVariableParameter, out parameterBindingResult)) - { - VariableSources.Add(new Tuple((string)parameterBindingResult.ConstantValue, commandAst)); - } - } + result.Add(new CompletionResult(category)); } } - - return AstVisitAction.Continue; + return result.Count > 0 ? result : null; } - public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) + if (lineKeyword.Value.Equals("REMOTEHELPRUNSPACE", StringComparison.OrdinalIgnoreCase)) { - return functionDefinitionAst != Top ? AstVisitAction.SkipChildren : AstVisitAction.Continue; + var result = new List(); + foreach (CompletionResult variable in CompleteVariable(keywordArgument.Value)) + { + // ListItemText is used because it excludes the "$" as expected by REMOTEHELPRUNSPACE. + result.Add(new CompletionResult(variable.ListItemText, variable.ListItemText, variable.ResultType, variable.ToolTip)); + } + return result.Count > 0 ? result : null; } - public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst) + if (lineKeyword.Value.Equals("EXTERNALHELP", StringComparison.OrdinalIgnoreCase)) { - return scriptBlockExpressionAst != Top ? AstVisitAction.SkipChildren : AstVisitAction.Continue; + context.WordToComplete = keywordArgument.Value; + var result = new List(CompleteFilename(context, containerOnly: false, (new HashSet() { ".xml" }))); + return result.Count > 0 ? result : null; } - public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst) - { - return scriptBlockAst != Top ? AstVisitAction.SkipChildren : AstVisitAction.Continue; - } + return null; } - private static readonly Lazy> s_specialVariablesCache = new Lazy>(BuildSpecialVariablesCache); + 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 SortedSet BuildSpecialVariablesCache() + private static readonly HashSet s_commentHelpAllowedDuplicateKeywords = new(StringComparer.OrdinalIgnoreCase) { - var result = new SortedSet(); - foreach (var member in typeof(SpecialVariables).GetFields(BindingFlags.NonPublic | BindingFlags.Static)) + "EXAMPLE", + "LINK", + "PARAMETER" + }; + + private static readonly string[] s_commentHelpForwardCategories = new string[] + { + "Alias", + "All", + "Cmdlet", + "ExternalScript", + "FAQ", + "Filter", + "Function", + "General", + "Glossary", + "HelpFile", + "Provider", + "ScriptCommand" + }; + + private static FunctionDefinitionAst GetCommentHelpFunctionTarget(CompletionContext context) + { + if (context.TokenAtCursor.Kind != TokenKind.Comment) { - if (member.FieldType.Equals(typeof(string))) + return null; + } + + Ast lastAst = context.RelatedAsts[^1]; + Ast firstAstAfterComment = lastAst.Find(ast => ast.Extent.StartOffset >= context.TokenAtCursor.Extent.EndOffset && ast is not NamedBlockAst, searchNestedScriptBlocks: false); + + // Comment-based help can apply to a following function definition if it starts within 2 lines + int commentEndLine = context.TokenAtCursor.Extent.EndLineNumber + 2; + + if (lastAst is NamedBlockAst) + { + // Helpblock before function inside advanced function + if (firstAstAfterComment is not null + && firstAstAfterComment.Extent.StartLineNumber <= commentEndLine + && firstAstAfterComment is FunctionDefinitionAst outerHelpFunctionDefAst) { - result.Add((string)member.GetValue(null)); + return outerHelpFunctionDefAst; + } + + // Helpblock inside function + if (lastAst.Parent.Parent is FunctionDefinitionAst innerHelpFunctionDefAst) + { + return innerHelpFunctionDefAst; } } - return result; - } + if (lastAst is ScriptBlockAst) + { + // Helpblock before function + if (firstAstAfterComment is not null + && firstAstAfterComment.Extent.StartLineNumber <= commentEndLine + && firstAstAfterComment is FunctionDefinitionAst statement) + { + return statement; + } - #endregion Variables + // Advanced function with help inside + if (lastAst.Parent is FunctionDefinitionAst advFuncDefAst) + { + return advFuncDefAst; + } + } - #region Comments + return null; + } - // Complete the history entries - internal static List CompleteComment(CompletionContext context) + private static List CompleteCommentParameterValue(CompletionContext context, string wordToComplete) { - List results = new List(); - - Match matchResult = Regex.Match(context.WordToComplete, @"^#([\w\-]*)$"); - if (!matchResult.Success) { return results; } + FunctionDefinitionAst foundFunction = GetCommentHelpFunctionTarget(context); - string wordToComplete = matchResult.Groups[1].Value; - Collection psobjs; + ReadOnlyCollection foundParameters = null; + if (foundFunction is not null) + { + foundParameters = foundFunction.Parameters ?? foundFunction.Body.ParamBlock?.Parameters; + } + else if (context.RelatedAsts[^1] is ScriptBlockAst scriptAst) + { + // The helpblock is for a script file + foundParameters = scriptAst.ParamBlock?.Parameters; + } - int entryId; - if (Regex.IsMatch(wordToComplete, @"^[0-9]+$") && LanguagePrimitives.TryConvertTo(wordToComplete, out entryId)) + if (foundParameters is null || foundParameters.Count == 0) { - context.Helper.AddCommandWithPreferenceSetting("Get-History", typeof(GetHistoryCommand)).AddParameter("Id", entryId); - psobjs = context.Helper.ExecuteCurrentPowerShell(out _); + return null; + } - if (psobjs != null && psobjs.Count == 1) + var parametersToShow = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (ParameterAst parameter in foundParameters) + { + if (parameter.Name.VariablePath.UserPath.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)) { - var historyInfo = PSObject.Base(psobjs[0]) as HistoryInfo; - if (historyInfo != null) - { - var commandLine = historyInfo.CommandLine; - if (!string.IsNullOrEmpty(commandLine)) - { - // var tooltip = "Id: " + historyInfo.Id + "\n" + - // "ExecutionStatus: " + historyInfo.ExecutionStatus + "\n" + - // "StartExecutionTime: " + historyInfo.StartExecutionTime + "\n" + - // "EndExecutionTime: " + historyInfo.EndExecutionTime + "\n"; - // Use the commandLine as the Tooltip in case the commandLine is multiple lines of scripts - results.Add(new CompletionResult(commandLine, commandLine, CompletionResultType.History, commandLine)); - } - } + parametersToShow.Add(parameter.Name.VariablePath.UserPath); } - - return results; } - wordToComplete = "*" + wordToComplete + "*"; - context.Helper.AddCommandWithPreferenceSetting("Get-History", typeof(GetHistoryCommand)); - - psobjs = context.Helper.ExecuteCurrentPowerShell(out _); - var pattern = WildcardPattern.Get(wordToComplete, WildcardOptions.IgnoreCase); - - if (psobjs != null) + MatchCollection usedParameters = Regex.Matches(context.TokenAtCursor.Text, @"(?<=^\s*\.parameter\s+)\w.*(?<=\S)", RegexOptions.Multiline | RegexOptions.IgnoreCase); + foreach (Match parameter in usedParameters) { - for (int index = psobjs.Count - 1; index >= 0; index--) + if (wordToComplete.Equals(parameter.Value, StringComparison.OrdinalIgnoreCase)) { - var psobj = psobjs[index]; - if (!(PSObject.Base(psobj) is HistoryInfo historyInfo)) continue; - - var commandLine = historyInfo.CommandLine; - if (!string.IsNullOrEmpty(commandLine) && pattern.IsMatch(commandLine)) - { - // var tooltip = "Id: " + historyInfo.Id + "\n" + - // "ExecutionStatus: " + historyInfo.ExecutionStatus + "\n" + - // "StartExecutionTime: " + historyInfo.StartExecutionTime + "\n" + - // "EndExecutionTime: " + historyInfo.EndExecutionTime + "\n"; - // Use the commandLine as the Tooltip in case the commandLine is multiple lines of scripts - results.Add(new CompletionResult(commandLine, commandLine, CompletionResultType.History, commandLine)); - } + continue; } + parametersToShow.Remove(parameter.Value); } - return results; + var result = new List(); + foreach (string parameter in parametersToShow) + { + result.Add(new CompletionResult(parameter)); + } + + return result.Count > 0 ? result : null; } #endregion Comments @@ -4999,17 +6489,19 @@ internal static List CompleteComment(CompletionContext context new List> { new Tuple("Where", "Where({ expression } [, mode [, numberToReturn]])"), - new Tuple("ForEach", "ForEach(expression [, arguments...])") + new Tuple("ForEach", "ForEach(expression [, arguments...])"), + new Tuple("PSWhere", "PSWhere({ expression } [, mode [, numberToReturn]])"), + new Tuple("PSForEach", "PSForEach(expression [, arguments...])"), }; // List of DSC collection-value variables private static readonly HashSet s_dscCollectionVariables = new HashSet(StringComparer.OrdinalIgnoreCase) { "SelectedNodes", "AllNodes" }; - internal static List CompleteMember(CompletionContext context, bool @static) + internal static List CompleteMember(CompletionContext context, bool @static, ref int replacementLength) { // If we get here, we know that either: - // * the cursor appeared immediately after a member access token ('.' or '::'). + // * the cursor appeared after a member access token ('.' or '::'). // * the parent of the ast on the cursor was a member expression. // // In the first case, we have 2 possibilities: @@ -5017,31 +6509,35 @@ internal static List CompleteMember(CompletionContext context, // * the last ast is a string constant, with something like: echo $foo. var results = new List(); - var lastAst = context.RelatedAsts.Last(); - var lastAstAsMemberExpr = lastAst as MemberExpressionAst; + var memberName = "*"; Ast memberNameCandidateAst = null; ExpressionAst targetExpr = null; - if (lastAstAsMemberExpr != null) + + if (lastAst is MemberExpressionAst LastAstAsMemberExpression) { // If the cursor is not inside the member name in the member expression, assume // that the user had incomplete input, but the parser got lucky and succeeded parsing anyway. - if (context.TokenAtCursor.Extent.StartOffset >= lastAstAsMemberExpr.Member.Extent.StartOffset) + if (context.TokenAtCursor is not null && context.TokenAtCursor.Extent.StartOffset >= LastAstAsMemberExpression.Member.Extent.StartOffset) { - memberNameCandidateAst = lastAstAsMemberExpr.Member; + memberNameCandidateAst = LastAstAsMemberExpression.Member; } - targetExpr = lastAstAsMemberExpr.Expression; + targetExpr = LastAstAsMemberExpression.Expression; + // Handles scenario where the cursor is after the member access token but before the text + // like: "".Le + // which completes the member using the partial text after the cursor. + if (LastAstAsMemberExpression.Member is StringConstantExpressionAst stringExpression && stringExpression.Extent.StartOffset <= context.CursorPosition.Offset) + { + memberName = $"{stringExpression.Value}*"; + } } else { memberNameCandidateAst = lastAst; } - var memberNameAst = memberNameCandidateAst as StringConstantExpressionAst; - - var memberName = "*"; - if (memberNameAst != null) + if (memberNameCandidateAst is StringConstantExpressionAst memberNameAst) { // Make sure to correctly handle: echo $foo. if (!memberNameAst.Value.Equals(".", StringComparison.OrdinalIgnoreCase) && !memberNameAst.Value.Equals("::", StringComparison.OrdinalIgnoreCase)) @@ -5055,8 +6551,7 @@ internal static List CompleteMember(CompletionContext context, return results; } - var commandAst = lastAst.Parent as CommandAst; - if (commandAst != null) + if (lastAst.Parent is CommandAst commandAst) { int i; for (i = commandAst.CommandElements.Count - 1; i >= 0; --i) @@ -5076,10 +6571,36 @@ internal static List CompleteMember(CompletionContext context, targetExpr = nextToLastAst as ExpressionAst; } } - else if (lastAst.Parent is MemberExpressionAst) + else if (lastAst.Parent is MemberExpressionAst parentAsMemberExpression) { + if (lastAst is ErrorExpressionAst) + { + // Handles scenarios like $PSVersionTable.PSVersi.Major. + // where the cursor is moved back to a previous member expression while + // there's an incomplete member expression at the end + targetExpr = parentAsMemberExpression; + do + { + if (targetExpr is MemberExpressionAst memberExpression) + { + targetExpr = memberExpression.Expression; + } + else + { + break; + } + } while (targetExpr.Extent.EndOffset >= context.CursorPosition.Offset); + + if (targetExpr.Parent != parentAsMemberExpression + && targetExpr.Parent is MemberExpressionAst memberAst + && memberAst.Member is StringConstantExpressionAst stringExpression + && stringExpression.Extent.StartOffset <= context.CursorPosition.Offset) + { + memberName = $"{stringExpression.Value}*"; + } + } // If 'targetExpr' has already been set, we should skip this step. This is for some member completion - // cases in ISE. In ISE, we may add a new statement in the middle of existing statements as follows: + // cases in VSCode, where we may add a new statement in the middle of existing statements as follows: // $xml = New-Object Xml // $xml. // $xml.Save("C:\data.xml") @@ -5087,24 +6608,45 @@ internal static List CompleteMember(CompletionContext context, // a MemberExpressionAst '$xml.$xml', whose parent is still a MemberExpressionAst '$xml.$xml.Save'. // But here we DO NOT want to re-assign 'targetExpr' to be '$xml.$xml'. 'targetExpr' in this case // should be '$xml'. - if (targetExpr == null) + else { - var memberExprAst = (MemberExpressionAst)lastAst.Parent; - targetExpr = memberExprAst.Expression; + targetExpr ??= parentAsMemberExpression.Expression; } } - else if (lastAst.Parent is BinaryExpressionAst && context.TokenAtCursor.Kind.Equals(TokenKind.Multiply)) + else if (lastAst.Parent is BinaryExpressionAst binaryExpression && context.TokenAtCursor.Kind.Equals(TokenKind.Multiply)) { - var memberExprAst = ((BinaryExpressionAst)lastAst.Parent).Left as MemberExpressionAst; - if (memberExprAst != null) + if (binaryExpression.Left is MemberExpressionAst memberExpression) { - targetExpr = memberExprAst.Expression; - if (memberExprAst.Member is StringConstantExpressionAst) + targetExpr = memberExpression.Expression; + if (memberExpression.Member is StringConstantExpressionAst stringExpression) { - memberName = ((StringConstantExpressionAst)memberExprAst.Member).Value + "*"; + memberName = $"{stringExpression.Value}*"; } } } + else if (lastAst.Parent is ErrorStatementAst errorStatement) + { + // Handles switches like: + // switch ($x) + // { + // 'RandomString'. + // { } + // } + Ast astBeforeMemberAccessToken = null; + for (int i = errorStatement.Bodies.Count - 1; i >= 0; i--) + { + astBeforeMemberAccessToken = errorStatement.Bodies[i]; + if (astBeforeMemberAccessToken.Extent.EndOffset < lastAst.Extent.EndOffset) + { + break; + } + } + + if (astBeforeMemberAccessToken is ExpressionAst expression) + { + targetExpr = expression; + } + } if (targetExpr == null) { @@ -5137,6 +6679,11 @@ internal static List CompleteMember(CompletionContext context, inferredTypes = AstTypeInference.InferTypeOf(targetExpr, context.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval).ToArray(); } + if (!@static && inferredTypes.Length == 1 && inferredTypes[0].Name.Equals("System.Void", StringComparison.OrdinalIgnoreCase)) + { + return results; + } + if (inferredTypes != null && inferredTypes.Length > 0) { // Use inferred types if we have any @@ -5193,28 +6740,77 @@ internal static List CompleteMember(CompletionContext context, } } + if (memberName != "*" && results.Count > 0) + { + // -1 because membername always has a trailing wildcard * + replacementLength = memberName.Length - 1; + } + return results; } + internal static List CompleteComparisonOperatorValues(CompletionContext context, ExpressionAst operatorLeftValue) + { + var result = new List(); + var resolvedTypes = new List(); + + if (SafeExprEvaluator.TrySafeEval(operatorLeftValue, context.ExecutionContext, out object value) && value is not null) + { + resolvedTypes.Add(value.GetType()); + } + else + { + var inferredTypes = AstTypeInference.InferTypeOf(operatorLeftValue, context.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval); + foreach (var type in inferredTypes) + { + if (type.Type is not null) + { + resolvedTypes.Add(type.Type); + } + } + } + + foreach (var type in resolvedTypes) + { + if (type.IsEnum) + { + foreach (var name in type.GetEnumNames()) + { + if (name.StartsWith(context.WordToComplete, StringComparison.OrdinalIgnoreCase)) + { + result.Add(new CompletionResult($"'{name}'", name, CompletionResultType.ParameterValue, name)); + } + } + + break; + } + } + + return result; + } + /// /// Complete members against extension methods 'Where' and 'ForEach' /// - private static void CompleteExtensionMethods(string memberName, List results) + private static void CompleteExtensionMethods(string memberName, List results, bool addMethodParenthesis = true) { var pattern = WildcardPattern.Get(memberName, WildcardOptions.IgnoreCase); - CompleteExtensionMethods(pattern, results); + CompleteExtensionMethods(pattern, results, addMethodParenthesis); } /// /// Complete members against extension methods 'Where' and 'ForEach' based on the given pattern. /// - private static void CompleteExtensionMethods(WildcardPattern pattern, List results) + private static void CompleteExtensionMethods(WildcardPattern pattern, List results, bool addMethodParenthesis) { - results.AddRange(from member in s_extensionMethods - where pattern.IsMatch(member.Item1) - select - new CompletionResult(member.Item1 + "(", member.Item1, - CompletionResultType.Method, member.Item2)); + foreach (var member in s_extensionMethods) + { + if (pattern.IsMatch(member.Item1)) + { + string completionText = addMethodParenthesis ? $"{member.Item1}(" : member.Item1; + results.Add(new CompletionResult(completionText, member.Item1, CompletionResultType.Method, member.Item2)); + } + } } /// @@ -5244,23 +6840,143 @@ private static bool IsInDscContext(ExpressionAst expression) return Ast.GetAncestorAst(expression) != null; } - internal static void CompleteMemberByInferredType(TypeInferenceContext context, IEnumerable inferredTypes, List results, string memberName, Func filter, bool isStatic) + internal static List CompleteIndexExpression(CompletionContext context, ExpressionAst indexTarget) + { + var result = new List(); + object value; + if (SafeExprEvaluator.TrySafeEval(indexTarget, context.ExecutionContext, out value) + && value is not null + && PSObject.Base(value) is IDictionary dictionary) + { + foreach (var key in dictionary.Keys) + { + if (key is string keyAsString && keyAsString.StartsWith(context.WordToComplete, StringComparison.OrdinalIgnoreCase)) + { + result.Add(new CompletionResult($"'{keyAsString}'", keyAsString, CompletionResultType.Property, keyAsString)); + } + } + } + else + { + var inferredTypes = AstTypeInference.InferTypeOf(indexTarget, context.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval); + foreach (var type in inferredTypes) + { + if (type is PSSyntheticTypeName synthetic) + { + foreach (var member in synthetic.Members) + { + if (member.Name.StartsWith(context.WordToComplete, StringComparison.OrdinalIgnoreCase)) + { + result.Add(new CompletionResult($"'{member.Name}'", member.Name, CompletionResultType.Property, member.Name)); + } + } + } + } + } + return result; + } + + private static void CompleteFormatViewByInferredType(CompletionContext context, string[] inferredTypeNames, List results, string commandName) + { + var typeInfoDB = context.TypeInferenceContext.ExecutionContext.FormatDBManager.GetTypeInfoDataBase(); + + if (typeInfoDB is null) + { + return; + } + + Type controlBodyType = commandName switch + { + "Format-Table" => typeof(TableControlBody), + "Format-List" => typeof(ListControlBody), + "Format-Wide" => typeof(WideControlBody), + "Format-Custom" => typeof(ComplexControlBody), + _ => null + }; + + Diagnostics.Assert(controlBodyType is not null, "This should never happen unless a new Format-* cmdlet is added"); + + var wordToComplete = context.WordToComplete; + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); + WildcardPattern viewPattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); + + var uniqueNames = new HashSet(); + foreach (ViewDefinition viewDefinition in typeInfoDB.viewDefinitionsSection.viewDefinitionList) + { + if (viewDefinition?.appliesTo is not null && controlBodyType == viewDefinition.mainControl.GetType()) + { + foreach (TypeOrGroupReference applyTo in viewDefinition.appliesTo.referenceList) + { + foreach (string inferredTypeName in inferredTypeNames) + { + // We use 'StartsWith()' because 'applyTo.Name' can look like "System.Diagnostics.Process#IncludeUserName". + if (applyTo.name.StartsWith(inferredTypeName, StringComparison.OrdinalIgnoreCase) + && uniqueNames.Add(viewDefinition.name) + && viewPattern.IsMatch(viewDefinition.name)) + { + 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 || ContainsCharactersRequiringQuotes(viewDefinition.name)) + { + completionText = "'" + completionText.Replace("'", "''") + "'"; + } + + results.Add(new CompletionResult(completionText, viewDefinition.name, CompletionResultType.Text, viewDefinition.name)); + } + } + } + } + } + } + + internal static void CompleteMemberByInferredType( + TypeInferenceContext context, + IEnumerable inferredTypes, + List results, + string memberName, + Func filter, + bool isStatic, + HashSet excludedMembers = null, + bool addMethodParenthesis = true, + bool ignoreTypesWithoutDefaultConstructor = false) { bool extensionMethodsAdded = false; HashSet typeNameUsed = new HashSet(StringComparer.OrdinalIgnoreCase); WildcardPattern memberNamePattern = WildcardPattern.Get(memberName, WildcardOptions.IgnoreCase); foreach (var psTypeName in inferredTypes) { - if (typeNameUsed.Contains(psTypeName.Name)) + if (!typeNameUsed.Add(psTypeName.Name) + || (ignoreTypesWithoutDefaultConstructor && psTypeName.Type is not null && psTypeName.Type.GetConstructor(Type.EmptyTypes) is null && !psTypeName.Type.IsInterface)) { continue; } - typeNameUsed.Add(psTypeName.Name); + if (ignoreTypesWithoutDefaultConstructor && psTypeName.TypeDefinitionAst is not null) + { + bool foundConstructor = false; + bool foundDefaultConstructor = false; + foreach (var member in psTypeName.TypeDefinitionAst.Members) + { + if (member is FunctionMemberAst methodDefinition && methodDefinition.IsConstructor) + { + foundConstructor = true; + if (methodDefinition.Parameters.Count == 0) + { + foundDefaultConstructor = true; + break; + } + } + } + if (foundConstructor && !foundDefaultConstructor) + { + continue; + } + } + var members = context.GetMembersByInferredType(psTypeName, isStatic, filter); foreach (var member in members) { - AddInferredMember(member, memberNamePattern, results); + AddInferredMember(member, memberNamePattern, results, excludedMembers, addMethodParenthesis); } // Check if we need to complete against the extension methods 'Where' and 'ForEach' @@ -5268,7 +6984,7 @@ internal static void CompleteMemberByInferredType(TypeInferenceContext context, { // Complete extension methods 'Where' and 'ForEach' for Enumerable types extensionMethodsAdded = true; - CompleteExtensionMethods(memberNamePattern, results); + CompleteExtensionMethods(memberNamePattern, results, addMethodParenthesis); } } @@ -5280,14 +6996,13 @@ internal static void CompleteMemberByInferredType(TypeInferenceContext context, .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Utility\\Sort-Object") .AddParameter("Property", new[] { "ResultType", "ListItemText" }) .AddParameter("Unique"); - Exception unused; - var sortedResults = powerShellExecutionHelper.ExecuteCurrentPowerShell(out unused, results); + var sortedResults = powerShellExecutionHelper.ExecuteCurrentPowerShell(out _, results); results.Clear(); - results.AddRange(sortedResults.Select(psobj => PSObject.Base(psobj) as CompletionResult)); + results.AddRange(sortedResults.Select(static psobj => PSObject.Base(psobj) as CompletionResult)); } } - private static void AddInferredMember(object member, WildcardPattern memberNamePattern, List results) + private static void AddInferredMember(object member, WildcardPattern memberNamePattern, List results, HashSet excludedMembers, bool addMethodParenthesis) { string memberName = null; bool isMethod = false; @@ -5313,7 +7028,7 @@ private static void AddInferredMember(object member, WildcardPattern memberNameP { memberName = methodCacheEntry[0].method.Name; isMethod = true; - getToolTip = () => string.Join("\n", methodCacheEntry.methodInformationStructures.Select(m => m.methodDefinition)); + getToolTip = () => string.Join('\n', methodCacheEntry.methodInformationStructures.Select(static m => m.methodDefinition)); } var psMemberInfo = member as PSMemberInfo; @@ -5332,21 +7047,45 @@ private static void AddInferredMember(object member, WildcardPattern memberNameP getToolTip = () => GetCimPropertyToString(cimProperty); } - var memberAst = member as MemberAst; - if (memberAst != null) + if (member is MemberAst memberAst) { - memberName = memberAst is CompilerGeneratedMemberFunctionAst ? "new" : memberAst.Name; - isMethod = memberAst is FunctionMemberAst || memberAst is CompilerGeneratedMemberFunctionAst; + if (memberAst is CompilerGeneratedMemberFunctionAst) + { + memberName = "new"; + isMethod = true; + } + else if (memberAst is FunctionMemberAst functionMember) + { + memberName = functionMember.IsConstructor ? "new" : functionMember.Name; + isMethod = true; + } + else + { + memberName = memberAst.Name; + isMethod = false; + } getToolTip = memberAst.GetTooltip; } - if (memberName == null || !memberNamePattern.IsMatch(memberName)) + if (memberName == null || !memberNamePattern.IsMatch(memberName) || (excludedMembers is not null && excludedMembers.Contains(memberName))) { return; } var completionResultType = isMethod ? CompletionResultType.Method : CompletionResultType.Property; - var completionText = isMethod ? memberName + "(" : memberName; + string completionText; + if (isMethod && addMethodParenthesis) + { + completionText = $"{memberName}("; + } + else if (ContainsCharactersRequiringQuotes(memberName)) + { + completionText = $"'{memberName}'"; + } + else + { + completionText = memberName; + } results.Add(new CompletionResult(completionText, memberName, completionResultType, getToolTip())); } @@ -5388,6 +7127,12 @@ private static bool IsWriteablePropertyMember(object member) return psPropertyInfo.IsSettable; } + if (member is PropertyMemberAst) + { + // Properties in PowerShell classes are always writeable + return true; + } + return false; } @@ -5539,7 +7284,7 @@ internal override CompletionResult GetCompletionResult(string keyMatched, string /// This type represents a generic type for type name completion. It only contains information that can be /// inferred from the full type name. /// - private class GenericTypeCompletionInStringFormat : TypeCompletionInStringFormat + private sealed class GenericTypeCompletionInStringFormat : TypeCompletionInStringFormat { /// /// Get the number of generic type arguments required by the type represented by this instance. @@ -5591,7 +7336,7 @@ internal override CompletionResult GetCompletionResult(string keyMatched, string if (i != 0) tooltip.Append(", "); tooltip.Append(GenericArgumentCount == 1 ? "T" - : string.Format(CultureInfo.InvariantCulture, "T{0}", i + 1)); + : string.Create(CultureInfo.InvariantCulture, $"T{i + 1}")); } tooltip.Append(']'); @@ -5656,7 +7401,7 @@ internal override CompletionResult GetCompletionResult(string keyMatched, string /// /// This type represents a generic type for type name completion. It contains the actual type instance. /// - private class GenericTypeCompletion : TypeCompletion + private sealed class GenericTypeCompletion : TypeCompletion { internal override CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix) { @@ -5693,7 +7438,7 @@ internal override CompletionResult GetCompletionResult(string keyMatched, string /// /// This type represents a namespace for namespace completion. /// - private class NamespaceCompletion : TypeCompletionBase + private sealed class NamespaceCompletion : TypeCompletionBase { internal string Namespace; @@ -5715,7 +7460,7 @@ internal override CompletionResult GetCompletionResult(string keyMatched, string } } - private class TypeCompletionMapping + private sealed class TypeCompletionMapping { // The Key is the string we'll be searching on. It could complete to various things. internal string Key; @@ -5824,7 +7569,7 @@ private static TypeCompletionMapping[][] InitializeTypeCache() #endregion Process_LoadedAssemblies - var grouping = entries.Values.GroupBy(t => t.Key.Count(c => c == '.')).OrderBy(g => g.Key).ToArray(); + var grouping = entries.Values.GroupBy(static t => t.Key.Count(c => c == '.')).OrderBy(static g => g.Key).ToArray(); var localTypeCache = new TypeCompletionMapping[grouping.Last().Key + 1][]; foreach (var group in grouping) { @@ -5941,7 +7686,7 @@ internal static List CompleteNamespace(CompletionContext conte var localTypeCache = s_typeCache ?? InitializeTypeCache(); var results = new List(); var wordToComplete = context.WordToComplete; - var dots = wordToComplete.Count(c => c == '.'); + var dots = wordToComplete.Count(static c => c == '.'); if (dots >= localTypeCache.Length || localTypeCache[dots] == null) { return results; @@ -5957,7 +7702,7 @@ internal static List CompleteNamespace(CompletionContext conte } } - results.Sort((c1, c2) => string.Compare(c1.ListItemText, c2.ListItemText, StringComparison.OrdinalIgnoreCase)); + results.Sort(static (c1, c2) => string.Compare(c1.ListItemText, c2.ListItemText, StringComparison.OrdinalIgnoreCase)); return results; } @@ -5985,7 +7730,7 @@ internal static List CompleteType(CompletionContext context, s var results = new List(); var completionTextSet = new HashSet(StringComparer.OrdinalIgnoreCase); var wordToComplete = context.WordToComplete; - var dots = wordToComplete.Count(c => c == '.'); + var dots = wordToComplete.Count(static c => c == '.'); if (dots >= localTypeCache.Length || localTypeCache[dots] == null) { return results; @@ -6016,7 +7761,7 @@ internal static List CompleteType(CompletionContext context, s if (context.RelatedAsts != null && context.RelatedAsts.Count > 0) { var scriptBlockAst = (ScriptBlockAst)context.RelatedAsts[0]; - var typeAsts = scriptBlockAst.FindAll(ast => ast is TypeDefinitionAst, false).Cast(); + var typeAsts = scriptBlockAst.FindAll(static ast => ast is TypeDefinitionAst, false).Cast(); foreach (var typeAst in typeAsts.Where(ast => pattern.IsMatch(ast.Name))) { string toolTipPrefix = string.Empty; @@ -6031,7 +7776,7 @@ internal static List CompleteType(CompletionContext context, s } } - results.Sort((c1, c2) => string.Compare(c1.ListItemText, c2.ListItemText, StringComparison.OrdinalIgnoreCase)); + results.Sort(static (c1, c2) => string.Compare(c1.ListItemText, c2.ListItemText, StringComparison.OrdinalIgnoreCase)); return results; } @@ -6071,67 +7816,31 @@ private static string GetNamespaceToRemove(CompletionContext context, TypeComple internal static List CompleteHelpTopics(CompletionContext context) { - var results = new List(); - var searchPaths = new List(); - var currentCulture = CultureInfo.CurrentCulture.Name; - - // Add the user scope path first, since it is searched in order. - var userHelpRoot = Path.Combine(HelpUtils.GetUserHomeHelpSearchPath(), currentCulture); - - if (Directory.Exists(userHelpRoot)) - { - searchPaths.Add(userHelpRoot); - } - - var dirPath = Path.Combine(Utils.GetApplicationBase(Utils.DefaultPowerShellShellID), currentCulture); - searchPaths.Add(dirPath); - - var wordToComplete = context.WordToComplete + "*"; - var topicPattern = WildcardPattern.Get("about_*.help.txt", WildcardOptions.IgnoreCase); - List files = new List(); - - try + ArrayList helpProviders = context.ExecutionContext.HelpSystem.HelpProviders; + HelpFileHelpProvider helpFileProvider = null; + for (int i = helpProviders.Count - 1; i >= 0; i--) { - var wildcardPattern = WildcardPattern.Get(wordToComplete, WildcardOptions.IgnoreCase); - - foreach (var dir in searchPaths) + if (helpProviders[i] is HelpFileHelpProvider provider) { - foreach (var file in Directory.EnumerateFiles(dir)) - { - if (wildcardPattern.IsMatch(Path.GetFileName(file))) - { - files.Add(file); - } - } + helpFileProvider = provider; + break; } } - catch (Exception) + + if (helpFileProvider is null) { + return null; } - if (files != null) + List results = new(); + Collection filesMatched = MUIFileSearcher.SearchFiles($"{context.WordToComplete}*.help.txt", helpFileProvider.GetExtendedSearchPaths()); + foreach (string path in filesMatched) { - foreach (string file in files) + string fileName = Path.GetFileName(path); + if (fileName.StartsWith("about_", StringComparison.OrdinalIgnoreCase)) { - if (file == null) - { - continue; - } - - try - { - var fileName = Path.GetFileName(file); - if (fileName == null || !topicPattern.IsMatch(fileName)) - continue; - - // All topic files are ending with ".help.txt" - var completionText = fileName.Substring(0, fileName.Length - 9); - results.Add(new CompletionResult(completionText)); - } - catch (Exception) - { - continue; - } + string topicName = fileName.Substring(0, fileName.Length - ".help.txt".Length); + results.Add(new CompletionResult(topicName, topicName, CompletionResultType.ParameterValue, topicName)); } } @@ -6153,9 +7862,7 @@ internal static List 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(); @@ -6285,162 +7992,469 @@ internal static List CompleteHashtableKeyForDynamicKeyword( return results; } - internal static List CompleteHashtableKey(CompletionContext completionContext, HashtableAst hashtableAst) + private static PSTypeName GetNestedHashtableKeyType(TypeInferenceContext typeContext, PSTypeName parentType, IList nestedKeys) { - var typeAst = hashtableAst.Parent as ConvertExpressionAst; - if (typeAst != null) + var currentType = parentType; + // The nestedKeys list should have the outer most key as the last element, and the inner most key as the first element + // If we fail to resolve the type of any key we return null + for (int i = nestedKeys.Count - 1; i >= 0; i--) { - var result = new List(); - CompleteMemberByInferredType( - completionContext.TypeInferenceContext, AstTypeInference.InferTypeOf(typeAst, completionContext.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval), - result, completionContext.WordToComplete + "*", IsWriteablePropertyMember, isStatic: false); - return result; + if (currentType is null) + { + return null; + } + + var typeMembers = typeContext.GetMembersByInferredType(currentType, false, null); + currentType = null; + foreach (var member in typeMembers) + { + if (member is PropertyInfo propertyInfo) + { + if (propertyInfo.Name.Equals(nestedKeys[i], StringComparison.OrdinalIgnoreCase)) + { + currentType = new PSTypeName(propertyInfo.PropertyType); + break; + } + } + else if (member is PropertyMemberAst memberAst && memberAst.Name.Equals(nestedKeys[i], StringComparison.OrdinalIgnoreCase)) + { + if (memberAst.PropertyType is null) + { + return null; + } + else + { + if (memberAst.PropertyType.TypeName is ArrayTypeName arrayType) + { + currentType = new PSTypeName(arrayType.ElementType); + } + else + { + currentType = new PSTypeName(memberAst.PropertyType.TypeName); + } + } + + break; + } + } } - // hashtable arguments sometimes have expected keys. Examples: - // new-object System.Drawing.Point -prop @{ X=1; Y=1 } - // dir | sort-object -prop @{Expression=... ; Ascending=... } - // format-table -Property - // Expression - // FormatString - // Label - // Width - // Alignment - // format-list -Property - // Expression - // FormatString - // Label - // format-custom -Property - // Expression - // Depth - // format-* -GroupBy - // Expression - // FormatString - // Label - // + return currentType; + } + + internal static List CompleteHashtableKey(CompletionContext completionContext, HashtableAst hashtableAst) + { + Ast previousAst = hashtableAst; + Ast parentAst = hashtableAst.Parent; + string parameterName = null; + var nestedHashtableKeys = new List(); + + // This loop determines if it's a nested hashtable and what the outermost hashtable is used for (Dynamic keyword, command argument, etc.) + // Note this also considers hashtables with arrays of hashtables to be nested to support scenarios like this: + // class Level1 + // { + // [Level2[]] $Prop1 + // } + // class Level2 + // { + // [string] $Prop2 + // } + // [Level1] @{ + // Prop1 = @( + // @{Prop2="Hello"} + // @{Pro} + // ) + // } + while (parentAst is not null) + { + switch (parentAst) + { + case HashtableAst parentTable: + foreach (var pair in parentTable.KeyValuePairs) + { + if (pair.Item2 == previousAst) + { + // Try to get the value of the hashtable key in the nested hashtable. + // If we fail to get the value then return early because we can't generate any useful completions + if (SafeExprEvaluator.TrySafeEval(pair.Item1, completionContext.ExecutionContext, out object value)) + { + if (value is not string stringValue) + { + return null; + } + + nestedHashtableKeys.Add(stringValue); + break; + } + else + { + return null; + } + } + } + break; + + case DynamicKeywordStatementAst dynamicKeyword: + return CompleteHashtableKeyForDynamicKeyword(completionContext, dynamicKeyword, hashtableAst); + + case CommandParameterAst cmdParam: + parameterName = cmdParam.ParameterName; + parentAst = cmdParam.Parent; + goto ExitWhileLoop; + + case AssignmentStatementAst assignment: + if (assignment.Left is MemberExpressionAst or ConvertExpressionAst) + { + parentAst = assignment.Left; + } + goto ExitWhileLoop; + + case CommandAst: + case ConvertExpressionAst: + case UsingStatementAst: + goto ExitWhileLoop; + + case CommandExpressionAst: + case PipelineAst: + case StatementBlockAst: + case ArrayExpressionAst: + case ArrayLiteralAst: + break; + + default: + return null; + } - // Find out if we are in a command argument. Consider the following possibilities: - // cmd @{} - // cmd -foo @{} - // cmd -foo:@{} - // cmd @{},@{} - // cmd -foo @{},@{} - // cmd -foo:@{},@{} + previousAst = parentAst; + parentAst = parentAst.Parent; + } - var ast = hashtableAst.Parent; + ExitWhileLoop: - // Handle completion for hashtable within DynamicKeyword statement - var dynamicKeywordStatementAst = ast as DynamicKeywordStatementAst; - if (dynamicKeywordStatementAst != null) + bool hashtableIsNested = nestedHashtableKeys.Count > 0; + int cursorOffset = completionContext.CursorPosition.Offset; + string wordToComplete = completionContext.WordToComplete; + var excludedKeys = new HashSet(StringComparer.OrdinalIgnoreCase); + // Filters out keys that have already been defined in the hashtable, except the one the cursor is at + foreach (var keyPair in hashtableAst.KeyValuePairs) { - return CompleteHashtableKeyForDynamicKeyword(completionContext, dynamicKeywordStatementAst, hashtableAst); + if (!(cursorOffset >= keyPair.Item1.Extent.StartOffset && cursorOffset <= keyPair.Item1.Extent.EndOffset)) + { + excludedKeys.Add(keyPair.Item1.Extent.Text); + } } - if (ast is ArrayLiteralAst) + if (parentAst is UsingStatementAst usingStatement) { - ast = ast.Parent; + if (hashtableIsNested || usingStatement.UsingStatementKind != UsingStatementKind.Module) + { + return null; + } + + var result = new List(); + foreach (string key in s_requiresModuleSpecKeys) + { + if (excludedKeys.Contains(key) + || (wordToComplete is not null && !key.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)) + || (key.Equals("RequiredVersion") && (excludedKeys.Contains("ModuleVersion") || excludedKeys.Contains("MaximumVersion"))) + || ((key.Equals("ModuleVersion") || key.Equals("MaximumVersion")) && excludedKeys.Contains("RequiredVersion"))) + { + continue; + } + + string toolTip = GetRequiresModuleSpecKeysToolTip(key); + + result.Add(new CompletionResult(key, key, CompletionResultType.Property, toolTip)); + } + + return result; } - if (ast is CommandParameterAst) + if (parentAst is MemberExpressionAst or ConvertExpressionAst) { - ast = ast.Parent; + IEnumerable inferredTypes; + if (hashtableIsNested) + { + var nestedType = GetNestedHashtableKeyType( + completionContext.TypeInferenceContext, + AstTypeInference.InferTypeOf(parentAst, completionContext.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval)[0], + nestedHashtableKeys); + if (nestedType is null) + { + return null; + } + + inferredTypes = TypeInferenceVisitor.GetInferredEnumeratedTypes(new PSTypeName[] { nestedType }); + } + else + { + inferredTypes = TypeInferenceVisitor.GetInferredEnumeratedTypes( + AstTypeInference.InferTypeOf(parentAst, completionContext.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval)); + } + + var result = new List(); + CompleteMemberByInferredType( + completionContext.TypeInferenceContext, + inferredTypes, + result, + wordToComplete + "*", + IsWriteablePropertyMember, + isStatic: false, + excludedKeys, + ignoreTypesWithoutDefaultConstructor: true); + return result; } - var commandAst = ast as CommandAst; - if (commandAst != null) + if (parentAst is CommandAst commandAst) { var binding = new PseudoParameterBinder().DoPseudoParameterBinding(commandAst, null, null, bindingType: PseudoParameterBinder.BindingType.ArgumentCompletion); - if (binding == null) + if (binding is null) { return null; } - string parameterName = null; - foreach (var boundArg in binding.BoundArguments) + if (parameterName is null) { - var astPair = boundArg.Value as AstPair; - if (astPair != null) + foreach (var boundArg in binding.BoundArguments) { - if (astPair.Argument == hashtableAst) + if (boundArg.Value is AstPair pair && pair.Argument == previousAst) { parameterName = boundArg.Key; - break; } - - continue; - } - - var astArrayPair = boundArg.Value as AstArrayPair; - if (astArrayPair != null) - { - if (astArrayPair.Argument.Contains(hashtableAst)) + else if (boundArg.Value is AstArrayPair arrayPair && arrayPair.Argument.Contains(previousAst)) { parameterName = boundArg.Key; - break; } - - continue; } } - if (parameterName != null) + if (parameterName is not null) { + List results; if (parameterName.Equals("GroupBy", StringComparison.OrdinalIgnoreCase)) { - switch (binding.CommandName) + if (!hashtableIsNested) { - case "Format-Table": - case "Format-List": - case "Format-Wide": - case "Format-Custom": - return GetSpecialHashTableKeyMembers("Expression", "FormatString", "Label"); + switch (binding.CommandName) + { + case "Format-Table": + case "Format-List": + case "Format-Wide": + case "Format-Custom": + return GetSpecialHashTableKeyMembers(excludedKeys, wordToComplete, "Expression", "FormatString", "Label"); + } } return null; } if (parameterName.Equals("Property", StringComparison.OrdinalIgnoreCase)) + { + if (!hashtableIsNested) + { + switch (binding.CommandName) + { + case "New-Object": + var inferredType = AstTypeInference.InferTypeOf(commandAst, completionContext.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval); + results = new List(); + CompleteMemberByInferredType( + completionContext.TypeInferenceContext, inferredType, + results, completionContext.WordToComplete + "*", IsWriteablePropertyMember, isStatic: false, excludedKeys); + return results; + case "Select-Object": + return GetSpecialHashTableKeyMembers(excludedKeys, wordToComplete, "Name", "Expression"); + case "Sort-Object": + return GetSpecialHashTableKeyMembers(excludedKeys, wordToComplete, "Expression", "Ascending", "Descending"); + case "Group-Object": + return GetSpecialHashTableKeyMembers(excludedKeys, wordToComplete, "Expression"); + case "Format-Table": + return GetSpecialHashTableKeyMembers(excludedKeys, wordToComplete, "Expression", "FormatString", "Label", "Width", "Alignment"); + case "Format-List": + return GetSpecialHashTableKeyMembers(excludedKeys, wordToComplete, "Expression", "FormatString", "Label"); + case "Format-Wide": + return GetSpecialHashTableKeyMembers(excludedKeys, wordToComplete, "Expression", "FormatString"); + case "Format-Custom": + return GetSpecialHashTableKeyMembers(excludedKeys, wordToComplete, "Expression", "Depth"); + case "Set-CimInstance": + case "New-CimInstance": + results = new List(); + NativeCompletionCimCommands(parameterName, binding.BoundArguments, results, commandAst, completionContext, excludedKeys, binding.CommandName); + // this method adds a null CompletionResult to the list but we don't want that here. + if (results.Count > 1) + { + results.RemoveAt(results.Count - 1); + return results; + } + return null; + } + return null; + } + } + + if (parameterName.Equals("FilterHashtable", StringComparison.OrdinalIgnoreCase)) { switch (binding.CommandName) { - case "New-Object": - var inferredType = AstTypeInference.InferTypeOf(commandAst, completionContext.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval); - var result = new List(); - CompleteMemberByInferredType( - completionContext.TypeInferenceContext, inferredType, - result, completionContext.WordToComplete + "*", IsWriteablePropertyMember, isStatic: false); - return result; - case "Select-Object": - return GetSpecialHashTableKeyMembers("Name", "Expression"); - case "Sort-Object": - return GetSpecialHashTableKeyMembers("Expression", "Ascending", "Descending"); - case "Group-Object": - return GetSpecialHashTableKeyMembers("Expression"); - case "Format-Table": - return GetSpecialHashTableKeyMembers("Expression", "FormatString", "Label", "Width", "Alignment"); - case "Format-List": - return GetSpecialHashTableKeyMembers("Expression", "FormatString", "Label"); - case "Format-Wide": - return GetSpecialHashTableKeyMembers("Expression", "FormatString"); - case "Format-Custom": - return GetSpecialHashTableKeyMembers("Expression", "Depth"); + case "Get-WinEvent": + if (nestedHashtableKeys.Count == 1 + && nestedHashtableKeys[0].Equals("SuppressHashFilter", StringComparison.OrdinalIgnoreCase) + && hashtableAst.Parent.Parent.Parent is HashtableAst) + { + return GetSpecialHashTableKeyMembers(excludedKeys, wordToComplete, "LogName", "ProviderName", "Path", "Keywords", "ID", "Level", + "StartTime", "EndTime", "UserID", "Data"); + } + else if (!hashtableIsNested) + { + return GetSpecialHashTableKeyMembers(excludedKeys, wordToComplete, "LogName", "ProviderName", "Path", "Keywords", "ID", "Level", + "StartTime", "EndTime", "UserID", "Data", "SuppressHashFilter"); + } + + return null; + } + } + + if (parameterName.Equals("Arguments", StringComparison.OrdinalIgnoreCase)) + { + if (!hashtableIsNested) + { + switch (binding.CommandName) + { + case "Invoke-CimMethod": + results = new List(); + NativeCompletionCimCommands(parameterName, binding.BoundArguments, results, commandAst, completionContext, excludedKeys, binding.CommandName); + // this method adds a null CompletionResult to the list but we don't want that here. + if (results.Count > 1) + { + results.RemoveAt(results.Count - 1); + return results; + } + return null; + } + } + return null; + } + + IEnumerable inferredTypes; + if (hashtableIsNested) + { + var nestedType = GetNestedHashtableKeyType( + completionContext.TypeInferenceContext, + new PSTypeName(binding.BoundParameters[parameterName].Parameter.Type), + nestedHashtableKeys); + if (nestedType is null) + { + return null; + } + inferredTypes = TypeInferenceVisitor.GetInferredEnumeratedTypes(new PSTypeName[] { nestedType }); + } + else + { + inferredTypes = TypeInferenceVisitor.GetInferredEnumeratedTypes(new PSTypeName[] { new PSTypeName(binding.BoundParameters[parameterName].Parameter.Type) }); + } + + results = new List(); + CompleteMemberByInferredType( + completionContext.TypeInferenceContext, + inferredTypes, + results, + $"{wordToComplete}*", + IsWriteablePropertyMember, + isStatic: false, + excludedKeys, + ignoreTypesWithoutDefaultConstructor: true); + return results; + } + } + else if (!hashtableIsNested && parentAst is AssignmentStatementAst assignment && assignment.Left is VariableExpressionAst assignmentVar) + { + var firstSplatUse = completionContext.RelatedAsts[0].Find( + currentAst => + currentAst.Extent.StartOffset > hashtableAst.Extent.EndOffset + && currentAst is VariableExpressionAst splatVar + && splatVar.Splatted + && splatVar.VariablePath.UserPath.Equals(assignmentVar.VariablePath.UserPath, StringComparison.OrdinalIgnoreCase), + searchNestedScriptBlocks: true) as VariableExpressionAst; + + if (firstSplatUse is not null && firstSplatUse.Parent is CommandAst command) + { + var binding = new PseudoParameterBinder() + .DoPseudoParameterBinding( + command, + pipeArgumentType: null, + paramAstAtCursor: null, + PseudoParameterBinder.BindingType.ParameterCompletion); + + if (binding is null) + { + return null; + } + + var results = new List(); + foreach (var parameter in binding.UnboundParameters) + { + if (!excludedKeys.Contains(parameter.Parameter.Name) + && (wordToComplete is null || parameter.Parameter.Name.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase))) + { + results.Add(new CompletionResult(parameter.Parameter.Name, parameter.Parameter.Name, CompletionResultType.ParameterName, $"[{parameter.Parameter.Type.Name}]")); } } + + if (results.Count > 0) + { + return results; + } } } return null; } - private static List GetSpecialHashTableKeyMembers(params string[] keys) + private static List GetSpecialHashTableKeyMembers(HashSet excludedKeys, string wordToComplete, params string[] keys) { - // Resources were removed because they missed the deadline for loc. - // return keys.Select(key => new CompletionResult(key, key, CompletionResultType.Property, - // ResourceManagerCache.GetResourceString(typeof(CompletionCompleters).Assembly, - // "TabCompletionStrings", key + "HashKeyDescription"))).ToList(); - return keys.Select(key => new CompletionResult(key, key, CompletionResultType.Property, key)).ToList(); + var result = new List(); + foreach (string key in keys) + { + if ((string.IsNullOrEmpty(wordToComplete) || key.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)) && !excludedKeys.Contains(key)) + { + string toolTip = GetHashtableKeyDescriptionToolTip(key); + + result.Add(new CompletionResult(key, key, CompletionResultType.Property, toolTip)); + } + } + + if (result.Count == 0) + { + return null; + } + + 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 @@ -6483,66 +8497,69 @@ internal static bool IsPathSafelyExpandable(ExpandableStringExpressionAst expand internal static string CombineVariableWithPartialPath(VariableExpressionAst variableAst, string extraText, ExecutionContext executionContext) { var varPath = variableAst.VariablePath; - if (varPath.IsVariable || varPath.DriveName.Equals("env", StringComparison.OrdinalIgnoreCase)) + if (!varPath.IsVariable && !varPath.DriveName.Equals("env", StringComparison.OrdinalIgnoreCase)) { - try - { - // We check the strict mode inside GetVariableValue - object value = VariableOps.GetVariableValue(varPath, executionContext, variableAst); - var strValue = (value == null) ? string.Empty : value as string; + return null; + } - if (strValue == null) - { - object baseObj = PSObject.Base(value); - if (baseObj is string || baseObj.GetType().IsPrimitive) - { - strValue = LanguagePrimitives.ConvertTo(value); - } - } + if (varPath.UnqualifiedPath.Equals(SpecialVariables.PSScriptRoot, StringComparison.OrdinalIgnoreCase) + && !string.IsNullOrEmpty(variableAst.Extent.File)) + { + return Path.GetDirectoryName(variableAst.Extent.File) + extraText; + } + + try + { + // We check the strict mode inside GetVariableValue + object value = VariableOps.GetVariableValue(varPath, executionContext, variableAst); + var strValue = (value == null) ? string.Empty : value as string; - if (strValue != null) + if (strValue == null) + { + object baseObj = PSObject.Base(value); + if (baseObj is string || baseObj?.GetType()?.IsPrimitive is true) { - return strValue + extraText; + strValue = LanguagePrimitives.ConvertTo(value); } } - catch (Exception) + + if (strValue != null) { + return strValue + extraText; } } + catch (Exception) + { + } return null; } - internal static string HandleDoubleAndSingleQuote(ref string wordToComplete) + /// + /// Calls Get-Command to get command info objects. + /// + /// The fake bound parameters. + /// The parameters to add. + /// Collection of command info objects. + internal static Collection 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 commands = ps.Invoke(); + + return commands; } internal static bool IsSplattedVariable(Ast targetExpr) @@ -6610,7 +8627,7 @@ internal static void CompleteMemberHelper( var completionText = memberInfo.Name; // Handle scenarios like this: $aa | add-member 'a b' 23; $aa.a - if (completionText.IndexOfAny(s_charactersRequiringQuotes) != -1) + if (ContainsCharactersRequiringQuotes(completionText)) { completionText = completionText.Replace("'", "''"); completionText = "'" + completionText + "'"; @@ -6657,7 +8674,7 @@ internal static void CompleteMemberHelper( if (pattern.IsMatch(key)) { // Handle scenarios like this: $hashtable["abc#d"] = 100; $hashtable.ab - if (key.IndexOfAny(s_charactersRequiringQuotes) != -1) + if (ContainsCharactersRequiringQuotes(key)) { key = key.Replace("'", "''"); key = "'" + key + "'"; @@ -6715,32 +8732,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(':'); @@ -6807,7 +8798,7 @@ internal static bool IsAmpersandNeeded(CompletionContext context, bool defaultCh return defaultChoice; } - private class ItemPathComparer : IComparer + private sealed class ItemPathComparer : IComparer { public int Compare(PSObject x, PSObject y) { @@ -6842,7 +8833,7 @@ public int Compare(PSObject x, PSObject y) } } - private class CommandNameComparer : IComparer + private sealed class CommandNameComparer : IComparer { public int Compare(PSObject x, PSObject y) { @@ -6869,7 +8860,7 @@ public int Compare(PSObject x, PSObject y) } /// - /// This class is very similar to the restricted langauge checker, but it is meant to allow more things, yet still + /// This class is very similar to the restricted language checker, but it is meant to allow more things, yet still /// be considered "safe", at least in the sense that tab completion can rely on it to not do bad things. The primary /// use is for intellisense where you don't want to run arbitrary code, but you do want to know the values /// of various expressions so you can get the members. @@ -7125,7 +9116,7 @@ public PropertyNameCompleter() /// /// Initializes a new instance of the class. /// - /// The name of the property of the input object for witch to complete with property names. + /// The name of the property of the input object for which to complete with property names. public PropertyNameCompleter(string parameterNameOfInput) { _parameterNameOfInput = parameterNameOfInput; 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..d54f4d5dc87 --- /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 +{ + /// + /// Shared helper class for common completion helper methods. + /// + internal static class CompletionHelpers + { + private static readonly SearchValues s_defaultCharsToCheck = SearchValues.Create("$`"); + + private const string SingleQuote = "'"; + private const string DoubleQuote = "\""; + + /// + /// Get matching completions from word to complete. + /// This makes it easier to handle different variations of completions with consideration of quotes. + /// + /// The word to complete. + /// The possible completion values to iterate. + /// The optional completion display info mapper delegate for tool tip and list item text. + /// The optional completion result type. Default is Text. + /// The optional match strategy delegate. + /// List of matching completion results. + internal static IEnumerable GetMatchingResults( + string wordToComplete, + IEnumerable 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); + } + } + } + + /// + /// Provides the display information for a completion result. + /// This delegate is used to map a string value to its corresponding display information. + /// + /// The input value to be mapped + /// Completion display info containing tool tip and list item text. + internal delegate (string ToolTip, string ListItemText) CompletionDisplayInfoMapper(string value); + + /// + /// Provides the default display information for a completion result. + /// Defaults to using the input value for both the tool tip and list item text. + /// + /// Completion display info containing tool tip and list item text. + internal static readonly CompletionDisplayInfoMapper DefaultDisplayInfoMapper = value + => (value, value); + + /// + /// Normalizes the input string to an expandable string format for PowerShell. + /// + /// The input string to be normalized. + /// The normalized string with special characters replaced by their PowerShell escape sequences. + /// + /// This method replaces special characters in the input string with their PowerShell equivalent escape sequences: + /// + /// Replaces "\r" (carriage return) with "`r". + /// Replaces "\n" (newline) with "`n". + /// Replaces "\t" (tab) with "`t". + /// Replaces "\0" (null) with "`0". + /// Replaces "\a" (bell) with "`a". + /// Replaces "\b" (backspace) with "`b". + /// Replaces "\u001b" (escape character) with "`e". + /// Replaces "\f" (form feed) with "`f". + /// Replaces "\v" (vertical tab) with "`v". + /// + /// + 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"); + + /// + /// Defines a strategy for determining if a value matches a word or pattern. + /// + /// The input string to check for a match. + /// The word or pattern to match against. + /// + /// true if the value matches the specified word or pattern; otherwise, false. + /// + internal delegate bool MatchStrategy(string value, string wordToComplete); + + /// + /// Determines if the given value matches the specified word using a literal, case-insensitive prefix match. + /// + /// + /// true if the value starts with the word (case-insensitively); otherwise, false. + /// + internal static readonly MatchStrategy LiteralMatchOrdinalIgnoreCase = (value, wordToComplete) + => value.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase); + + /// + /// Determines if the given value matches the specified word using wildcard pattern matching. + /// + /// + /// true if the value matches the word as a wildcard pattern; otherwise, false. + /// + /// + /// Wildcard pattern matching allows for flexible matching, where wilcards can represent + /// multiple characters in the input. This strategy is case-insensitive. + /// + internal static readonly MatchStrategy WildcardPatternMatchIgnoreCase = (value, wordToComplete) + => WildcardPattern + .Get(wordToComplete + "*", WildcardOptions.IgnoreCase) + .IsMatch(value); + + /// + /// Determines if the given value matches the specified word considering wildcard characters literally. + /// + /// + /// true if the value matches either the literal normalized word or the wildcard pattern with escaping; + /// otherwise, false. + /// + /// + /// 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. + /// + internal static readonly MatchStrategy WildcardPatternEscapeMatch = (value, wordToComplete) + => LiteralMatchOrdinalIgnoreCase(value, wordToComplete) || + WildcardPatternMatchIgnoreCase(value, WildcardPattern.Escape(wordToComplete)); + + /// + /// Determines if the given value matches the specified word taking into account wildcard characters. + /// + /// + /// true if the value matches either the literal normalized word or the wildcard pattern; otherwise, false. + /// + /// + /// This strategy attempts a literal match first for performance and, if unsuccessful, evaluates the word against a wildcard pattern. + /// + internal static readonly MatchStrategy DefaultMatch = (value, wordToComplete) + => LiteralMatchOrdinalIgnoreCase(value, wordToComplete) || + WildcardPatternMatchIgnoreCase(value, wordToComplete); + + /// + /// Removes wrapping quotes from a string and returns the quote used, if present. + /// + /// + /// The string to process, potentially surrounded by single or double quotes. + /// This parameter is updated in-place to exclude the removed quotes. + /// + /// + /// The type of quote detected (single or double), or an empty string if no quote is found. + /// + /// + /// 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. + /// + 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; + } + + /// + /// Determines whether the specified completion string requires quotes. + /// Quoting is required if: + /// + /// There are parsing errors in the input string. + /// The parsed token count is not exactly two (the input token + EOF). + /// The first token is a string or a PowerShell keyword containing special characters. + /// The first token is a semi colon or comma token. + /// + /// + /// The input string to analyze for quoting requirements. + /// true if the string requires quotes, false otherwise. + 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; + } + + /// + /// Determines whether the given text contains an escaped newline string. + /// + /// The input string to check for escaped newlines. + /// + /// true if the text contains the escaped Unix-style newline string ("`n") or + /// the Windows-style newline string ("`r`n"); otherwise, false. + /// + private static bool ContainsEscapedNewlineString(string text) + => text.Contains("`n", StringComparison.Ordinal); + + private static bool ContainsCharsToCheck(ReadOnlySpan text) + => text.ContainsAny(s_defaultCharsToCheck); + + /// + /// Quotes a given completion text. + /// + /// + /// The text to be quoted. + /// + /// + /// The quote character to use for enclosing the text. Defaults to a single quote if not provided. + /// + /// + /// The quoted . + /// + 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/ExtensibleCompletion.cs b/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs index 777fc6c16f4..2b5281a8f26 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs @@ -32,7 +32,7 @@ public class ArgumentCompleterAttribute : Attribute /// The type must implement and have a default constructor. public ArgumentCompleterAttribute(Type type) { - if (type == null || (type.GetInterfaces().All(t => t != typeof(IArgumentCompleter)))) + if (type == null || (type.GetInterfaces().All(static t => t != typeof(IArgumentCompleter)))) { throw PSTraceSource.NewArgumentException(nameof(type)); } @@ -40,19 +40,40 @@ public ArgumentCompleterAttribute(Type type) Type = type; } + /// + /// Initializes a new instance of the class. + /// This constructor is used by derived attributes implementing . + /// + protected ArgumentCompleterAttribute() + { + if (this is not IArgumentCompleterFactory) + { + throw PSTraceSource.NewInvalidOperationException(); + } + } + /// /// This constructor is used primarily via PowerShell scripts. /// /// public ArgumentCompleterAttribute(ScriptBlock scriptBlock) { - if (scriptBlock == null) + if (scriptBlock is null) { throw PSTraceSource.NewArgumentNullException(nameof(scriptBlock)); } ScriptBlock = scriptBlock; } + + internal IArgumentCompleter CreateArgumentCompleter() + { + return Type != null + ? Activator.CreateInstance(Type) as IArgumentCompleter + : this is IArgumentCompleterFactory factory + ? factory.Create() + : null; + } } /// @@ -85,6 +106,67 @@ IEnumerable CompleteArgument( } #nullable restore + /// + /// Creates a new argument completer. + /// + /// + /// If an attribute that derives from implements this interface, + /// it will be used to create the , thus giving a way to parameterize a completer. + /// The derived attribute can have properties or constructor arguments that are used when creating the completer. + /// + /// + /// This example shows the intended usage of to pass arguments to an argument completer. + /// + /// public class NumberCompleterAttribute : ArgumentCompleterAttribute, IArgumentCompleterFactory { + /// private readonly int _from; + /// private readonly int _to; + /// + /// public NumberCompleterAttribute(int from, int to){ + /// _from = from; + /// _to = to; + /// } + /// + /// // use the attribute parameters to create a parameterized completer + /// IArgumentCompleter Create() => new NumberCompleter(_from, _to); + /// } + /// + /// class NumberCompleter : IArgumentCompleter { + /// private readonly int _from; + /// private readonly int _to; + /// + /// public NumberCompleter(int from, int to){ + /// _from = from; + /// _to = to; + /// } + /// + /// IEnumerable{CompletionResult} CompleteArgument(string commandName, string parameterName, string wordToComplete, + /// CommandAst commandAst, IDictionary fakeBoundParameters) { + /// for(int i = _from; i < _to; i++) { + /// yield return new CompletionResult(i.ToString()); + /// } + /// } + /// } + /// + /// + public interface IArgumentCompleterFactory + { + /// + /// Creates an instance of a class implementing the interface. + /// + /// An IArgumentCompleter instance. + IArgumentCompleter Create(); + } + + /// + /// Base class for parameterized argument completer attributes. + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public abstract class ArgumentCompleterFactoryAttribute : ArgumentCompleterAttribute, IArgumentCompleterFactory + { + /// + public abstract IArgumentCompleter Create(); + } + /// /// [Cmdlet(VerbsLifecycle.Register, "ArgumentCompleter", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=528576")] diff --git a/src/System.Management.Automation/engine/CommandCompletion/PseudoParameterBinder.cs b/src/System.Management.Automation/engine/CommandCompletion/PseudoParameterBinder.cs index 0d195280239..2e7457cd812 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/PseudoParameterBinder.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/PseudoParameterBinder.cs @@ -949,8 +949,9 @@ internal enum BindingType /// Indicate the type of the piped-in argument. /// The CommandParameterAst the cursor is pointing at. /// Indicates whether pseudo binding is for argument binding, argument completion, or parameter completion. + /// Indicates if the pseudo binding should bind positional parameters /// PseudoBindingInfo. - internal PseudoBindingInfo DoPseudoParameterBinding(CommandAst command, Type pipeArgumentType, CommandParameterAst paramAstAtCursor, BindingType bindingType) + internal PseudoBindingInfo DoPseudoParameterBinding(CommandAst command, Type pipeArgumentType, CommandParameterAst paramAstAtCursor, BindingType bindingType, bool bindPositional = true) { if (command == null) { @@ -981,7 +982,7 @@ internal PseudoBindingInfo DoPseudoParameterBinding(CommandAst command, Type pip executionContext.LanguageMode = PSLanguageMode.ConstrainedLanguage; } - _bindingEffective = PrepareCommandElements(executionContext); + _bindingEffective = PrepareCommandElements(executionContext, paramAstAtCursor); } finally { @@ -1008,12 +1009,15 @@ internal PseudoBindingInfo DoPseudoParameterBinding(CommandAst command, Type pip unboundArguments = BindNamedParameters(); _bindingEffective = _currentParameterSetFlag != 0; - // positional binding - unboundArguments = BindPositionalParameter( - unboundArguments, - _currentParameterSetFlag, - _defaultParameterSetFlag, - bindingType); + if (bindPositional) + { + // positional binding + unboundArguments = BindPositionalParameter( + unboundArguments, + _currentParameterSetFlag, + _defaultParameterSetFlag, + bindingType); + } // VFRA/pipeline binding if the given command is a binary cmdlet or a script cmdlet if (!_function) @@ -1185,7 +1189,7 @@ private void InitializeMembers() _duplicateParameters.Clear(); } - private bool PrepareCommandElements(ExecutionContext context) + private bool PrepareCommandElements(ExecutionContext context, CommandParameterAst paramAtCursor) { int commandIndex = 0; bool dotSource = _commandAst.InvocationOperator == TokenKind.Dot; @@ -1194,7 +1198,7 @@ private bool PrepareCommandElements(ExecutionContext context) 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) { @@ -1207,20 +1211,46 @@ private bool PrepareCommandElements(ExecutionContext context) bool implementsDynamicParameters = commandProcessor != null && commandProcessor.CommandInfo.ImplementsDynamicParameters; - var argumentsToGetDynamicParameters = implementsDynamicParameters - ? new List(_commandElements.Count) - : null; if (commandProcessor != null || scriptProcessor != null) { // Pre-processing the arguments -- command arguments for (commandIndex++; commandIndex < _commandElements.Count; commandIndex++) { + if (implementsDynamicParameters && _commandElements[commandIndex] == paramAtCursor) + { + // Commands with dynamic parameters will try to bind the command elements. + // A partially complete parameter will most likely cause a binding error and negatively affect the results. + continue; + } + var parameter = _commandElements[commandIndex] as CommandParameterAst; if (parameter != null) { - if (argumentsToGetDynamicParameters != null) + if (implementsDynamicParameters) { - argumentsToGetDynamicParameters.Add(parameter.Extent.Text); + CommandParameterInternal paramToAdd; + if (parameter.Argument is null) + { + paramToAdd = CommandParameterInternal.CreateParameter(parameter.ParameterName, parameter.Extent.Text); + } + else + { + object value; + if (!SafeExprEvaluator.TrySafeEval(parameter.Argument, context, out value)) + { + value = parameter.Argument.Extent.Text; + } + + paramToAdd = CommandParameterInternal.CreateParameterWithArgument( + parameterAst: null, + parameterName: parameter.ParameterName, + parameterText: parameter.Extent.Text, + argumentAst: null, + value: value, + spaceAfterParameter: false); + } + + commandProcessor.AddParameter(paramToAdd); } AstPair parameterArg = parameter.Argument != null @@ -1231,21 +1261,40 @@ private bool PrepareCommandElements(ExecutionContext context) } else { - var dash = _commandElements[commandIndex] as StringConstantExpressionAst; - if (dash != null && dash.Value.Trim().Equals("-", StringComparison.OrdinalIgnoreCase)) + object valueToAdd; + ExpressionAst expressionToAdd; + if (_commandElements[commandIndex] is ConstantExpressionAst constant) { - // "-" is represented by StringConstantExpressionAst. Most likely the user type a tab here, - // and we don't want it be treated as an argument - continue; + if (constant.Extent.Text.Equals("-", StringComparison.Ordinal)) + { + // A value of "-" is most likely the user trying to tab here, + // and we don't want it be treated as an argument + continue; + } + + valueToAdd = constant.Value; + expressionToAdd = constant; } + else if (_commandElements[commandIndex] is ExpressionAst expression) + { + if (!SafeExprEvaluator.TrySafeEval(expression, context, out valueToAdd)) + { + valueToAdd = expression.Extent.Text; + } - var expressionArgument = _commandElements[commandIndex] as ExpressionAst; - if (expressionArgument != null) + expressionToAdd = expression; + } + else { - argumentsToGetDynamicParameters?.Add(expressionArgument.Extent.Text); + continue; + } - _arguments.Add(new AstPair(null, expressionArgument)); + if (implementsDynamicParameters) + { + commandProcessor.AddParameter(CommandParameterInternal.CreateArgument(valueToAdd)); } + + _arguments.Add(new AstPair(null, expressionToAdd)); } } } @@ -1255,7 +1304,6 @@ private bool PrepareCommandElements(ExecutionContext context) _function = false; if (implementsDynamicParameters) { - ParameterBinderController.AddArgumentsToCommandProcessor(commandProcessor, argumentsToGetDynamicParameters.ToArray()); bool retryWithNoArgs = false, alreadyRetried = false; do @@ -1358,7 +1406,6 @@ private CommandProcessorBase PrepareFromAst(ExecutionContext context, out string } ast.Visit(exportVisitor); - CommandProcessorBase commandProcessor = null; resolvedCommandName = _commandAst.GetCommandName(); @@ -1862,6 +1909,13 @@ private static AstParameterArgumentPair GetNextPositionalArgument( while (unboundArgumentsIndex < unboundArgumentsCollection.Count) { AstParameterArgumentPair argument = unboundArgumentsCollection[unboundArgumentsIndex++]; + if (argument is AstPair astPair + && astPair.Argument is VariableExpressionAst argumentVariable + && argumentVariable.Splatted) + { + continue; + } + if (!argument.ParameterSpecified) { result = argument; diff --git a/src/System.Management.Automation/engine/CommandCompletion/ScopeArgumentCompleter.cs b/src/System.Management.Automation/engine/CommandCompletion/ScopeArgumentCompleter.cs new file mode 100644 index 00000000000..444ccf79adb --- /dev/null +++ b/src/System.Management.Automation/engine/CommandCompletion/ScopeArgumentCompleter.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections; +using System.Collections.Generic; +using System.Management.Automation.Language; + +namespace System.Management.Automation +{ + /// + /// Provides argument completion for Scope parameter. + /// + public class ScopeArgumentCompleter : IArgumentCompleter + { + private static readonly string[] s_Scopes = new string[] { "Global", "Local", "Script" }; + + /// + /// Returns completion results for scope parameter. + /// + /// The command name. + /// The parameter name. + /// The word to complete. + /// The command AST. + /// The fake bound parameters. + /// List of completion results. + public IEnumerable CompleteArgument( + string commandName, + string parameterName, + string wordToComplete, + CommandAst commandAst, + IDictionary fakeBoundParameters) + => 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 ee6e5943800..cf4f561c983 100644 --- a/src/System.Management.Automation/engine/CommandDiscovery.cs +++ b/src/System.Management.Automation/engine/CommandDiscovery.cs @@ -55,7 +55,7 @@ internal CommandLookupEventArgs(string commandName, CommandOrigin commandOrigin, public bool StopSearch { get; set; } /// - /// The CommandInfo obejct for the command that was found. + /// The CommandInfo object for the command that was found. /// public CommandInfo Command { get; set; } @@ -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. /// + /// + /// True if this for parameter completion and script requirements should be ignored. + /// /// /// /// @@ -271,14 +274,15 @@ internal void AddSessionStateCmdletEntryToCache(SessionStateCmdletEntry entry, b /// If the security manager is preventing the command from running. /// 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,12 +305,12 @@ 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( scriptInfo.Name, - new Collection { requiredModule.Name }, + new Collection { requiredModule.GetRequiredModuleNotFoundVersionMessage() }, "ScriptRequiresMissingModules", false, error); @@ -316,113 +320,42 @@ internal static void VerifyRequiredModules(ExternalScriptInfo scriptInfo, Execut } } - private static Collection GetPSSnapinNames(IEnumerable PSSnapins) - { - Collection result = new Collection(); - - foreach (var PSSnapin in PSSnapins) - { - result.Add(BuildPSSnapInDisplayName(PSSnapin)); - } - - return result; - } - - 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); - IEnumerable requiresPSSnapIns = scriptInfo.RequiresPSSnapIns; - if (requiresPSSnapIns != null && requiresPSSnapIns.Any()) + if (!string.IsNullOrEmpty(scriptInfo.RequiresApplicationID)) { - Collection requiresMissingPSSnapIns = null; - VerifyRequiredSnapins(requiresPSSnapIns, context, out requiresMissingPSSnapIns); - if (requiresMissingPSSnapIns != null) - { - ScriptRequiresException scriptRequiresException = - new ScriptRequiresException( - scriptInfo.Name, - requiresMissingPSSnapIns, - "ScriptRequiresMissingPSSnapIns", - true); - throw scriptRequiresException; - } - } - else - { - // If there were no PSSnapins required but there is a shellID required, then we need - // to error + ScriptRequiresException sre = + new ScriptRequiresException( + scriptInfo.Name, + string.Empty, + string.Empty, + "RequiresShellIDInvalidForSingleShell"); - if (!string.IsNullOrEmpty(scriptInfo.RequiresApplicationID)) - { - ScriptRequiresException sre = - new ScriptRequiresException( - scriptInfo.Name, - string.Empty, - string.Empty, - "RequiresShellIDInvalidForSingleShell"); - - throw sre; - } + throw sre; } return CreateCommandProcessorForScript(scriptInfo, Context, useLocalScope, sessionState); } - private static void VerifyRequiredSnapins(IEnumerable requiresPSSnapIns, ExecutionContext context, out Collection requiresMissingPSSnapIns) - { - requiresMissingPSSnapIns = null; - Dbg.Assert(context.InitialSessionState != null, "PowerShell should be hosted with InitialSessionState"); - - foreach (var requiresPSSnapIn in requiresPSSnapIns) - { - IEnumerable loadedPSSnapIns = null; - loadedPSSnapIns = context.InitialSessionState.GetPSSnapIn(requiresPSSnapIn.Name); - if (loadedPSSnapIns == null || !loadedPSSnapIns.Any()) - { - if (requiresMissingPSSnapIns == null) - { - requiresMissingPSSnapIns = new Collection(); - } - - requiresMissingPSSnapIns.Add(BuildPSSnapInDisplayName(requiresPSSnapIn)); - } - else - { - // the requires PSSnapin is loaded. now check the PSSnapin version - PSSnapInInfo loadedPSSnapIn = loadedPSSnapIns.First(); - Diagnostics.Assert(loadedPSSnapIn.Version != null, - string.Format( - CultureInfo.InvariantCulture, - "Version is null for loaded PSSnapin {0}.", loadedPSSnapIn)); - if (requiresPSSnapIn.Version != null) - { - if (!AreInstalledRequiresVersionsCompatible( - requiresPSSnapIn.Version, loadedPSSnapIn.Version)) - { - if (requiresMissingPSSnapIns == null) - { - requiresMissingPSSnapIns = new Collection(); - } - - requiresMissingPSSnapIns.Add(BuildPSSnapInDisplayName(requiresPSSnapIn)); - } - } - } - } - } - // This method verifies the following 3 elements of #Requires statement // #Requires -RunAsAdministrator // #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) @@ -431,7 +364,7 @@ internal static void VerifyPSVersion(ExternalScriptInfo scriptInfo) // in single shell mode if (requiresPSVersion != null) { - if (!Utils.IsPSVersionSupported(requiresPSVersion)) + if (!PSVersionInfo.IsValidPSVersion(requiresPSVersion)) { ScriptRequiresException scriptRequiresException = new ScriptRequiresException( @@ -464,11 +397,11 @@ internal static void VerifyPSEdition(ExternalScriptInfo scriptInfo) // if (isRequiresPSEditionSpecified && !isCurrentEditionListed) { - var specifiedEditionsString = string.Join(",", scriptInfo.RequiresPSEditions); + var specifiedEditionsString = string.Join(',', scriptInfo.RequiresPSEditions); var message = StringUtil.Format(DiscoveryExceptions.RequiresPSEditionNotCompatible, scriptInfo.Name, specifiedEditionsString, - PSVersionInfo.PSEdition); + PSVersionInfo.PSEditionValue); var ex = new RuntimeException(message); ex.SetErrorId("ScriptRequiresUnmatchedPSEdition"); ex.SetTargetObject(scriptInfo.Name); @@ -491,32 +424,6 @@ internal static void VerifyElevatedPrivileges(ExternalScriptInfo scriptInfo) } } - /// - /// Used to determine compatibility between the versions in the requires statement and - /// the installed version. The version can be PSSnapin or msh. - /// - /// Versions in the requires statement. - /// Version installed. - /// - /// true if requires and installed's major version match and requires' minor version - /// is smaller than or equal to installed's - /// - /// - /// In PowerShell V2, script requiring PowerShell 1.0 will fail. - /// - private static bool AreInstalledRequiresVersionsCompatible(Version requires, Version installed) - { - return requires.Major == installed.Major && requires.Minor <= installed.Minor; - } - - private static string BuildPSSnapInDisplayName(PSSnapInSpecification PSSnapin) - { - return PSSnapin.Version == null ? - PSSnapin.Name : - StringUtil.Format(DiscoveryExceptions.PSSnapInNameVersion, - PSSnapin.Name, PSSnapin.Version); - } - /// /// Look up a command using a CommandInfo object and return its CommandProcessorBase. /// @@ -529,6 +436,9 @@ private static string BuildPSSnapInDisplayName(PSSnapInSpecification PSSnapin) /// False if not. Null if command discovery should default to something reasonable /// for the command discovered. /// + /// + /// True if this for parameter completion and script requirements should be ignored. + /// /// The session state the commandInfo should be run in. /// /// @@ -539,7 +449,7 @@ private static string BuildPSSnapInDisplayName(PSSnapInSpecification PSSnapin) /// If the security manager is preventing the command from running. /// internal CommandProcessorBase LookupCommandProcessor(CommandInfo commandInfo, - CommandOrigin commandOrigin, bool? useLocalScope, SessionStateInternal sessionState) + CommandOrigin commandOrigin, bool? useLocalScope, SessionStateInternal sessionState, bool forCompletion = false) { CommandProcessorBase processor = null; @@ -585,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) { @@ -819,10 +729,7 @@ internal static CommandInfo LookupCommandInfo( } // Otherwise, invoke the CommandNotFound handler - if (result == null) - { - result = InvokeCommandNotFoundHandler(commandName, context, originalCommandName, commandOrigin); - } + result ??= InvokeCommandNotFoundHandler(commandName, context, originalCommandName, commandOrigin); } while (false); } else @@ -1053,31 +960,26 @@ private static CommandInfo TryModuleAutoDiscovery(string commandName, if (etwEnabled) CommandDiscoveryEventSource.Log.ModuleAutoDiscoveryStart(commandName); CommandInfo result = null; - bool cleanupModuleAnalysisAppDomain = false; try { // If commandName had a slash, it was module-qualified or path-qualified. // In that case, we should not return anything (module-qualified is handled // by the previous call to TryModuleAutoLoading(). - int colonOrBackslash = commandName.IndexOfAny(Utils.Separators.ColonOrBackslash); + int colonOrBackslash = commandName.AsSpan().IndexOfAny('\\', ':'); if (colonOrBackslash != -1) return null; CmdletInfo cmdletInfo = context.SessionState.InvokeCommand.GetCmdlet("Microsoft.PowerShell.Core\\Get-Module"); - if ((commandOrigin == CommandOrigin.Internal) || - ((cmdletInfo != null) && (cmdletInfo.Visibility == SessionStateEntryVisibility.Public))) + if (commandOrigin == CommandOrigin.Internal || cmdletInfo?.Visibility == SessionStateEntryVisibility.Public) { // Search for a module with a matching command, as long as the user would have the ability to // import the module. cmdletInfo = context.SessionState.InvokeCommand.GetCmdlet("Microsoft.PowerShell.Core\\Import-Module"); - if (((commandOrigin == CommandOrigin.Internal) || - ((cmdletInfo != null) && (cmdletInfo.Visibility == SessionStateEntryVisibility.Public)))) + if (commandOrigin == CommandOrigin.Internal || cmdletInfo?.Visibility == SessionStateEntryVisibility.Public) { discoveryTracer.WriteLine("Executing non module-qualified search: {0}", commandName); context.CommandDiscovery.RegisterLookupCommandInfoAction("ActiveModuleSearch", commandName); - cleanupModuleAnalysisAppDomain = context.TakeResponsibilityForModuleAnalysisAppDomain(); - // Get the available module files, preferring modules from $PSHOME so that user modules don't // override system modules during auto-loading if (etwEnabled) CommandDiscoveryEventSource.Log.SearchingForModuleFilesStart(); @@ -1088,30 +990,33 @@ private static CommandInfo TryModuleAutoDiscovery(string commandName, { // WinBlue:69141 - We need to get the full path here because the module path might be C:\Users\User1\DOCUME~1 // While the exportedCommands are cached, they are cached with the full path - string expandedModulePath = IO.Path.GetFullPath(modulePath); - string moduleShortName = System.IO.Path.GetFileNameWithoutExtension(expandedModulePath); + string expandedModulePath = Path.GetFullPath(modulePath); + string moduleShortName = Path.GetFileNameWithoutExtension(expandedModulePath); var exportedCommands = AnalysisCache.GetExportedCommands(expandedModulePath, false, context); if (exportedCommands == null) { continue; } - CommandTypes exportedCommandTypes; // Skip if module only has class or other types and no commands. - if (exportedCommands.TryGetValue(commandName, out exportedCommandTypes)) + if (exportedCommands.TryGetValue(commandName, out CommandTypes exportedCommandTypes)) { - Exception exception; discoveryTracer.WriteLine("Found in module: {0}", expandedModulePath); - Collection matchingModule = AutoloadSpecifiedModule(expandedModulePath, context, + Collection matchingModule = AutoloadSpecifiedModule( + expandedModulePath, + context, cmdletInfo != null ? cmdletInfo.Visibility : SessionStateEntryVisibility.Private, - out exception); - lastError = exception; - if ((matchingModule == null) || (matchingModule.Count == 0)) + out lastError); + + if (matchingModule is null || matchingModule.Count == 0) { - string error = StringUtil.Format(DiscoveryExceptions.CouldNotAutoImportMatchingModule, commandName, moduleShortName); - CommandNotFoundException commandNotFound = new CommandNotFoundException( + string errorMessage = lastError is null + ? StringUtil.Format(DiscoveryExceptions.CouldNotAutoImportMatchingModule, commandName, moduleShortName) + : StringUtil.Format(DiscoveryExceptions.CouldNotAutoImportMatchingModuleWithErrorMessage, commandName, moduleShortName, lastError.Message); + + throw new CommandNotFoundException( originalCommandName, lastError, - "CouldNotAutoloadMatchingModule", error); - throw commandNotFound; + "CouldNotAutoloadMatchingModule", + errorMessage); } result = LookupCommandInfo(commandName, commandTypes, searchResolutionOptions, commandOrigin, context); @@ -1142,10 +1047,6 @@ private static CommandInfo TryModuleAutoDiscovery(string commandName, finally { context.CommandDiscovery.UnregisterLookupCommandInfoAction("ActiveModuleSearch", commandName); - if (cleanupModuleAnalysisAppDomain) - { - context.ReleaseResponsibilityForModuleAnalysisAppDomain(); - } } if (etwEnabled) CommandDiscoveryEventSource.Log.ModuleAutoDiscoveryStop(commandName); @@ -1158,7 +1059,7 @@ private static CommandInfo TryModuleAutoLoading(string commandName, ExecutionCon CommandInfo result = null; // If commandName was module-qualified. In that case, we should load the module. - var colonOrBackslash = commandName.IndexOfAny(Utils.Separators.ColonOrBackslash); + var colonOrBackslash = commandName.AsSpan().IndexOfAny('\\', ':'); // If we don't see '\', there is no module specified, so no module to load. // If we see ':' before '\', then we probably have a drive qualified path, not a module name @@ -1169,7 +1070,7 @@ private static CommandInfo TryModuleAutoLoading(string commandName, ExecutionCon string moduleName; // Now we check if there exists the second '\' - var secondBackslash = moduleCommandName.IndexOfAny(Utils.Separators.Backslash); + var secondBackslash = moduleCommandName.IndexOf('\\'); if (secondBackslash == -1) { moduleName = commandName.Substring(0, colonOrBackslash); @@ -1263,10 +1164,8 @@ internal void RegisterLookupCommandInfoAction(string currentAction, string comma case "ActivePostCommand": currentActionSet = _activePostCommand; break; } - if (currentActionSet.Contains(command)) + if (!currentActionSet.Add(command)) throw new InvalidOperationException(); - else - currentActionSet.Add(command); } internal void UnregisterLookupCommandInfoAction(string currentAction, string command) @@ -1280,8 +1179,7 @@ internal void UnregisterLookupCommandInfoAction(string currentAction, string com case "ActivePostCommand": currentActionSet = _activePostCommand; break; } - if (currentActionSet.Contains(command)) - currentActionSet.Remove(command); + currentActionSet.Remove(command); } private readonly HashSet _activePreLookup = new HashSet(StringComparer.OrdinalIgnoreCase); @@ -1325,7 +1223,7 @@ internal LookupPathCollection GetLookupDirectoryPaths() if (_pathCacheKey != null) { - string[] tokenizedPath = _pathCacheKey.Split(Utils.Separators.PathSeparator, StringSplitOptions.RemoveEmptyEntries); + string[] tokenizedPath = _pathCacheKey.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries); _cachedPath = new Collection(); foreach (string directory in tokenizedPath) @@ -1417,7 +1315,7 @@ private static void InitPathExtCache(string pathExt) lock (s_lockObject) { s_cachedPathExtCollection = pathExt != null - ? pathExt.Split(Utils.Separators.PathSeparator, StringSplitOptions.RemoveEmptyEntries) + ? pathExt.ToLower().Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries) : Array.Empty(); s_cachedPathExtCollectionWithPs1 = new string[s_cachedPathExtCollection.Length + 1]; s_cachedPathExtCollectionWithPs1[0] = StringLiterals.PowerShellScriptFileExtension; @@ -1489,7 +1387,7 @@ internal IEnumerator GetCmdletInfo(string cmdletName, bool searchAll } // The engine cmdlets get imported (via Import-Module) once when PowerShell starts and the cmdletInfo is added to PSSnapinHelpers._cmdletcache(static) with ModuleName // as "System.Management.Automation.dll" instead of the actual snapin name. The next time we load something in an InitialSessionState, we look at this _cmdletcache and - // if the the assembly is already loaded, we just return the cmdlets back. So, the CmdletInfo has moduleName has "System.Management.Automation.dll". So, when M3P Activity + // if the assembly is already loaded, we just return the cmdlets back. So, the CmdletInfo has moduleName has "System.Management.Automation.dll". So, when M3P Activity // tries to access Microsoft.PowerShell.Core\\Get-Command, it cannot. So, adding an additional check to return the correct cmdletInfo for cmdlets from core modules. else if (InitialSessionState.IsEngineModule(cmdletInfo.ModuleName)) { diff --git a/src/System.Management.Automation/engine/CommandInfo.cs b/src/System.Management.Automation/engine/CommandInfo.cs index 7538d9fb19f..eb5fbf70f3b 100644 --- a/src/System.Management.Automation/engine/CommandInfo.cs +++ b/src/System.Management.Automation/engine/CommandInfo.cs @@ -17,33 +17,27 @@ namespace System.Management.Automation { /// - /// Defines the types of commands that MSH can execute. + /// Defines the types of commands that PowerShell can execute. /// [Flags] public enum CommandTypes { /// /// Aliases create a name that refers to other command types. - /// - /// /// Aliases are only persisted within the execution of a single engine. - /// + /// Alias = 0x0001, /// /// Script functions that are defined by a script block. - /// - /// /// Functions are only persisted within the execution of a single engine. - /// + /// Function = 0x0002, /// /// Script filters that are defined by a script block. - /// - /// /// Filters are only persisted within the execution of a single engine. - /// + /// Filter = 0x0004, /// @@ -52,17 +46,15 @@ public enum CommandTypes Cmdlet = 0x0008, /// - /// An MSH script (*.ps1 file) + /// An PowerShell script (*.ps1 file) /// ExternalScript = 0x0010, /// /// Any existing application (can be console or GUI). - /// - /// /// An application can have any extension that can be executed either directly through CreateProcess /// or indirectly through ShellExecute. - /// + /// Application = 0x0020, /// @@ -77,11 +69,9 @@ public enum CommandTypes /// /// All possible command types. + /// NOTE: a CommandInfo instance will never specify All as its CommandType + /// but All can be used when filtering the CommandTypes. /// - /// - /// Note, a CommandInfo instance will never specify - /// All as its CommandType but All can be used when filtering the CommandTypes. - /// All = Alias | Function | Filter | Cmdlet | Script | ExternalScript | Application | Configuration, } @@ -110,10 +100,7 @@ internal CommandInfo(string name, CommandTypes type) // The name can be empty for functions and filters but it // can't be null - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } + ArgumentNullException.ThrowIfNull(name); Name = name; CommandType = type; @@ -288,10 +275,7 @@ internal void SetCommandType(CommandTypes newType) /// internal void Rename(string newName) { - if (string.IsNullOrEmpty(newName)) - { - throw new ArgumentNullException(nameof(newName)); - } + ArgumentException.ThrowIfNullOrEmpty(newName); Name = newName; } @@ -467,11 +451,8 @@ private MergedCommandParameterMetadata GetMergedCommandParameterMetadataSafely() processInCurrentThread: true, waitForCompletionInCurrentThread: true); - if (eventArgs.Exception != null) - { - // An exception happened on a different thread, rethrow it here on the correct thread. - eventArgs.Exception.Throw(); - } + // An exception happened on a different thread, rethrow it here on the correct thread. + eventArgs.Exception?.Throw(); return eventArgs.Result; } @@ -481,7 +462,7 @@ private MergedCommandParameterMetadata GetMergedCommandParameterMetadataSafely() return result; } - private class GetMergedCommandParameterMetadataSafelyEventArgs : EventArgs + private sealed class GetMergedCommandParameterMetadataSafelyEventArgs : EventArgs { public MergedCommandParameterMetadata Result; public ExceptionDispatchInfo Exception; @@ -529,7 +510,7 @@ private void GetMergedCommandParameterMetadata(out MergedCommandParameterMetadat processor = scriptCommand != null ? new CommandProcessor(scriptCommand, _context, useLocalScope: true, fromScriptFile: false, sessionState: scriptCommand.ScriptBlock.SessionStateInternal ?? Context.EngineSessionState) - : new CommandProcessor((CmdletInfo)this, _context) { UseLocalScope = true }; + : new CommandProcessor((CmdletInfo)this, _context); ParameterBinderController.AddArgumentsToCommandProcessor(processor, Arguments); CommandProcessorBase oldCurrentCommandProcessor = Context.CurrentCommandProcessor; @@ -928,7 +909,7 @@ public PSMemberNameAndType(string name, PSTypeName typeName, object value = null /// but can be used where a real type might not be available, in which case the name of the type can be used. /// The type encodes the members of dynamic objects in the type name. /// - internal class PSSyntheticTypeName : PSTypeName + internal sealed class PSSyntheticTypeName : PSTypeName { internal static PSSyntheticTypeName Create(string typename, IList membersTypes) => Create(new PSTypeName(typename), membersTypes); @@ -939,7 +920,7 @@ internal static PSSyntheticTypeName Create(PSTypeName typename, IList(); members.AddRange(membersTypes); - members.Sort((c1, c2) => string.Compare(c1.Name, c2.Name, StringComparison.OrdinalIgnoreCase)); + members.Sort(static (c1, c2) => string.Compare(c1.Name, c2.Name, StringComparison.OrdinalIgnoreCase)); return new PSSyntheticTypeName(typeName, typename.Type, members); } @@ -980,7 +961,7 @@ private static string GetMemberTypeProjection(string typename, IList m.Name)) + foreach (var m in members.OrderBy(static m => m.Name)) { if (!IsPSTypeName(m)) { diff --git a/src/System.Management.Automation/engine/CommandMetadata.cs b/src/System.Management.Automation/engine/CommandMetadata.cs index eb59f9140a2..df767f714b0 100644 --- a/src/System.Management.Automation/engine/CommandMetadata.cs +++ b/src/System.Management.Automation/engine/CommandMetadata.cs @@ -847,45 +847,40 @@ internal string GetProxyCommand(string helpComment, bool generateDynamicParamete { if (string.IsNullOrEmpty(helpComment)) { - helpComment = string.Format(CultureInfo.InvariantCulture, @" -.ForwardHelpTargetName {0} -.ForwardHelpCategory {1} -", - _wrappedCommand, _wrappedCommandType); + helpComment = string.Create(CultureInfo.InvariantCulture, $@" +.ForwardHelpTargetName {_wrappedCommand} +.ForwardHelpCategory {_wrappedCommandType} +"); } string dynamicParamblock = string.Empty; if (generateDynamicParameters && this.ImplementsDynamicParameters) { - dynamicParamblock = string.Format(CultureInfo.InvariantCulture, @" + dynamicParamblock = string.Create(CultureInfo.InvariantCulture, $@" dynamicparam -{{{0}}} +{{{GetDynamicParamBlock()}}} -", GetDynamicParamBlock()); +"); } - string result = string.Format(CultureInfo.InvariantCulture, @"{0} -param({1}) + string result = string.Create(CultureInfo.InvariantCulture, $@"{GetDecl()} +param({GetParamBlock()}) -{2}begin -{{{3}}} +{dynamicParamblock}begin +{{{GetBeginBlock()}}} process -{{{4}}} +{{{GetProcessBlock()}}} end -{{{5}}} +{{{GetEndBlock()}}} + +clean +{{{GetCleanBlock()}}} <# -{6} +{CodeGeneration.EscapeBlockCommentContent(helpComment)} #> -", - GetDecl(), - GetParamBlock(), - dynamicParamblock, - GetBeginBlock(), - GetProcessBlock(), - GetEndBlock(), - CodeGeneration.EscapeBlockCommentContent(helpComment)); +"); return result; } @@ -1014,9 +1009,10 @@ internal string GetBeginBlock() commandOrigin = string.Empty; } + string wrappedCommand = CodeGeneration.EscapeSingleQuotedStringContent(_wrappedCommand); if (_wrappedAnyCmdlet) { - result = string.Format(CultureInfo.InvariantCulture, @" + result = string.Create(CultureInfo.InvariantCulture, $@" try {{ $outBuffer = $null if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) @@ -1024,38 +1020,30 @@ internal string GetBeginBlock() $PSBoundParameters['OutBuffer'] = 1 }} - $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('{0}', [System.Management.Automation.CommandTypes]::{1}) + $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('{wrappedCommand}', [System.Management.Automation.CommandTypes]::{_wrappedCommandType}) $scriptCmd = {{& $wrappedCmd @PSBoundParameters }} - $steppablePipeline = $scriptCmd.GetSteppablePipeline({2}) + $steppablePipeline = $scriptCmd.GetSteppablePipeline({commandOrigin}) $steppablePipeline.Begin($PSCmdlet) }} catch {{ throw }} -", - CodeGeneration.EscapeSingleQuotedStringContent(_wrappedCommand), - _wrappedCommandType, - commandOrigin - ); +"); } else { - result = string.Format(CultureInfo.InvariantCulture, @" + result = string.Create(CultureInfo.InvariantCulture, $@" try {{ - $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('{0}', [System.Management.Automation.CommandTypes]::{1}) + $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('{wrappedCommand}', [System.Management.Automation.CommandTypes]::{_wrappedCommandType}) $PSBoundParameters.Add('$args', $args) $scriptCmd = {{& $wrappedCmd @PSBoundParameters }} - $steppablePipeline = $scriptCmd.GetSteppablePipeline({2}) + $steppablePipeline = $scriptCmd.GetSteppablePipeline({commandOrigin}) $steppablePipeline.Begin($myInvocation.ExpectingInput, $ExecutionContext) }} catch {{ throw }} -", - CodeGeneration.EscapeSingleQuotedStringContent(_wrappedCommand), - _wrappedCommandType, - commandOrigin - ); +"); } return result; @@ -1063,6 +1051,11 @@ internal string GetBeginBlock() internal string GetProcessBlock() { + // The reason we wrap scripts in 'try { } catch { throw }' (here and elsewhere) is to turn + // an exception that could be thrown from .NET method invocation into a terminating error + // that can be propagated up. + // By default, an exception thrown from .NET method is not terminating, but when enclosed + // in try/catch, it will be turned into a terminating error. return @" try { $steppablePipeline.Process($_) @@ -1074,9 +1067,10 @@ internal string GetProcessBlock() internal string GetDynamicParamBlock() { - return string.Format(CultureInfo.InvariantCulture, @" + string wrappedCommand = CodeGeneration.EscapeSingleQuotedStringContent(_wrappedCommand); + return string.Create(CultureInfo.InvariantCulture, $@" try {{ - $targetCmd = $ExecutionContext.InvokeCommand.GetCommand('{0}', [System.Management.Automation.CommandTypes]::{1}, $PSBoundParameters) + $targetCmd = $ExecutionContext.InvokeCommand.GetCommand('{wrappedCommand}', [System.Management.Automation.CommandTypes]::{_wrappedCommandType}, $PSBoundParameters) $dynamicParams = @($targetCmd.Parameters.GetEnumerator() | Microsoft.PowerShell.Core\Where-Object {{ $_.Value.IsDynamic }}) if ($dynamicParams.Length -gt 0) {{ @@ -1097,9 +1091,7 @@ internal string GetDynamicParamBlock() }} catch {{ throw }} -", - CodeGeneration.EscapeSingleQuotedStringContent(_wrappedCommand), - _wrappedCommandType); +"); } internal string GetEndBlock() @@ -1113,6 +1105,18 @@ internal string GetEndBlock() "; } + internal string GetCleanBlock() + { + // Here we don't need to enclose the script in a 'try/catch' like elsewhere, because + // 1. the 'Clean' block doesn't propagate up any exception (terminating error); + // 2. only one expression in the script, so nothing else needs to be stopped when invoking the method fails. + return @" + if ($null -ne $steppablePipeline) { + $steppablePipeline.Clean() + } +"; + } + #endregion #region Helper methods for restricting commands needed by implicit and interactive remoting @@ -1228,7 +1232,7 @@ private static CommandMetadata GetRestrictedGetHelp() // This should only be called with 1 valid category ParameterMetadata categoryParameter = new ParameterMetadata("Category", typeof(string[])); - categoryParameter.Attributes.Add(new ValidateSetAttribute(Enum.GetNames(typeof(HelpCategory)))); + categoryParameter.Attributes.Add(new ValidateSetAttribute(Enum.GetNames())); categoryParameter.Attributes.Add(new ValidateCountAttribute(0, 1)); return GetRestrictedCmdlet("Get-Help", null, "https://go.microsoft.com/fwlink/?LinkID=113316", nameParameter, categoryParameter); diff --git a/src/System.Management.Automation/engine/CommandParameter.cs b/src/System.Management.Automation/engine/CommandParameter.cs index e6752ff023b..1c58bb87e29 100644 --- a/src/System.Management.Automation/engine/CommandParameter.cs +++ b/src/System.Management.Automation/engine/CommandParameter.cs @@ -12,14 +12,14 @@ namespace System.Management.Automation [DebuggerDisplay("{ParameterName}")] internal sealed class CommandParameterInternal { - private class Parameter + private sealed class Parameter { internal Ast ast; internal string parameterName; internal string parameterText; } - private class Argument + private sealed class Argument { internal Ast ast; internal object value; @@ -124,10 +124,7 @@ internal bool ArgumentToBeSplatted /// internal void SetArgumentValue(Ast ast, object value) { - if (_argument == null) - { - _argument = new Argument(); - } + _argument ??= new Argument(); _argument.value = value; _argument.ast = ast; diff --git a/src/System.Management.Automation/engine/CommandPathSearch.cs b/src/System.Management.Automation/engine/CommandPathSearch.cs index c00d02c5af2..92a533e6ec0 100644 --- a/src/System.Management.Automation/engine/CommandPathSearch.cs +++ b/src/System.Management.Automation/engine/CommandPathSearch.cs @@ -37,17 +37,17 @@ internal class CommandPathSearch : IEnumerable, IEnumerator /// /// The patterns to search for in the paths. /// - /// - /// Use likely relevant search. + /// + /// The fuzzy matcher to use for fuzzy searching. /// internal CommandPathSearch( string commandName, LookupPathCollection lookupPaths, ExecutionContext context, Collection? acceptableCommandNames, - bool useFuzzyMatch) + FuzzyMatcher? fuzzyMatcher) { - _useFuzzyMatch = useFuzzyMatch; + _fuzzyMatcher = fuzzyMatcher; string[] commandPatterns; if (acceptableCommandNames != null) { @@ -110,7 +110,17 @@ private void ResolveCurrentDirectoryInLookupPaths() sessionState.CurrentDrive.Provider.NameEquals(fileSystemProviderName) && sessionState.IsProviderLoaded(fileSystemProviderName); - string environmentCurrentDirectory = Directory.GetCurrentDirectory(); + string? environmentCurrentDirectory = null; + + try + { + environmentCurrentDirectory = Directory.GetCurrentDirectory(); + } + catch (FileNotFoundException) + { + // This can happen if the current working directory is deleted by another process on non-Windows + // In this case, we'll just ignore it and continue on with the current directory as null + } LocationGlobber pathResolver = _context.LocationGlobber; @@ -346,9 +356,12 @@ public bool MoveNext() /// public void Reset() { + _lookupPathsEnumerator.Dispose(); _lookupPathsEnumerator = _lookupPaths.GetEnumerator(); + _patternEnumerator.Dispose(); _patternEnumerator = _patterns.GetEnumerator(); _currentDirectoryResults = Array.Empty(); + _currentDirectoryResultsEnumerator.Dispose(); _currentDirectoryResultsEnumerator = _currentDirectoryResults.GetEnumerator(); _justReset = true; } @@ -421,13 +434,13 @@ private void GetNewDirectoryResults(string pattern, string directory) // to forcefully use null if pattern is "." if (pattern.Length != 1 || pattern[0] != '.') { - if (_useFuzzyMatch) + if (_fuzzyMatcher is not null) { var files = new List(); var matchingFiles = Directory.EnumerateFiles(directory); foreach (string file in matchingFiles) { - if (FuzzyMatcher.IsFuzzyMatch(Path.GetFileName(file), pattern)) + if (_fuzzyMatcher.IsFuzzyMatch(Path.GetFileName(file), pattern)) { files.Add(file); } @@ -490,8 +503,7 @@ private void GetNewDirectoryResults(string pattern, string directory) if (name.Equals(baseNames[i], StringComparison.OrdinalIgnoreCase) || (!Platform.IsWindows && Platform.NonWindowsIsExecutable(name))) { - if (result == null) - result = new Collection(); + result ??= new Collection(); result.Add(fileNames[i]); break; } @@ -517,8 +529,7 @@ private void GetNewDirectoryResults(string pattern, string directory) if (fileName.EndsWith(allowedExt, StringComparison.OrdinalIgnoreCase) || (!Platform.IsWindows && Platform.NonWindowsIsExecutable(fileName))) { - if (result == null) - result = new Collection(); + result ??= new Collection(); result.Add(fileName); } } @@ -578,7 +589,7 @@ private void GetNewDirectoryResults(string pattern, string directory) private readonly string[] _orderedPathExt; private readonly Collection? _acceptableCommandNames; - private readonly bool _useFuzzyMatch = false; + private readonly FuzzyMatcher? _fuzzyMatcher; #endregion private members } diff --git a/src/System.Management.Automation/engine/CommandProcessor.cs b/src/System.Management.Automation/engine/CommandProcessor.cs index fb1fe81fc54..d9758cea2fa 100644 --- a/src/System.Management.Automation/engine/CommandProcessor.cs +++ b/src/System.Management.Automation/engine/CommandProcessor.cs @@ -227,6 +227,7 @@ internal override void Prepare(IDictionary psDefaultParameterValues) Context.LanguageMode = scriptCmdletInfo.ScriptBlock.LanguageMode.Value; // If it's from ConstrainedLanguage to FullLanguage, indicate the transition before parameter binding takes place. + // When transitioning to FullLanguage mode, we don't want any ConstrainedLanguage restrictions or incorrect Audit messages. if (oldLanguageMode == PSLanguageMode.ConstrainedLanguage && Context.LanguageMode == PSLanguageMode.FullLanguage) { oldLangModeTransitionStatus = Context.LanguageModeTransitionInParameterBinding; @@ -309,13 +310,11 @@ internal override void DoBegin() internal override void ProcessRecord() { // Invoke the Command method with the request object - if (!this.RanBeginAlready) { RanBeginAlready = true; try { - // NOTICE-2004/06/08-JonN 959638 using (commandRuntime.AllowThisCommandToWrite(true)) { if (Context._debuggingMode > 0 && Command is not PSScriptCmdlet) @@ -326,12 +325,9 @@ internal override void ProcessRecord() Command.DoBeginProcessing(); } } - // 2004/03/18-JonN This is understood to be - // an FXCOP violation, cleared by KCwalina. - catch (Exception e) // Catch-all OK, 3rd party callout. + catch (Exception e) { - // This cmdlet threw an exception, so - // wrap it and bubble it up. + // This cmdlet threw an exception, so wrap it and bubble it up. throw ManageInvocationException(e); } } @@ -366,6 +362,7 @@ internal override void ProcessRecord() // NOTICE-2004/06/08-JonN 959638 using (commandRuntime.AllowThisCommandToWrite(true)) + using (ParameterBinderBase.bindingTracer.TraceScope("CALLING ProcessRecord")) { if (CmdletParameterBinderController.ObsoleteParameterWarningList != null && CmdletParameterBinderController.ObsoleteParameterWarningList.Count > 0) @@ -400,14 +397,13 @@ internal override void ProcessRecord() } catch (LoopFlowException) { - // Win8:84066 - Don't wrap LoopFlowException, we incorrectly raise a PipelineStoppedException + // Don't wrap LoopFlowException, we incorrectly raise a PipelineStoppedException // which gets caught by a script try/catch if we wrap here. throw; } - // 2004/03/18-JonN This is understood to be - // an FXCOP violation, cleared by KCwalina. - catch (Exception e) // Catch-all OK, 3rd party callout. + catch (Exception e) { + // Catch-all OK, 3rd party callout. exceptionToThrow = e; } finally @@ -691,7 +687,7 @@ private static Cmdlet ConstructInstance(Type type) /// If the constructor for the cmdlet threw an exception. /// /// - /// The type referenced by refered to an + /// The type referenced by referred to an /// abstract type or them member was invoked via a late-binding mechanism. /// /// @@ -784,7 +780,7 @@ private void Init(IScriptCommandInfo scriptCommandInfo) // If the script has been dotted, throw an error if it's from a different language mode. if (!this.UseLocalScope) { - ValidateCompatibleLanguageMode(scriptCommandInfo.ScriptBlock, _context.LanguageMode, Command.MyInvocation); + ValidateCompatibleLanguageMode(scriptCommandInfo.ScriptBlock, _context, Command.MyInvocation); } } diff --git a/src/System.Management.Automation/engine/CommandProcessorBase.cs b/src/System.Management.Automation/engine/CommandProcessorBase.cs index c6825cc6925..ddadf9f7516 100644 --- a/src/System.Management.Automation/engine/CommandProcessorBase.cs +++ b/src/System.Management.Automation/engine/CommandProcessorBase.cs @@ -4,9 +4,8 @@ using System.Collections; using System.Collections.ObjectModel; using System.Management.Automation.Internal; -using System.Management.Automation.Language; - -using Dbg = System.Management.Automation.Diagnostics; +using System.Management.Automation.Security; +using System.Runtime.InteropServices; namespace System.Management.Automation { @@ -46,6 +45,7 @@ internal CommandProcessorBase(CommandInfo commandInfo) string errorTemplate = expAttribute.ExperimentAction == ExperimentAction.Hide ? DiscoveryExceptions.ScriptDisabledWhenFeatureOn : DiscoveryExceptions.ScriptDisabledWhenFeatureOff; + string errorMsg = StringUtil.Format(errorTemplate, expAttribute.ExperimentName); ErrorRecord errorRecord = new ErrorRecord( new InvalidOperationException(errorMsg), @@ -54,6 +54,8 @@ internal CommandProcessorBase(CommandInfo commandInfo) commandInfo); throw new CmdletInvocationException(errorRecord); } + + HasCleanBlock = scriptCommand.ScriptBlock.HasCleanBlock; } CommandInfo = commandInfo; @@ -87,6 +89,11 @@ internal bool AddedToPipelineAlready /// internal CommandInfo CommandInfo { get; set; } + /// + /// Gets whether the command has a 'Clean' block defined. + /// + internal bool HasCleanBlock { get; } + /// /// This indicates whether this command processor is created from /// a script file. @@ -187,11 +194,11 @@ internal bool UseLocalScope /// be used when a script block is being dotted. /// /// The script block being dotted. - /// The current language mode. + /// The current execution context. /// The invocation info about the command. protected static void ValidateCompatibleLanguageMode( ScriptBlock scriptBlock, - PSLanguageMode languageMode, + ExecutionContext context, InvocationInfo invocationInfo) { // If we are in a constrained language mode (Core or Restricted), block it. @@ -200,10 +207,11 @@ protected static void ValidateCompatibleLanguageMode( // functions that were never designed to handle untrusted data. // This function won't be called for NoLanguage mode so the only direction checked is trusted // (FullLanguage mode) script running in a constrained/restricted session. - if ((scriptBlock.LanguageMode.HasValue) && - (scriptBlock.LanguageMode != languageMode) && - ((languageMode == PSLanguageMode.RestrictedLanguage) || - (languageMode == PSLanguageMode.ConstrainedLanguage))) + var languageMode = context.LanguageMode; + if (scriptBlock.LanguageMode.HasValue && + scriptBlock.LanguageMode != languageMode && + (languageMode == PSLanguageMode.RestrictedLanguage || + languageMode == PSLanguageMode.ConstrainedLanguage)) { // Finally check if script block is really just PowerShell commands plus parameters. // If so then it is safe to dot source across language mode boundaries. @@ -219,14 +227,24 @@ protected static void ValidateCompatibleLanguageMode( if (!isSafeToDotSource) { - ErrorRecord errorRecord = new ErrorRecord( - new NotSupportedException( - DiscoveryExceptions.DotSourceNotSupported), - "DotSourceNotSupported", - ErrorCategory.InvalidOperation, - null); - errorRecord.SetInvocationInfo(invocationInfo); - throw new CmdletInvocationException(errorRecord); + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + ErrorRecord errorRecord = new ErrorRecord( + new NotSupportedException(DiscoveryExceptions.DotSourceNotSupported), + "DotSourceNotSupported", + ErrorCategory.InvalidOperation, + targetObject: null); + errorRecord.SetInvocationInfo(invocationInfo); + throw new CmdletInvocationException(errorRecord); + } + + string scriptBlockId = scriptBlock.GetFileName() ?? string.Empty; + SystemPolicy.LogWDACAuditMessage( + context: context, + title: CommandBaseStrings.WDACLogTitle, + message: StringUtil.Format(CommandBaseStrings.WDACLogMessage, scriptBlockId, scriptBlock.LanguageMode, languageMode), + fqid: "ScriptBlockDotSourceNotAllowed", + dropIntoDebugger: true); } } } @@ -345,10 +363,7 @@ internal void SetCurrentScopeToExecutionScope() // Make sure we have a session state instance for this command. // If one hasn't been explicitly set, then use the session state // available on the engine execution context... - if (CommandSessionState == null) - { - CommandSessionState = Context.EngineSessionState; - } + CommandSessionState ??= Context.EngineSessionState; // Store off the current scope _previousScope = CommandSessionState.CurrentScope; @@ -371,13 +386,10 @@ internal void RestorePreviousScope() Context.EngineSessionState = _previousCommandSessionState; - if (_previousScope != null) - { - // Restore the scope but use the same session state instance we - // got it from because the command may have changed the execution context - // session state... - CommandSessionState.CurrentScope = _previousScope; - } + // Restore the scope but use the same session state instance we + // got it from because the command may have changed the execution context + // session state... + CommandSessionState.CurrentScope = _previousScope; } private SessionStateScope _previousScope; @@ -452,16 +464,14 @@ internal void DoPrepare(IDictionary psDefaultParameterValues) HandleObsoleteCommand(ObsoleteAttribute); } } - catch (Exception) + catch (InvalidComObjectException e) { - if (_useLocalScope) - { - // If we had an exception during Prepare, we're done trying to execute the command - // so the scope we created needs to release any resources it hold.s - CommandSessionState.RemoveScope(CommandScope); - } + // This type of exception could be thrown from parameter binding. + string msg = StringUtil.Format(ParserStrings.InvalidComObjectException, e.Message); + var newEx = new RuntimeException(msg, e); - throw; + newEx.SetErrorId("InvalidComObjectException"); + throw newEx; } finally { @@ -508,26 +518,23 @@ internal virtual void DoBegin() // The RedirectShellErrorOutputPipe flag is used by the V2 hosting API to force the // redirection. // - if (this.RedirectShellErrorOutputPipe || _context.ShellFunctionErrorOutputPipe != null) + if (RedirectShellErrorOutputPipe || _context.ShellFunctionErrorOutputPipe is not null) { - _context.ShellFunctionErrorOutputPipe = this.commandRuntime.ErrorOutputPipe; + _context.ShellFunctionErrorOutputPipe = commandRuntime.ErrorOutputPipe; } _context.CurrentCommandProcessor = this; + SetCurrentScopeToExecutionScope(); + using (commandRuntime.AllowThisCommandToWrite(true)) + using (ParameterBinderBase.bindingTracer.TraceScope("CALLING BeginProcessing")) { - using (ParameterBinderBase.bindingTracer.TraceScope( - "CALLING BeginProcessing")) + if (Context._debuggingMode > 0 && Command is not PSScriptCmdlet) { - SetCurrentScopeToExecutionScope(); - - if (Context._debuggingMode > 0 && Command is not PSScriptCmdlet) - { - Context.Debugger.CheckCommand(this.Command.MyInvocation); - } - - Command.DoBeginProcessing(); + Context.Debugger.CheckCommand(Command.MyInvocation); } + + Command.DoBeginProcessing(); } } catch (Exception e) @@ -589,20 +596,14 @@ internal virtual void Complete() try { using (commandRuntime.AllowThisCommandToWrite(true)) + using (ParameterBinderBase.bindingTracer.TraceScope("CALLING EndProcessing")) { - using (ParameterBinderBase.bindingTracer.TraceScope( - "CALLING EndProcessing")) - { - this.Command.DoEndProcessing(); - } + this.Command.DoEndProcessing(); } } - // 2004/03/18-JonN This is understood to be - // an FXCOP violation, cleared by KCwalina. catch (Exception e) { - // This cmdlet threw an exception, so - // wrap it and bubble it up. + // This cmdlet threw an exception, wrap it as needed and bubble it up. throw ManageInvocationException(e); } } @@ -631,46 +632,121 @@ internal void DoComplete() // The RedirectShellErrorOutputPipe flag is used by the V2 hosting API to force the // redirection. // - if (this.RedirectShellErrorOutputPipe || _context.ShellFunctionErrorOutputPipe != null) + if (RedirectShellErrorOutputPipe || _context.ShellFunctionErrorOutputPipe is not null) { - _context.ShellFunctionErrorOutputPipe = this.commandRuntime.ErrorOutputPipe; + _context.ShellFunctionErrorOutputPipe = commandRuntime.ErrorOutputPipe; } _context.CurrentCommandProcessor = this; - SetCurrentScopeToExecutionScope(); Complete(); } finally { - OnRestorePreviousScope(); - _context.ShellFunctionErrorOutputPipe = oldErrorOutputPipe; _context.CurrentCommandProcessor = oldCurrentCommandProcessor; - // Destroy the local scope at this point if there is one... - if (_useLocalScope && CommandScope != null) - { - CommandSessionState.RemoveScope(CommandScope); - } + RestorePreviousScope(); + } + } - // and the previous scope... - if (_previousScope != null) + protected virtual void CleanResource() + { + try + { + using (commandRuntime.AllowThisCommandToWrite(permittedToWriteToPipeline: true)) + using (ParameterBinderBase.bindingTracer.TraceScope("CALLING CleanResource")) { - // Restore the scope but use the same session state instance we - // got it from because the command may have changed the execution context - // session state... - CommandSessionState.CurrentScope = _previousScope; + Command.DoCleanResource(); } + } + catch (HaltCommandException) + { + throw; + } + catch (FlowControlException) + { + throw; + } + catch (Exception e) + { + // This cmdlet threw an exception, so wrap it and bubble it up. + throw ManageInvocationException(e); + } + } + + internal void DoCleanup() + { + // The property 'PropagateExceptionsToEnclosingStatementBlock' controls whether a general exception + // (an exception thrown from a .NET method invocation, or an expression like '1/0') will be turned + // into a terminating error, which will be propagated up and thus stop the rest of the running script. + // It is usually used by TryStatement and TrapStatement, which makes the general exception catch-able. + // + // For the 'Clean' block, we don't want to bubble up the general exception when the command is enclosed + // in a TryStatement or has TrapStatement accompanying, because no exception can escape from 'Clean' and + // thus it's pointless to bubble up the general exception in this case. + // + // Therefore we set this property to 'false' here to mask off the previous setting that could be from a + // TryStatement or TrapStatement. Example: + // PS:1> function b { end {} clean { 1/0; Write-Host 'clean' } } + // PS:2> b + // RuntimeException: Attempted to divide by zero. + // clean + // ## Note that, outer 'try/trap' doesn't affect the general exception happens in 'Clean' block. + // ## so its behavior is consistent regardless of whether the command is enclosed by 'try/catch' or not. + // PS:3> try { b } catch { 'outer catch' } + // RuntimeException: Attempted to divide by zero. + // clean + // + // Be noted that, this doesn't affect the TryStatement/TrapStatement within the 'Clean' block. Example: + // ## 'try/trap' within 'Clean' block makes the general exception catch-able. + // PS:3> function a { end {} clean { try { 1/0; Write-Host 'clean' } catch { Write-Host "caught: $_" } } } + // PS:4> a + // caught: Attempted to divide by zero. + bool oldExceptionPropagationState = _context.PropagateExceptionsToEnclosingStatementBlock; + _context.PropagateExceptionsToEnclosingStatementBlock = false; + + Pipe oldErrorOutputPipe = _context.ShellFunctionErrorOutputPipe; + CommandProcessorBase oldCurrentCommandProcessor = _context.CurrentCommandProcessor; - // Restore the previous session state - if (_previousCommandSessionState != null) + try + { + if (RedirectShellErrorOutputPipe || _context.ShellFunctionErrorOutputPipe is not null) { - Context.EngineSessionState = _previousCommandSessionState; + _context.ShellFunctionErrorOutputPipe = commandRuntime.ErrorOutputPipe; } + + _context.CurrentCommandProcessor = this; + SetCurrentScopeToExecutionScope(); + CleanResource(); + } + finally + { + _context.PropagateExceptionsToEnclosingStatementBlock = oldExceptionPropagationState; + _context.ShellFunctionErrorOutputPipe = oldErrorOutputPipe; + _context.CurrentCommandProcessor = oldCurrentCommandProcessor; + + RestorePreviousScope(); } } + internal void ReportCleanupError(Exception exception) + { + var error = exception is IContainsErrorRecord icer + ? icer.ErrorRecord + : new ErrorRecord(exception, "Clean.ReportException", ErrorCategory.NotSpecified, targetObject: null); + + PSObject errorWrap = PSObject.AsPSObject(error); + errorWrap.WriteStream = WriteStreamType.Error; + + var errorPipe = commandRuntime.ErrorMergeTo == MshCommandRuntime.MergeDataStream.Output + ? commandRuntime.OutputPipe + : commandRuntime.ErrorOutputPipe; + + errorPipe.Add(errorWrap); + _context.QuestionMarkVariableValue = false; + } + /// /// For diagnostic purposes. /// @@ -777,23 +853,16 @@ internal PipelineStoppedException ManageInvocationException(Exception e) { do // false loop { - ProviderInvocationException pie = e as ProviderInvocationException; - if (pie != null) + if (e is ProviderInvocationException pie) { - // If a ProviderInvocationException occurred, - // discard the ProviderInvocationException and - // re-wrap in CmdletProviderInvocationException - e = new CmdletProviderInvocationException( - pie, - Command.MyInvocation); + // If a ProviderInvocationException occurred, discard the ProviderInvocationException + // and re-wrap it in CmdletProviderInvocationException. + e = new CmdletProviderInvocationException(pie, Command.MyInvocation); break; } - // 1021203-2005/05/09-JonN - // HaltCommandException will cause the command - // to stop, but not be reported as an error. - // 906445-2005/05/16-JonN - // FlowControlException should not be wrapped + // HaltCommandException will cause the command to stop, but not be reported as an error. + // FlowControlException should not be wrapped. if (e is PipelineStoppedException || e is CmdletInvocationException || e is ActionPreferenceStopException @@ -813,9 +882,7 @@ internal PipelineStoppedException ManageInvocationException(Exception e) } // wrap all other exceptions - e = new CmdletInvocationException( - e, - Command.MyInvocation); + e = new CmdletInvocationException(e, Command.MyInvocation); } while (false); // commandRuntime.ManageException will always throw PipelineStoppedException @@ -943,15 +1010,27 @@ public void Dispose() private void Dispose(bool disposing) { if (_disposed) + { return; + } if (disposing) { - // 2004/03/05-JonN Look into using metadata to check - // whether IDisposable is implemented, in order to avoid - // this expensive reflection cast. - IDisposable id = Command as IDisposable; - if (id != null) + if (UseLocalScope) + { + // Clean up the PS drives that are associated with this local scope. + // This operation may be needed at multiple stages depending on whether the 'clean' block is declared: + // 1. when there is a 'clean' block, it needs to be done only after 'clean' block runs, because the scope + // needs to be preserved until the 'clean' block finish execution. + // 2. when there is no 'clean' block, it needs to be done when + // (1) there is any exception thrown from 'DoPrepare()', 'DoBegin()', 'DoExecute()', or 'DoComplete'; + // (2) OR, the command runs to the end successfully; + // Doing this cleanup at those multiple stages is cumbersome. Since we will always dispose the command in + // the end, doing this cleanup here will cover all the above cases. + CommandSessionState.RemoveScope(CommandScope); + } + + if (Command is IDisposable id) { id.Dispose(); } diff --git a/src/System.Management.Automation/engine/CommandSearcher.cs b/src/System.Management.Automation/engine/CommandSearcher.cs index 9745d2aa9ad..1d945094004 100644 --- a/src/System.Management.Automation/engine/CommandSearcher.cs +++ b/src/System.Management.Automation/engine/CommandSearcher.cs @@ -9,6 +9,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Management.Automation.Internal; +using System.Management.Automation.Security; using Dbg = System.Management.Automation.Diagnostics; @@ -24,29 +25,20 @@ internal class CommandSearcher : IEnumerable, IEnumerator - /// - /// The name of the command to look for. - /// - /// - /// Determines which types of commands glob resolution of the name will take place on. - /// - /// - /// The types of commands to look for. - /// - /// - /// The execution context for this engine instance... - /// - /// - /// If is null. - /// - /// - /// If is null or empty. - /// + /// The name of the command to look for. + /// Determines which types of commands glob resolution of the name will take place on. + /// The types of commands to look for. + /// The execution context for this engine instance. + /// The fuzzy matcher to use for fuzzy searching. + /// + /// If is null. + /// If is null or empty. internal CommandSearcher( string commandName, SearchResolutionOptions options, CommandTypes commandTypes, - ExecutionContext context) + ExecutionContext context, + FuzzyMatcher? fuzzyMatcher = null) { Diagnostics.Assert(context != null, "caller to verify context is not null"); Diagnostics.Assert(!string.IsNullOrEmpty(commandName), "caller to verify commandName is valid"); @@ -55,6 +47,7 @@ internal CommandSearcher( _context = context; _commandResolutionOptions = options; _commandTypes = commandTypes; + _fuzzyMatcher = fuzzyMatcher; // Initialize the enumerators this.Reset(); @@ -158,7 +151,7 @@ public bool MoveNext() } else { - // Ok see it it's in the applications list + // Ok, see if it's in the applications list foreach (string path in _context.EngineSessionState.Applications) { if (checkPath(path, _commandName)) @@ -705,8 +698,7 @@ private static bool checkPath(string path, string commandName) foreach (KeyValuePair aliasEntry in _context.EngineSessionState.GetAliasTable()) { if (aliasMatcher.IsMatch(aliasEntry.Key) || - (_commandResolutionOptions.HasFlag(SearchResolutionOptions.FuzzyMatch) && - FuzzyMatcher.IsFuzzyMatch(aliasEntry.Key, _commandName))) + (_fuzzyMatcher is not null && _fuzzyMatcher.IsFuzzyMatch(aliasEntry.Key, _commandName))) { matchingAliases.Add(aliasEntry.Value); } @@ -785,8 +777,7 @@ private static bool checkPath(string path, string commandName) foreach ((string functionName, FunctionInfo functionInfo) in _context.EngineSessionState.GetFunctionTable()) { if (functionMatcher.IsMatch(functionName) || - (_commandResolutionOptions.HasFlag(SearchResolutionOptions.FuzzyMatch) && - FuzzyMatcher.IsFuzzyMatch(functionName, _commandName))) + (_fuzzyMatcher is not null && _fuzzyMatcher.IsFuzzyMatch(functionName, _commandName))) { matchingFunction.Add(functionInfo); } @@ -849,11 +840,21 @@ private static bool ShouldSkipCommandResolutionForConstrainedLanguage(CommandInf return false; } - // Don't return untrusted commands to trusted functions - if ((result.DefiningLanguageMode == PSLanguageMode.ConstrainedLanguage) && - (executionContext.LanguageMode == PSLanguageMode.FullLanguage)) + // Don't return untrusted commands to trusted functions. + if (result.DefiningLanguageMode == PSLanguageMode.ConstrainedLanguage && executionContext.LanguageMode == PSLanguageMode.FullLanguage) { - return true; + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + return true; + } + + // This audit log message is to inform the user that an expected command will not be available because it is not trusted + // when the machine is in policy enforcement mode. + SystemPolicy.LogWDACAuditMessage( + context: executionContext, + title: CommandBaseStrings.SearcherWDACLogTitle, + message: StringUtil.Format(CommandBaseStrings.SearcherWDACLogMessage, result.Name, result.ModuleName ?? string.Empty), + fqid: "CommandSearchFailureForUntrustedCommand"); } // Don't allow invocation of trusted functions from debug breakpoints. @@ -928,10 +929,7 @@ private static bool ShouldSkipCommandResolutionForConstrainedLanguage(CommandInf } } - if (module == null) - { - module = modules[0]; - } + module ??= modules[0]; } return module; @@ -953,24 +951,13 @@ private static bool ShouldSkipCommandResolutionForConstrainedLanguage(CommandInf if (result != null) { - if (result is FilterInfo) + var formatString = result switch { - CommandDiscovery.discoveryTracer.WriteLine( - "Filter found: {0}", - function); - } - else if (result is ConfigurationInfo) - { - CommandDiscovery.discoveryTracer.WriteLine( - "Configuration found: {0}", - function); - } - else - { - CommandDiscovery.discoveryTracer.WriteLine( - "Function found: {0} {1}", - function); - } + FilterInfo => "Filter found: {0}", + ConfigurationInfo => "Configuration found: {0}", + _ => "Function found: {0}", + }; + CommandDiscovery.discoveryTracer.WriteLine(formatString, function); } else { @@ -1021,10 +1008,8 @@ private static bool ShouldSkipCommandResolutionForConstrainedLanguage(CommandInf { foreach (CmdletInfo cmdlet in cmdletList) { - if (cmdletMatcher != null && - cmdletMatcher.IsMatch(cmdlet.Name) || - (_commandResolutionOptions.HasFlag(SearchResolutionOptions.FuzzyMatch) && - FuzzyMatcher.IsFuzzyMatch(cmdlet.Name, _commandName))) + if ((cmdletMatcher is not null && cmdletMatcher.IsMatch(cmdlet.Name)) || + (_fuzzyMatcher is not null && _fuzzyMatcher.IsFuzzyMatch(cmdlet.Name, _commandName))) { if (string.IsNullOrEmpty(moduleName) || moduleName.Equals(cmdlet.ModuleName, StringComparison.OrdinalIgnoreCase)) { @@ -1454,7 +1439,7 @@ private static CanDoPathLookupResult CanDoPathLookup(string possiblePath) // If the command contains any path separators, we can't // do the path lookup - if (possiblePath.IndexOfAny(Utils.Separators.Directory) != -1) + if (possiblePath.AsSpan().IndexOfAny('\\', '/') != -1) { result = CanDoPathLookupResult.DirectorySeparator; break; @@ -1463,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; @@ -1499,6 +1484,11 @@ private static CanDoPathLookupResult CanDoPathLookup(string possiblePath) /// private readonly ExecutionContext _context; + /// + /// The fuzzy matcher to use for fuzzy searching. + /// + private readonly FuzzyMatcher? _fuzzyMatcher; + /// /// A routine to initialize the path searcher... /// @@ -1531,7 +1521,7 @@ private void setupPathSearcher() _context.CommandDiscovery.GetLookupDirectoryPaths(), _context, acceptableCommandNames: null, - useFuzzyMatch: _commandResolutionOptions.HasFlag(SearchResolutionOptions.FuzzyMatch)); + _fuzzyMatcher); } else { @@ -1547,7 +1537,7 @@ private void setupPathSearcher() _context.CommandDiscovery.GetLookupDirectoryPaths(), _context, ConstructSearchPatternsFromName(_commandName, commandDiscovery: true), - useFuzzyMatch: false); + fuzzyMatcher: null); } else if (_canDoPathLookupResult == CanDoPathLookupResult.PathIsRooted) { @@ -1571,7 +1561,7 @@ private void setupPathSearcher() directoryCollection, _context, ConstructSearchPatternsFromName(fileName, commandDiscovery: true), - useFuzzyMatch: false); + fuzzyMatcher: null); } else { @@ -1611,7 +1601,7 @@ private void setupPathSearcher() directoryCollection, _context, ConstructSearchPatternsFromName(fileName, commandDiscovery: true), - useFuzzyMatch: false); + fuzzyMatcher: null); } else { @@ -1730,17 +1720,14 @@ internal enum SearchResolutionOptions CommandNameIsPattern = 0x04, SearchAllScopes = 0x08, - /// Use fuzzy matching. - FuzzyMatch = 0x10, - /// /// Enable searching for cmdlets/functions by abbreviation expansion. /// - UseAbbreviationExpansion = 0x20, + UseAbbreviationExpansion = 0x10, /// /// Enable resolving wildcard in paths. /// - ResolveLiteralThenPathPatterns = 0x40 + ResolveLiteralThenPathPatterns = 0x20 } } diff --git a/src/System.Management.Automation/engine/CommonCommandParameters.cs b/src/System.Management.Automation/engine/CommonCommandParameters.cs index 376b9d0adbf..9dc92d817aa 100644 --- a/src/System.Management.Automation/engine/CommonCommandParameters.cs +++ b/src/System.Management.Automation/engine/CommonCommandParameters.cs @@ -59,7 +59,7 @@ public SwitchParameter Verbose /// /// /// This parameter tells the command to provide Programmer/Support type - /// messages to understand what is really occuring and give the user the + /// messages to understand what is really occurring and give the user the /// opportunity to stop or debug the situation. /// [Parameter] @@ -123,6 +123,27 @@ public ActionPreference InformationAction set { _commandRuntime.InformationPreference = value; } } + /// + /// Gets or sets the value of the ProgressAction parameter for the cmdlet. + /// + /// + /// This parameter tells the command what to do when a progress record occurs. + /// + /// + [Parameter] + [Alias("proga")] + public ActionPreference ProgressAction + { + get { return _commandRuntime.ProgressPreference; } + + set { _commandRuntime.ProgressPreference = value; } + } + /// /// Gets or sets the value of the ErrorVariable parameter for the cmdlet. /// @@ -204,7 +225,7 @@ public string OutVariable /// This parameter configures the number of objects to buffer before calling the downstream Cmdlet /// [Parameter] - [ValidateRangeAttribute(0, Int32.MaxValue)] + [ValidateRange(0, Int32.MaxValue)] [Alias("ob")] public int OutBuffer { diff --git a/src/System.Management.Automation/engine/CompiledCommandParameter.cs b/src/System.Management.Automation/engine/CompiledCommandParameter.cs index dc74210bda5..d2ca1aedd91 100644 --- a/src/System.Management.Automation/engine/CompiledCommandParameter.cs +++ b/src/System.Management.Automation/engine/CompiledCommandParameter.cs @@ -440,8 +440,7 @@ private void ProcessAttribute( ValidateArgumentsAttribute validateAttr = attribute as ValidateArgumentsAttribute; if (validateAttr != null) { - if (validationAttributes == null) - validationAttributes = new Collection(); + validationAttributes ??= new Collection(); validationAttributes.Add(validateAttr); if ((attribute is ValidateNotNullAttribute) || (attribute is ValidateNotNullOrEmptyAttribute)) { @@ -473,8 +472,7 @@ private void ProcessAttribute( ArgumentTransformationAttribute argumentAttr = attribute as ArgumentTransformationAttribute; if (argumentAttr != null) { - if (argTransformationAttributes == null) - argTransformationAttributes = new Collection(); + argTransformationAttributes ??= new Collection(); argTransformationAttributes.Add(argumentAttr); return; } @@ -649,7 +647,7 @@ internal ParameterCollectionTypeInformation(Type type) // to an ICollection is via reflected calls to Add(T), // but the advantage over plain IList is that we can typecast the elements. Type interfaceICollection = - Array.Find(interfaces, i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICollection<>)); + Array.Find(interfaces, static i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICollection<>)); if (interfaceICollection != null) { // We only deal with the first type for which ICollection is implemented diff --git a/src/System.Management.Automation/engine/CoreAdapter.cs b/src/System.Management.Automation/engine/CoreAdapter.cs index 48077bbafd4..907060bfe49 100644 --- a/src/System.Management.Automation/engine/CoreAdapter.cs +++ b/src/System.Management.Automation/engine/CoreAdapter.cs @@ -38,7 +38,7 @@ namespace System.Management.Automation internal abstract class Adapter { /// - /// Tracer for this and derivate classes. + /// Tracer for this and derivative classes. /// [TraceSource("ETS", "Extended Type System")] protected static PSTraceSource tracer = PSTraceSource.GetTracer("ETS", "Extended Type System"); @@ -833,7 +833,7 @@ private static Type GetArgumentType(object argument, bool isByRefParameter) return GetArgumentType(PSObject.Base(psref.Value), isByRefParameter: false); } - return argument.GetType(); + return GetObjectType(argument, debase: false); } internal static ConversionRank GetArgumentConversionRank(object argument, Type parameterType, bool isByRef, bool allowCastingToByRefLikeType) @@ -1157,8 +1157,8 @@ private static int CompareTypeSpecificity(OverloadCandidate candidate1, Overload return 0; } - Type[] params1 = GetGenericMethodDefinitionIfPossible(candidate1.method.method).GetParameters().Select(p => p.ParameterType).ToArray(); - Type[] params2 = GetGenericMethodDefinitionIfPossible(candidate2.method.method).GetParameters().Select(p => p.ParameterType).ToArray(); + Type[] params1 = GetGenericMethodDefinitionIfPossible(candidate1.method.method).GetParameters().Select(static p => p.ParameterType).ToArray(); + Type[] params2 = GetGenericMethodDefinitionIfPossible(candidate2.method.method).GetParameters().Select(static p => p.ParameterType).ToArray(); return CompareTypeSpecificity(params1, params2); } @@ -1177,7 +1177,7 @@ private static MethodBase GetGenericMethodDefinitionIfPossible(MethodBase method } [DebuggerDisplay("OverloadCandidate: {method.methodDefinition}")] - private class OverloadCandidate + private sealed class OverloadCandidate { internal MethodInformation method; internal ParameterInformation[] parameters; @@ -1363,7 +1363,7 @@ internal static MethodInformation FindBestMethod( Type targetType = methodInfo.method.DeclaringType; if (targetType != invocationConstraints.MethodTargetType && targetType.IsSubclassOf(invocationConstraints.MethodTargetType)) { - var parameterTypes = methodInfo.method.GetParameters().Select(parameter => parameter.ParameterType).ToArray(); + var parameterTypes = methodInfo.method.GetParameters().Select(static parameter => parameter.ParameterType).ToArray(); var targetTypeMethod = invocationConstraints.MethodTargetType.GetMethod(methodInfo.method.Name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, parameterTypes, null); if (targetTypeMethod != null && (targetTypeMethod.IsPublic || targetTypeMethod.IsFamily || targetTypeMethod.IsFamilyOrAssembly)) @@ -1377,6 +1377,27 @@ internal static MethodInformation FindBestMethod( return methodInfo; } + private static Type[] ResolveGenericTypeParameters(object[] genericTypeParameters) + { + if (genericTypeParameters is null || genericTypeParameters.Length == 0) + { + return null; + } + + Type[] genericParamTypes = new Type[genericTypeParameters.Length]; + for (int i = 0; i < genericTypeParameters.Length; i++) + { + genericParamTypes[i] = genericTypeParameters[i] switch + { + Type paramType => paramType, + ITypeName paramTypeName => TypeOps.ResolveTypeName(paramTypeName, paramTypeName.Extent), + _ => throw new ArgumentException("Unexpected value"), + }; + } + + return genericParamTypes; + } + private static MethodInformation FindBestMethodImpl( MethodInformation[] methods, PSMethodInvocationConstraints invocationConstraints, @@ -1394,59 +1415,84 @@ private static MethodInformation FindBestMethodImpl( // be turned into an array. // We also skip the optimization if the number of arguments and parameters is different // so we let the loop deal with possible optional parameters. - if ((methods.Length == 1) && - (!methods[0].hasVarArgs) && - (!methods[0].isGeneric) && - (methods[0].method == null || !(methods[0].method.DeclaringType.IsGenericTypeDefinition)) && + if (methods.Length == 1 + && !methods[0].hasVarArgs // generic methods need to be double checked in a loop below - generic methods can be rejected if type inference fails - (methods[0].parameters.Length == arguments.Length)) + && !methods[0].isGeneric + && (methods[0].method is null || !methods[0].method.DeclaringType.IsGenericTypeDefinition) + && methods[0].parameters.Length == arguments.Length) { return methods[0]; } - Type[] argumentTypes = arguments.Select(EffectiveArgumentType).ToArray(); - List candidates = new List(); + Type[] genericParamTypes = ResolveGenericTypeParameters(invocationConstraints?.GenericTypeParameters); + var candidates = new List(); + for (int i = 0; i < methods.Length; i++) { - MethodInformation method = methods[i]; + MethodInformation methodInfo = methods[i]; - if (method.method != null && method.method.DeclaringType.IsGenericTypeDefinition) + if (methodInfo.method?.DeclaringType.IsGenericTypeDefinition == true + || (!methodInfo.isGeneric && genericParamTypes is not null)) { - continue; // skip methods defined by an *open* generic type + // If method is defined by an *open* generic type, or + // if generic parameters were provided and this method isn't generic, skip it. + continue; } - if (method.isGeneric) + if (methodInfo.isGeneric) { - Type[] argumentTypesForTypeInference = new Type[argumentTypes.Length]; - Array.Copy(argumentTypes, argumentTypesForTypeInference, argumentTypes.Length); - if (invocationConstraints != null && invocationConstraints.ParameterTypes != null) + if (genericParamTypes is not null) + { + try + { + // This cast is safe, because + // 1. Only ConstructorInfo and MethodInfo derive from MethodBase + // 2. ConstructorInfo.IsGenericMethod is always false + var originalMethod = (MethodInfo)methodInfo.method; + methodInfo = new MethodInformation( + originalMethod.MakeGenericMethod(genericParamTypes), + parametersToIgnore: 0); + } + catch (ArgumentException) + { + // Just skip this possibility if the generic type parameters can't be used to make + // a valid generic method here. + continue; + } + } + else { - int parameterIndex = 0; - foreach (Type typeConstraintFromCallSite in invocationConstraints.ParameterTypes) + // Infer the generic method when generic parameter types are not specified. + Type[] argumentTypes = arguments.Select(EffectiveArgumentType).ToArray(); + Type[] paramConstraintTypes = invocationConstraints?.ParameterTypes; + + if (paramConstraintTypes is not null) { - if (typeConstraintFromCallSite != null) + for (int k = 0; k < paramConstraintTypes.Length; k++) { - argumentTypesForTypeInference[parameterIndex] = typeConstraintFromCallSite; + if (paramConstraintTypes[k] is not null) + { + argumentTypes[k] = paramConstraintTypes[k]; + } } - - parameterIndex++; } - } - method = TypeInference.Infer(method, argumentTypesForTypeInference); - if (method == null) - { - // Skip generic methods for which we cannot infer type arguments - continue; + methodInfo = TypeInference.Infer(methodInfo, argumentTypes); + if (methodInfo is null) + { + // Skip generic methods for which we cannot infer type arguments + continue; + } } } - if (!IsInvocationTargetConstraintSatisfied(method, invocationConstraints)) + if (!IsInvocationTargetConstraintSatisfied(methodInfo, invocationConstraints)) { continue; } - ParameterInformation[] parameters = method.parameters; + ParameterInformation[] parameters = methodInfo.parameters; if (arguments.Length != parameters.Length) { // Skip methods w/ an incorrect # of arguments. @@ -1454,7 +1500,7 @@ private static MethodInformation FindBestMethodImpl( if (arguments.Length > parameters.Length) { // If too many args,it's only OK if the method is varargs. - if (!method.hasVarArgs) + if (!methodInfo.hasVarArgs) { continue; } @@ -1462,12 +1508,12 @@ private static MethodInformation FindBestMethodImpl( else { // Too few args, OK if there are optionals, or varargs with the param array omitted - if (!method.hasOptional && (!method.hasVarArgs || (arguments.Length + 1) != parameters.Length)) + if (!methodInfo.hasOptional && (!methodInfo.hasVarArgs || (arguments.Length + 1) != parameters.Length)) { continue; } - if (method.hasOptional) + if (methodInfo.hasOptional) { // Count optionals. This code is rarely hit, mainly when calling code in the // assembly Microsoft.VisualBasic. If it were more frequent, the optional count @@ -1490,7 +1536,7 @@ private static MethodInformation FindBestMethodImpl( } } - OverloadCandidate candidate = new OverloadCandidate(method, arguments.Length); + OverloadCandidate candidate = new OverloadCandidate(methodInfo, arguments.Length); for (int j = 0; candidate != null && j < parameters.Length; j++) { ParameterInformation parameter = parameters[j]; @@ -1581,7 +1627,7 @@ private static MethodInformation FindBestMethodImpl( if (candidates.Count == 0) { - if ((methods.Length > 0) && (methods.All(m => m.method != null && m.method.DeclaringType.IsGenericTypeDefinition && m.method.IsStatic))) + if (methods.Length > 0 && methods.All(static m => m.method != null && m.method.DeclaringType.IsGenericTypeDefinition && m.method.IsStatic)) { errorId = "CannotInvokeStaticMethodOnUninstantiatedGenericType"; errorMsg = string.Format( @@ -1590,6 +1636,16 @@ private static MethodInformation FindBestMethodImpl( methods[0].method.DeclaringType.FullName); return null; } + else if (genericParamTypes is not null) + { + errorId = "MethodCountCouldNotFindBestGeneric"; + errorMsg = string.Format( + ExtendedTypeSystem.MethodGenericArgumentCountException, + methods[0].method.Name, + genericParamTypes.Length, + arguments.Length); + return null; + } else { errorId = "MethodCountCouldNotFindBest"; @@ -1614,18 +1670,21 @@ private static MethodInformation FindBestMethodImpl( internal static Type EffectiveArgumentType(object arg) { - if (arg != null) + arg = PSObject.Base(arg); + if (arg is null) + { + return typeof(LanguagePrimitives.Null); + } + + if (arg is object[] array && array.Length > 0) { - arg = PSObject.Base(arg); - object[] argAsArray = arg as object[]; - if (argAsArray != null && argAsArray.Length > 0 && PSObject.Base(argAsArray[0]) != null) + Type firstType = GetObjectType(array[0], debase: true); + if (firstType is not null) { - Type firstType = PSObject.Base(argAsArray[0]).GetType(); bool allSameType = true; - - for (int j = 1; j < argAsArray.Length; ++j) + for (int j = 1; j < array.Length; ++j) { - if (argAsArray[j] == null || firstType != PSObject.Base(argAsArray[j]).GetType()) + if (firstType != GetObjectType(array[j], debase: true)) { allSameType = false; break; @@ -1637,13 +1696,19 @@ internal static Type EffectiveArgumentType(object arg) return firstType.MakeArrayType(); } } - - return arg.GetType(); } - else + + return GetObjectType(arg, debase: false); + } + + internal static Type GetObjectType(object obj, bool debase) + { + if (debase) { - return typeof(LanguagePrimitives.Null); + obj = PSObject.Base(obj); } + + return obj == NullString.Value ? typeof(string) : obj?.GetType(); } internal static void SetReferences(object[] arguments, MethodInformation methodInformation, object[] originalArguments) @@ -1761,7 +1826,7 @@ internal static object[] GetMethodArgumentsBase(string methodName, } // We are going to put all the remaining arguments into an array - // and convert them to the propper type, if necessary to be the + // and convert them to the proper type, if necessary to be the // one argument for this last parameter int remainingArgumentCount = arguments.Length - parametersLength + 1; if (remainingArgumentCount == 1 && arguments[arguments.Length - 1] == null) @@ -1813,7 +1878,7 @@ internal static object[] GetMethodArgumentsBase(string methodName, } /// - /// Auxiliary method in MethodInvoke to set newArguments[index] with the propper value. + /// Auxiliary method in MethodInvoke to set newArguments[index] with the proper value. /// /// Used for the MethodException that might be thrown. /// The complete array of arguments. @@ -2165,7 +2230,7 @@ internal object Invoke(object target, object[] arguments) // be thrown when converting arguments to the ByRef-like parameter types. // // So when reaching here, we only care about (1) if the method return type is - // BeRef-like; (2) if it's a constrcutor of a ByRef-like type. + // BeRef-like; (2) if it's a constructor of a ByRef-like type. if (method is ConstructorInfo ctor) { @@ -2202,10 +2267,7 @@ internal object Invoke(object target, object[] arguments) if (!_useReflection) { - if (_methodInvoker == null) - { - _methodInvoker = GetMethodInvoker(methodInfo); - } + _methodInvoker ??= GetMethodInvoker(methodInfo); if (_methodInvoker != null) { @@ -2616,7 +2678,7 @@ internal class MethodCacheEntry : CacheEntry /// internal Func PSMethodCtor; - internal MethodCacheEntry(MethodBase[] methods) + internal MethodCacheEntry(IList methods) { methodInformationStructures = DotNetAdapter.GetMethodInformationArray(methods); } @@ -2773,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 @@ -2785,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; } @@ -2991,10 +3053,7 @@ internal override bool IsHidden { get { - if (_isHidden == null) - { - _isHidden = member.GetCustomAttributes(typeof(HiddenAttribute), inherit: false).Length != 0; - } + _isHidden ??= member.GetCustomAttributes(typeof(HiddenAttribute), inherit: false).Length != 0; return _isHidden.Value; } @@ -3075,9 +3134,8 @@ private static void AddOverload(List previousMethodEntry, MethodInfo private static void PopulateMethodReflectionTable(Type type, MethodInfo[] methods, CacheTable typeMethods) { - for (int i = 0; i < methods.Length; i++) + foreach (MethodInfo method in methods) { - MethodInfo method = methods[i]; if (method.DeclaringType == type) { string methodName = method.Name; @@ -3102,7 +3160,7 @@ private static void PopulateMethodReflectionTable(Type type, MethodInfo[] method private static void PopulateMethodReflectionTable(ConstructorInfo[] ctors, CacheTable typeMethods) { - foreach (var ctor in ctors) + foreach (ConstructorInfo ctor in ctors) { var previousMethodEntry = (List)typeMethods["new"]; if (previousMethodEntry == null) @@ -3127,26 +3185,14 @@ private static void PopulateMethodReflectionTable(ConstructorInfo[] ctors, Cache /// BindingFlags to use. private static void PopulateMethodReflectionTable(Type type, CacheTable typeMethods, BindingFlags bindingFlags) { - Type typeToGetMethod = type; - - // Assemblies in CoreCLR might not allow reflection execution on their internal types. In such case, we walk up - // the derivation chain to find the first public parent, and use reflection methods on the public parent. - if (!TypeResolver.IsPublic(type) && DisallowPrivateReflection(type)) - { - typeToGetMethod = GetFirstPublicParentType(type); - } + bool isStatic = bindingFlags.HasFlag(BindingFlags.Static); - // In CoreCLR, "GetFirstPublicParentType" may return null if 'type' is an interface - if (typeToGetMethod != null) - { - MethodInfo[] methods = typeToGetMethod.GetMethods(bindingFlags); - PopulateMethodReflectionTable(typeToGetMethod, methods, typeMethods); - } + MethodInfo[] methods = type.GetMethods(bindingFlags); + PopulateMethodReflectionTable(type, methods, typeMethods); Type[] interfaces = type.GetInterfaces(); - for (int interfaceIndex = 0; interfaceIndex < interfaces.Length; interfaceIndex++) + foreach (Type interfaceType in interfaces) { - var interfaceType = interfaces[interfaceIndex]; if (!TypeResolver.IsPublic(interfaceType)) { continue; @@ -3154,41 +3200,50 @@ private static void PopulateMethodReflectionTable(Type type, CacheTable typeMeth if (interfaceType.IsGenericType && type.IsArray) { - continue; // GetInterfaceMap is not supported in this scenario... not sure if we need to do something special here... + // A bit of background: Array doesn't directly support any generic interface at all. Instead, a stub class + // named 'SZArrayHelper' provides these generic interfaces at runtime for zero-based one-dimension arrays. + // This is why '[object[]].GetInterfaceMap([ICollection[object]])' throws 'ArgumentException'. + // (see https://stackoverflow.com/a/31883327) + // + // We had always been skipping generic interfaces for array types because 'GetInterfaceMap' doesn't work + // for it. Today, even though we don't use 'GetInterfaceMap' anymore, the same code is kept here because + // methods from generic interfaces of an array type could cause ambiguity in method overloads resolution. + // For example, "$objs = @(1,2,3,4); $objs.Contains(1)" would fail because there would be 2 overloads of + // the 'Contains' methods which are equally good matches for the call. + // bool IList.Contains(System.Object value) + // bool ICollection[Object].Contains(System.Object item) + continue; } - MethodInfo[] methods; - if (type.IsInterface) - { - methods = interfaceType.GetMethods(bindingFlags); - } - else - { - InterfaceMapping interfaceMapping = type.GetInterfaceMap(interfaceType); - methods = interfaceMapping.InterfaceMethods; - } + methods = interfaceType.GetMethods(bindingFlags); - for (int methodIndex = 0; methodIndex < methods.Length; methodIndex++) + foreach (MethodInfo interfaceMethod in methods) { - MethodInfo interfaceMethodDefinition = methods[methodIndex]; - - if ((!interfaceMethodDefinition.IsPublic) || - (interfaceMethodDefinition.IsStatic != ((BindingFlags.Static & bindingFlags) != 0))) + if (isStatic && interfaceMethod.IsVirtual) { + // Ignore static virtual/abstract methods on an interface because: + // 1. if it's implicitly implemented, which will be mostly the case, then the corresponding + // methods were already retrieved from the 'type.GetMethods' step above; + // 2. if it's explicitly implemented, we cannot call 'Invoke(null, args)' on the static method, + // but have to use 'type.GetInterfaceMap(interfaceType)' to get the corresponding target + // methods, and call 'Invoke(null, args)' on them. The target methods will be non-public + // in this case, which we always ignore. + // 3. The recommendation from .NET team is to ignore the static virtuals on interfaces, + // especially given that the APIs may change in .NET 7. continue; } - var previousMethodEntry = (List)typeMethods[interfaceMethodDefinition.Name]; + var previousMethodEntry = (List)typeMethods[interfaceMethod.Name]; if (previousMethodEntry == null) { - var methodEntry = new List { interfaceMethodDefinition }; - typeMethods.Add(interfaceMethodDefinition.Name, methodEntry); + var methodEntry = new List { interfaceMethod }; + typeMethods.Add(interfaceMethod.Name, methodEntry); } else { - if (!previousMethodEntry.Contains(interfaceMethodDefinition)) + if (!previousMethodEntry.Contains(interfaceMethod)) { - previousMethodEntry.Add(interfaceMethodDefinition); + previousMethodEntry.Add(interfaceMethod); } } } @@ -3211,7 +3266,7 @@ private static void PopulateMethodReflectionTable(Type type, CacheTable typeMeth for (int i = 0; i < typeMethods.memberCollection.Count; i++) { typeMethods.memberCollection[i] = - new MethodCacheEntry(((List)typeMethods.memberCollection[i]).ToArray()); + new MethodCacheEntry((List)typeMethods.memberCollection[i]); } } @@ -3224,38 +3279,24 @@ private static void PopulateMethodReflectionTable(Type type, CacheTable typeMeth /// BindingFlags to use. private static void PopulateEventReflectionTable(Type type, Dictionary typeEvents, BindingFlags bindingFlags) { - // Assemblies in CoreCLR might not allow reflection execution on their internal types. In such case, we walk up - // the derivation chain to find the first public parent, and use reflection events on the public parent. - if (!TypeResolver.IsPublic(type) && DisallowPrivateReflection(type)) - { - type = GetFirstPublicParentType(type); - } + EventInfo[] events = type.GetEvents(bindingFlags); + var tempTable = new Dictionary>(StringComparer.OrdinalIgnoreCase); - // In CoreCLR, "GetFirstPublicParentType" may return null if 'type' is an interface - if (type != null) + foreach (EventInfo typeEvent in events) { - EventInfo[] events = type.GetEvents(bindingFlags); - var tempTable = new Dictionary>(StringComparer.OrdinalIgnoreCase); - for (int i = 0; i < events.Length; i++) + string eventName = typeEvent.Name; + if (!tempTable.TryGetValue(eventName, out List entryList)) { - var typeEvent = events[i]; - string eventName = typeEvent.Name; - List previousEntry; - if (!tempTable.TryGetValue(eventName, out previousEntry)) - { - var eventEntry = new List { typeEvent }; - tempTable.Add(eventName, eventEntry); - } - else - { - previousEntry.Add(typeEvent); - } + entryList = new List(); + tempTable.Add(eventName, entryList); } - foreach (var entry in tempTable) - { - typeEvents.Add(entry.Key, new EventCacheEntry(entry.Value.ToArray())); - } + entryList.Add(typeEvent); + } + + foreach (KeyValuePair> entry in tempTable) + { + typeEvents.Add(entry.Key, new EventCacheEntry(entry.Value.ToArray())); } } @@ -3270,9 +3311,8 @@ private static bool PropertyAlreadyPresent(List previousProperties ParameterInfo[] propertyParameters = property.GetIndexParameters(); int propertyIndexLength = propertyParameters.Length; - for (int propertyIndex = 0; propertyIndex < previousProperties.Count; propertyIndex++) + foreach (PropertyInfo previousProperty in previousProperties) { - var previousProperty = previousProperties[propertyIndex]; ParameterInfo[] previousParameters = previousProperty.GetIndexParameters(); if (previousParameters.Length == propertyIndexLength) { @@ -3308,79 +3348,81 @@ private static bool PropertyAlreadyPresent(List previousProperties /// BindingFlags to use. private static void PopulatePropertyReflectionTable(Type type, CacheTable typeProperties, BindingFlags bindingFlags) { + bool isStatic = bindingFlags.HasFlag(BindingFlags.Static); var tempTable = new Dictionary>(StringComparer.OrdinalIgnoreCase); - Type typeToGetPropertyAndField = type; - - // Assemblies in CoreCLR might not allow reflection execution on their internal types. In such case, we walk up the - // derivation chain to find the first public parent, and use reflection properties/fields on the public parent. - if (!TypeResolver.IsPublic(type) && DisallowPrivateReflection(type)) - { - typeToGetPropertyAndField = GetFirstPublicParentType(type); - } - // In CoreCLR, "GetFirstPublicParentType" may return null if 'type' is an interface - PropertyInfo[] properties; - if (typeToGetPropertyAndField != null) + PropertyInfo[] properties = type.GetProperties(bindingFlags); + foreach (PropertyInfo property in properties) { - properties = typeToGetPropertyAndField.GetProperties(bindingFlags); - for (int i = 0; i < properties.Length; i++) - { - PopulateSingleProperty(type, properties[i], tempTable, properties[i].Name); - } + PopulateSingleProperty(type, property, tempTable, property.Name); } Type[] interfaces = type.GetInterfaces(); - for (int interfaceIndex = 0; interfaceIndex < interfaces.Length; interfaceIndex++) + foreach (Type interfaceType in interfaces) { - Type interfaceType = interfaces[interfaceIndex]; if (!TypeResolver.IsPublic(interfaceType)) { continue; } properties = interfaceType.GetProperties(bindingFlags); - for (int propertyIndex = 0; propertyIndex < properties.Length; propertyIndex++) + foreach (PropertyInfo property in properties) { - PopulateSingleProperty(type, properties[propertyIndex], tempTable, properties[propertyIndex].Name); + if (isStatic && + (property.GetMethod?.IsVirtual == true || property.SetMethod?.IsVirtual == true)) + { + // Ignore static virtual/abstract properties on an interface because: + // 1. if it's implicitly implemented, which will be mostly the case, then the corresponding + // properties were already retrieved from the 'type.GetProperties' step above; + // 2. if it's explicitly implemented, we cannot call 'GetValue(null)' on the static property, + // but have to use 'type.GetInterfaceMap(interfaceType)' to get the corresponding target + // get/set accessor methods, and call 'Invoke(null, args)' on them. The target methods will + // be non-public in this case, which we always ignore. + // 3. The recommendation from .NET team is to ignore the static virtuals on interfaces, + // especially given that the APIs may change in .NET 7. + continue; + } + + PopulateSingleProperty(type, property, tempTable, property.Name); } } - foreach (var pairs in tempTable) + foreach (KeyValuePair> entry in tempTable) { - var propertiesList = pairs.Value; + List propertiesList = entry.Value; PropertyInfo firstProperty = propertiesList[0]; if ((propertiesList.Count > 1) || (firstProperty.GetIndexParameters().Length != 0)) { - typeProperties.Add(pairs.Key, new ParameterizedPropertyCacheEntry(propertiesList)); + typeProperties.Add(entry.Key, new ParameterizedPropertyCacheEntry(propertiesList)); } else { - typeProperties.Add(pairs.Key, new PropertyCacheEntry(firstProperty)); + typeProperties.Add(entry.Key, new PropertyCacheEntry(firstProperty)); } } - // In CoreCLR, "GetFirstPublicParentType" may return null if 'type' is an interface - if (typeToGetPropertyAndField != null) + FieldInfo[] fields = type.GetFields(bindingFlags); + foreach (FieldInfo field in fields) { - FieldInfo[] fields = typeToGetPropertyAndField.GetFields(bindingFlags); - for (int i = 0; i < fields.Length; i++) + string fieldName = field.Name; + var previousMember = (PropertyCacheEntry)typeProperties[fieldName]; + if (previousMember == null) { - FieldInfo field = fields[i]; - string fieldName = field.Name; - var previousMember = (PropertyCacheEntry)typeProperties[fieldName]; - if (previousMember == null) - { - typeProperties.Add(fieldName, new PropertyCacheEntry(field)); - } - else - { - // A property/field declared with new in a derived class might appear twice - if (!string.Equals(previousMember.member.Name, fieldName)) - { - throw new ExtendedTypeSystemException("NotACLSComplaintField", null, - ExtendedTypeSystem.NotAClsCompliantFieldProperty, fieldName, type.FullName, previousMember.member.Name); - } - } + typeProperties.Add(fieldName, new PropertyCacheEntry(field)); + } + else if (!string.Equals(previousMember.member.Name, fieldName)) + { + // A property/field declared with 'new' in a derived class might appear twice, and it's OK to ignore + // the second property/field in that case. + // However, if the names of two properties/fields are different only in letter casing, then it's not + // CLS complaint and we throw an exception. + throw new ExtendedTypeSystemException( + "NotACLSComplaintField", + innerException: null, + ExtendedTypeSystem.NotAClsCompliantFieldProperty, + fieldName, + type.FullName, + previousMember.member.Name); } } } @@ -3411,66 +3453,6 @@ private static void PopulateSingleProperty(Type type, PropertyInfo property, Dic } } - #region Handle_Internal_Type_Reflection_In_CoreCLR - - /// - /// The dictionary cache about if an assembly supports reflection execution on its internal types. - /// - private static readonly ConcurrentDictionary s_disallowReflectionCache = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// Check if the type is defined in an assembly that disallows reflection execution on internal types. - /// - .NET Framework assemblies don't support reflection execution on their internal types. - /// - internal static bool DisallowPrivateReflection(Type type) - { - bool disallowReflection = false; - Assembly assembly = type.Assembly; - if (s_disallowReflectionCache.TryGetValue(assembly.FullName, out disallowReflection)) - { - return disallowReflection; - } - - var productAttribute = assembly.GetCustomAttribute(); - if (productAttribute != null && string.Equals(productAttribute.Product, "Microsoft® .NET Framework", StringComparison.OrdinalIgnoreCase)) - { - disallowReflection = true; - } - else - { - // Check for 'DisablePrivateReflectionAttribute'. It's applied at the assembly level, and allow an assembly to opt-out of private/internal reflection. - var disablePrivateReflectionAttribute = assembly.GetCustomAttribute(); - disallowReflection = disablePrivateReflectionAttribute != null; - } - - s_disallowReflectionCache.TryAdd(assembly.FullName, disallowReflection); - return disallowReflection; - } - - /// - /// Walk up the derivation chain to find the first public parent type. - /// - internal static Type GetFirstPublicParentType(Type type) - { - Dbg.Assert(!TypeResolver.IsPublic(type), "type should not be public."); - Type parent = type.BaseType; - while (parent != null) - { - if (parent.IsPublic) - { - return parent; - } - - parent = parent.BaseType; - } - - // Return null when type is an interface - return null; - } - - #endregion Handle_Internal_Type_Reflection_In_CoreCLR - /// /// Called from GetProperty and GetProperties to populate the /// typeTable with all public properties and fields @@ -3886,6 +3868,33 @@ private static bool PropertyIsStatic(PSProperty property) return entry.isStatic; } + /// + /// Get the string representation of the default value of passed-in parameter. + /// + /// ParameterInfo containing the parameter's default value. + /// String representation of the parameter's default value. + private static string GetDefaultValueStringRepresentation(ParameterInfo parameterInfo) + { + var parameterType = parameterInfo.ParameterType; + var parameterDefaultValue = parameterInfo.DefaultValue; + + if (parameterDefaultValue == null) + { + return (parameterType.IsValueType || parameterType.IsGenericMethodParameter) + ? "default" + : "null"; + } + + if (parameterType.IsEnum) + { + return string.Create(CultureInfo.InvariantCulture, $"{parameterType}.{parameterDefaultValue}"); + } + + return (parameterDefaultValue is string) + ? string.Create(CultureInfo.InvariantCulture, $"\"{parameterDefaultValue}\"") + : parameterDefaultValue.ToString(); + } + #endregion auxiliary methods and classes #region virtual @@ -3911,11 +3920,11 @@ protected override ConsolidatedString GetInternedTypeNameHierarchy(object obj) /// /// Get the .NET member based on the given member name. /// - /// + /// /// Dynamic members of an object that implements IDynamicMetaObjectProvider are not included because /// 1. Dynamic members cannot be invoked via reflection; /// 2. Access to dynamic members is handled by the DLR for free. - /// + /// /// Object to retrieve the PSMemberInfo from. /// Name of the member to be retrieved. /// @@ -3948,10 +3957,10 @@ protected override T GetFirstMemberOrDefault(object obj, MemberNamePredicate /// In the case of the DirectoryEntry adapter, this could be a cache of the objectClass /// to the properties available in it. /// - /// + /// /// Dynamic members of an object that implements IDynamicMetaObjectProvider are included because /// we want to view the dynamic members via 'Get-Member' and be able to auto-complete those members. - /// + /// /// Object to get all the member information from. /// All members in obj. protected override PSMemberInfoInternalCollection GetMembers(object obj) @@ -4284,11 +4293,10 @@ internal static object AuxiliaryMethodInvoke(object target, object[] arguments, /// /// The methods to be converted. /// The MethodInformation[] corresponding to methods. - internal static MethodInformation[] GetMethodInformationArray(MethodBase[] methods) + internal static MethodInformation[] GetMethodInformationArray(IList methods) { - int methodCount = methods.Length; - MethodInformation[] returnValue = new MethodInformation[methodCount]; - for (int i = 0; i < methods.Length; i++) + var returnValue = new MethodInformation[methods.Count]; + for (int i = 0; i < methods.Count; i++) { returnValue[i] = new MethodInformation(methods[i], 0); } @@ -4357,7 +4365,7 @@ private static object InvokeResolvedConstructor(MethodInformation bestMethod, ob /// /// This is a flavor of MethodInvokeDotNet to deal with a peculiarity of property setters: - /// Tthe setValue is always the last parameter. This enables a parameter after a varargs or optional + /// The setValue is always the last parameter. This enables a parameter after a varargs or optional /// parameters and GetBestMethodAndArguments is not prepared for that. /// This method disregards the last parameter in its call to GetBestMethodAndArguments used in this case /// more for its "Arguments" side than for its "BestMethod" side, since there is only one method. @@ -4433,7 +4441,7 @@ internal static string GetMethodInfoOverloadDefinition(string memberName, Method } builder.Append(memberName ?? methodEntry.Name); - if (methodEntry.IsGenericMethodDefinition) + if (methodEntry.IsGenericMethodDefinition || methodEntry.IsGenericMethod) { builder.Append('['); @@ -4479,6 +4487,13 @@ internal static string GetMethodInfoOverloadDefinition(string memberName, Method builder.Append(ToStringCodeMethods.Type(parameterType)); builder.Append(' '); builder.Append(parameter.Name); + + if (parameter.HasDefaultValue) + { + builder.Append(" = "); + builder.Append(GetDefaultValueStringRepresentation(parameter)); + } + builder.Append(", "); } @@ -4533,7 +4548,7 @@ protected override Collection MethodDefinitions(PSMethod method) MethodCacheEntry methodEntry = (MethodCacheEntry)method.adapterData; IList uniqueValues = methodEntry .methodInformationStructures - .Select(m => m.methodDefinition) + .Select(static m => m.methodDefinition) .Distinct(StringComparer.Ordinal) .ToList(); return new Collection(uniqueValues); @@ -4750,6 +4765,7 @@ protected override T GetFirstMemberOrDefault(object obj, MemberNamePredicate #endregion +#if !UNIX /// /// Used only to add a COM style type name to a COM interop .NET type. /// @@ -4780,6 +4796,8 @@ protected override ConsolidatedString GetInternedTypeNameHierarchy(object obj) return new ConsolidatedString(GetTypeNameHierarchy(obj), interned: true); } } +#endif + /// /// Adapter used for GetMember and GetMembers only. /// All other methods will not be called. @@ -5422,7 +5440,7 @@ private static object GetNodeObject(XmlNode node) } XmlNodeList nodeChildren = node.ChildNodes; - // nodeChildren will not be null as we already verified iff the node has children. + // nodeChildren will not be null as we already verified that the node has children. if ((nodeChildren.Count == 1) && (nodeChildren[0].NodeType == XmlNodeType.Text)) { return node.InnerText; @@ -5903,7 +5921,7 @@ private static MethodInfo Infer(MethodInfo genericMethod, Type[] typesOfMethodAr } Type[] typeParameters = genericMethod.GetGenericArguments(); - Type[] typesOfMethodParameters = genericMethod.GetParameters().Select(p => p.ParameterType).ToArray(); + Type[] typesOfMethodParameters = genericMethod.GetParameters().Select(static p => p.ParameterType).ToArray(); MethodInfo inferredMethod = Infer(genericMethod, typeParameters, typesOfMethodParameters, typesOfMethodArguments); @@ -5944,7 +5962,7 @@ private static MethodInfo Infer(MethodInfo genericMethod, ICollection type { s_tracer.WriteLine( "Types of method arguments: {0}", - string.Join(", ", typesOfMethodArguments.Select(t => t.ToString()).ToArray())); + string.Join(", ", typesOfMethodArguments.Select(static t => t.ToString()).ToArray())); } var typeInference = new TypeInference(typeParameters); @@ -5954,7 +5972,7 @@ private static MethodInfo Infer(MethodInfo genericMethod, ICollection type } IEnumerable inferredTypeParameters = typeParameters.Select(typeInference.GetInferredType); - if (inferredTypeParameters.Any(inferredType => inferredType == null)) + if (inferredTypeParameters.Any(static inferredType => inferredType == null)) { return null; } @@ -5962,7 +5980,7 @@ private static MethodInfo Infer(MethodInfo genericMethod, ICollection type try { MethodInfo instantiatedMethod = genericMethod.MakeGenericMethod(inferredTypeParameters.ToArray()); - s_tracer.WriteLine("Inference succesful: {0}", instantiatedMethod); + s_tracer.WriteLine("Inference successful: {0}", instantiatedMethod); return instantiatedMethod; } catch (ArgumentException e) @@ -5990,7 +6008,7 @@ internal TypeInference(ICollection typeParameters) #endif _typeParameterIndexToSetOfInferenceCandidates = new HashSet[typeParameters.Count]; #if DEBUG - List listOfTypeParameterPositions = typeParameters.Select(t => t.GenericParameterPosition).ToList(); + List listOfTypeParameterPositions = typeParameters.Select(static t => t.GenericParameterPosition).ToList(); listOfTypeParameterPositions.Sort(); Dbg.Assert( listOfTypeParameterPositions.Count == listOfTypeParameterPositions.Distinct().Count(), @@ -6022,9 +6040,9 @@ internal Type GetInferredType(Type typeParameter) ICollection inferenceCandidates = _typeParameterIndexToSetOfInferenceCandidates[typeParameter.GenericParameterPosition]; - if ((inferenceCandidates != null) && (inferenceCandidates.Any(t => t == typeof(LanguagePrimitives.Null)))) + if ((inferenceCandidates != null) && (inferenceCandidates.Any(static t => t == typeof(LanguagePrimitives.Null)))) { - Type firstValueType = inferenceCandidates.FirstOrDefault(t => t.IsValueType); + Type firstValueType = inferenceCandidates.FirstOrDefault(static t => t.IsValueType); if (firstValueType != null) { s_tracer.WriteLine("Cannot reconcile null and {0} (a value type)", firstValueType); @@ -6033,7 +6051,7 @@ internal Type GetInferredType(Type typeParameter) } else { - inferenceCandidates = inferenceCandidates.Where(t => t != typeof(LanguagePrimitives.Null)).ToList(); + inferenceCandidates = inferenceCandidates.Where(static t => t != typeof(LanguagePrimitives.Null)).ToList(); if (inferenceCandidates.Count == 0) { inferenceCandidates = null; diff --git a/src/System.Management.Automation/engine/Credential.cs b/src/System.Management.Automation/engine/Credential.cs index 86557c922c6..b2be0dbfd2e 100644 --- a/src/System.Management.Automation/engine/Credential.cs +++ b/src/System.Management.Automation/engine/Credential.cs @@ -16,7 +16,7 @@ namespace System.Management.Automation { /// - /// Defines the valid types of MSH credentials. Used by PromptForCredential calls. + /// Defines the valid types of PSCredentials. Used by PromptForCredential calls. /// [Flags] public enum PSCredentialTypes @@ -85,7 +85,7 @@ public enum PSCredentialUIOptions /// Offers a centralized way to manage usernames, passwords, and /// credentials. /// - [Serializable()] + [Serializable] public sealed class PSCredential : ISerializable { /// diff --git a/src/System.Management.Automation/engine/DataStoreAdapter.cs b/src/System.Management.Automation/engine/DataStoreAdapter.cs index 9bf7611d7ff..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. /// - [Dbg.TraceSourceAttribute( + [Dbg.TraceSource( "PSDriveInfo", "The namespace navigation tracer")] private static readonly Dbg.PSTraceSource s_tracer = @@ -289,7 +289,7 @@ protected PSDriveInfo(PSDriveInfo driveInfo) } /// - /// Constructs a drive that maps an MSH Path in + /// Constructs a drive that maps a PowerShell Path in /// the shell to a Cmdlet Provider. /// /// @@ -366,7 +366,7 @@ public PSDriveInfo( } /// - /// Constructs a drive that maps an MSH Path in + /// Constructs a drive that maps a PowerShell Path in /// the shell to a Cmdlet Provider. /// /// @@ -408,7 +408,7 @@ public PSDriveInfo( } /// - /// Constructs a drive that maps an MSH Path in + /// Constructs a drive that maps a PowerShell Path in /// the shell to a Cmdlet Provider. /// /// diff --git a/src/System.Management.Automation/engine/DataStoreAdapterProvider.cs b/src/System.Management.Automation/engine/DataStoreAdapterProvider.cs index 69202e6d20e..3ea2fd00fff 100644 --- a/src/System.Management.Automation/engine/DataStoreAdapterProvider.cs +++ b/src/System.Management.Automation/engine/DataStoreAdapterProvider.cs @@ -201,7 +201,7 @@ public Provider.ProviderCapabilities Capabilities /// /// /// The location can be either a fully qualified provider path - /// or an Msh path. This is the location that is substituted for the ~. + /// or a PowerShell path. This is the location that is substituted for the ~. /// public string Home { get; set; } @@ -368,7 +368,7 @@ internal ProviderInfo( /// The description of the provider. /// /// - /// The home path for the provider. This must be an MSH path. + /// The home path for the provider. This must be a PowerShell path. /// /// /// The help file for the provider. diff --git a/src/System.Management.Automation/engine/DefaultCommandRuntime.cs b/src/System.Management.Automation/engine/DefaultCommandRuntime.cs index d45abaaffbc..746d809d831 100644 --- a/src/System.Management.Automation/engine/DefaultCommandRuntime.cs +++ b/src/System.Management.Automation/engine/DefaultCommandRuntime.cs @@ -21,8 +21,7 @@ internal class DefaultCommandRuntime : ICommandRuntime2 /// public DefaultCommandRuntime(List outputList) { - if (outputList == null) - throw new System.ArgumentNullException(nameof(outputList)); + ArgumentNullException.ThrowIfNull(outputList); _output = outputList; } @@ -65,7 +64,7 @@ public void WriteObject(object sendToPipeline) /// /// Default implementation of the enumerated WriteObject. Either way, the - /// objects are added to the list passed to this object in the constuctor. + /// objects are added to the list passed to this object in the constructor. /// /// Object to write. /// If true, the collection is enumerated, otherwise @@ -230,6 +229,7 @@ public PSTransactionContext CurrentPSTransaction /// if it exists, otherwise throw an invalid operation exception. /// /// The error record to throw. + [System.Diagnostics.CodeAnalysis.DoesNotReturn] public void ThrowTerminatingError(ErrorRecord errorRecord) { if (errorRecord.Exception != null) diff --git a/src/System.Management.Automation/engine/DriveInterfaces.cs b/src/System.Management.Automation/engine/DriveInterfaces.cs index 299faa8b76e..c93e47d8c1c 100644 --- a/src/System.Management.Automation/engine/DriveInterfaces.cs +++ b/src/System.Management.Automation/engine/DriveInterfaces.cs @@ -70,7 +70,7 @@ public PSDriveInfo Current #region New /// - /// Creates a new MSH drive in session state. + /// Creates a new PSDrive in session state. /// /// /// The drive to be created. diff --git a/src/System.Management.Automation/engine/EngineIntrinsics.cs b/src/System.Management.Automation/engine/EngineIntrinsics.cs index a492a5215fc..e2a63a527a9 100644 --- a/src/System.Management.Automation/engine/EngineIntrinsics.cs +++ b/src/System.Management.Automation/engine/EngineIntrinsics.cs @@ -35,10 +35,7 @@ private EngineIntrinsics() /// internal EngineIntrinsics(ExecutionContext context) { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } + ArgumentNullException.ThrowIfNull(context); _context = context; _host = context.EngineHostInterface; diff --git a/src/System.Management.Automation/engine/ErrorPackage.cs b/src/System.Management.Automation/engine/ErrorPackage.cs index 7d6a1f2759f..b20da137590 100644 --- a/src/System.Management.Automation/engine/ErrorPackage.cs +++ b/src/System.Management.Automation/engine/ErrorPackage.cs @@ -18,7 +18,7 @@ namespace System.Management.Automation { /// - /// Errors reported by Monad will be in one of these categories. + /// Errors reported by PowerShell will be in one of these categories. /// /// /// Do not specify ErrorCategory.NotSpecified when creating an @@ -28,13 +28,15 @@ namespace System.Management.Automation public enum ErrorCategory { /// + /// /// No error category is specified, or the error category is invalid. - /// - /// + /// + /// /// Do not specify ErrorCategory.NotSpecified when creating an /// . /// Choose the best match from among the other values. - /// + /// + /// NotSpecified = 0, /// @@ -132,14 +134,16 @@ public enum ErrorCategory WriteError = 23, /// - /// A non-Monad command reported an error to its STDERR pipe. - /// - /// + /// + /// A native command reported an error to its STDERR pipe. + /// + /// /// The Engine uses this ErrorCategory when it executes a native /// console applications and captures the errors reported by the /// native application. Avoid using ErrorCategory.FromStdErr /// in other circumstances. - /// + /// + /// FromStdErr = 24, /// @@ -193,10 +197,7 @@ public class ErrorCategoryInfo #region ctor internal ErrorCategoryInfo(ErrorRecord errorRecord) { - if (errorRecord == null) - { - throw new ArgumentNullException(nameof(errorRecord)); - } + ArgumentNullException.ThrowIfNull(errorRecord); _errorRecord = errorRecord; } @@ -526,7 +527,6 @@ internal static string Ellipsize(CultureInfo uiCultureInfo, string original) /// It is permitted to subclass /// but there is no established scenario for doing this, nor has it been tested. /// - [Serializable] public class ErrorDetails : ISerializable { #region Constructor @@ -985,7 +985,6 @@ private string BuildMessage( /// . /// rather than the actual exception, to avoid the mutual references. /// - [Serializable] public class ErrorRecord : ISerializable { #region Constructor @@ -1026,10 +1025,7 @@ public ErrorRecord( throw PSTraceSource.NewArgumentNullException(nameof(exception)); } - if (errorId == null) - { - errorId = string.Empty; - } + errorId ??= string.Empty; // targetObject may be null _error = exception; @@ -1693,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(); @@ -1726,10 +1717,10 @@ public ErrorRecord(Exception exception, string errorId, ErrorCategory errorCateg /// information. /// /// - /// MSH defines certain exception classes which implement this interface. + /// PowerShell defines certain exception classes which implement this interface. /// This includes wrapper exceptions such as /// , - /// and also MSH engine errors such as + /// and also PowerShell engine errors such as /// . /// Cmdlets and providers should not define this interface; /// instead, they should use the @@ -1815,6 +1806,7 @@ public interface IContainsErrorRecord /// since the improved /// information about the error may help enable future scenarios. /// +#nullable enable public interface IResourceSupplier { /// diff --git a/src/System.Management.Automation/engine/EventManager.cs b/src/System.Management.Automation/engine/EventManager.cs index fd30298669e..75538135b92 100644 --- a/src/System.Management.Automation/engine/EventManager.cs +++ b/src/System.Management.Automation/engine/EventManager.cs @@ -44,6 +44,7 @@ protected int GetNextEventId() /// /// Creates a PowerShell event. + /// /// /// An optional identifier that identifies the source event /// @@ -56,11 +57,11 @@ protected int GetNextEventId() /// /// Any additional data you wish to attach to the event /// - /// protected abstract PSEventArgs CreateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData); /// /// Generate a PowerShell event. + /// /// /// An optional identifier that identifies the source event /// @@ -73,7 +74,6 @@ protected int GetNextEventId() /// /// Any additional data you wish to attach to the event /// - /// public PSEventArgs GenerateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData) { return this.GenerateEvent(sourceIdentifier, sender, args, extraData, false, false); @@ -81,6 +81,7 @@ public PSEventArgs GenerateEvent(string sourceIdentifier, object sender, object[ /// /// Generate a PowerShell event. + /// /// /// An optional identifier that identifies the source event /// @@ -100,7 +101,6 @@ public PSEventArgs GenerateEvent(string sourceIdentifier, object sender, object[ /// /// Wait for the event and associated action to be processed and completed. /// - /// public PSEventArgs GenerateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData, bool processInCurrentThread, bool waitForCompletionInCurrentThread) { @@ -133,14 +133,15 @@ protected internal virtual void ProcessNewEvent(PSEventArgs newEvent, bool proce /// /// Get the event subscription that corresponds to an identifier + /// /// /// The identifier that identifies the source of the events /// - /// public abstract IEnumerable GetEventSubscribers(string sourceIdentifier); /// /// Subscribes to an event on an object. + /// /// /// The source object that defines the event /// @@ -162,12 +163,12 @@ protected internal virtual void ProcessNewEvent(PSEventArgs newEvent, bool proce /// /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions /// - /// [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] public abstract PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent); /// /// Subscribes to an event on an object. + /// /// /// The source object that defines the event /// @@ -193,12 +194,12 @@ protected internal virtual void ProcessNewEvent(PSEventArgs newEvent, bool proce /// Indicate how many times the subscriber should be triggered before auto-unregister it /// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered /// - /// [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] public abstract PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent, int maxTriggerCount); /// /// Subscribes to an event on an object. + /// /// /// The source object that defines the event /// @@ -220,12 +221,12 @@ protected internal virtual void ProcessNewEvent(PSEventArgs newEvent, bool proce /// /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions /// - /// [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] public abstract PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent); /// /// Subscribes to an event on an object. + /// /// /// The source object that defines the event /// @@ -251,12 +252,12 @@ protected internal virtual void ProcessNewEvent(PSEventArgs newEvent, bool proce /// Indicate how many times the subscriber should be triggered before auto-unregister it /// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered /// - /// [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] public abstract PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent, int maxTriggerCount); /// /// Subscribes to an event on an object. + /// /// /// The source object that defines the event /// @@ -286,7 +287,6 @@ protected internal virtual void ProcessNewEvent(PSEventArgs newEvent, bool proce /// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered /// The default value is zero /// - /// [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] internal virtual PSEventSubscriber SubscribeEvent(object source, string eventName, @@ -303,10 +303,10 @@ internal virtual PSEventSubscriber SubscribeEvent(object source, /// /// Unsubscribes from an event on an object. + /// /// /// The subscriber associated with the event subscription /// - /// public abstract void UnsubscribeEvent(PSEventSubscriber subscriber); /// @@ -367,6 +367,7 @@ public override List Subscribers /// /// Subscribes to an event on an object. + /// /// /// The source object that defines the event /// @@ -388,7 +389,6 @@ public override List Subscribers /// /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions /// - /// [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent) { @@ -397,6 +397,7 @@ public override PSEventSubscriber SubscribeEvent(object source, string eventName /// /// Subscribes to an event on an object. + /// /// /// The source object that defines the event /// @@ -422,7 +423,6 @@ public override PSEventSubscriber SubscribeEvent(object source, string eventName /// Indicate how many times the subscriber should be triggered before auto-unregister it /// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered /// - /// [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent, int maxTriggerCount) { @@ -436,6 +436,7 @@ public override PSEventSubscriber SubscribeEvent(object source, string eventName /// /// Subscribes to an event on an object. + /// /// /// The source object that defines the event /// @@ -465,7 +466,6 @@ public override PSEventSubscriber SubscribeEvent(object source, string eventName /// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered /// The default value is zero /// - /// [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] internal override PSEventSubscriber SubscribeEvent(object source, string eventName, @@ -484,6 +484,7 @@ internal override PSEventSubscriber SubscribeEvent(object source, /// /// Subscribes to an event on an object. + /// /// /// The source object that defines the event /// @@ -505,7 +506,6 @@ internal override PSEventSubscriber SubscribeEvent(object source, /// /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions /// - /// [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent) { @@ -514,6 +514,7 @@ public override PSEventSubscriber SubscribeEvent(object source, string eventName /// /// Subscribes to an event on an object. + /// /// /// The source object that defines the event /// @@ -539,7 +540,6 @@ public override PSEventSubscriber SubscribeEvent(object source, string eventName /// Indicate how many times the subscriber should be triggered before auto-unregister it /// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered /// - /// [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent, int maxTriggerCount) { @@ -797,10 +797,10 @@ private void ProcessNewSubscriber(PSEventSubscriber subscriber, object source, s /// /// Unsubscribes from an event on an object. + /// /// /// The subscriber associated with the event subscription /// - /// public override void UnsubscribeEvent(PSEventSubscriber subscriber) { UnsubscribeEvent(subscriber, false); @@ -808,19 +808,16 @@ public override void UnsubscribeEvent(PSEventSubscriber subscriber) /// /// Unsubscribes from an event on an object. + /// /// /// The subscriber associated with the event subscription /// /// /// Indicate if we should skip draining /// - /// private void UnsubscribeEvent(PSEventSubscriber subscriber, bool skipDraining) { - if (subscriber == null) - { - throw new ArgumentNullException(nameof(subscriber)); - } + ArgumentNullException.ThrowIfNull(subscriber); Delegate existingSubscriber = null; lock (_eventSubscribers) @@ -862,10 +859,7 @@ private void UnsubscribeEvent(PSEventSubscriber subscriber, bool skipDraining) } // Stop the job - if (subscriber.Action != null) - { - subscriber.Action.NotifyJobStopped(); - } + subscriber.Action?.NotifyJobStopped(); lock (_eventSubscribers) { @@ -882,6 +876,7 @@ private void UnsubscribeEvent(PSEventSubscriber subscriber, bool skipDraining) /// /// Creates a PowerShell event. + /// /// /// An optional identifier that identifies the source event /// @@ -894,7 +889,6 @@ private void UnsubscribeEvent(PSEventSubscriber subscriber, bool skipDraining) /// /// Any additional data you wish to attach to the event /// - /// protected override PSEventArgs CreateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData) { return new PSEventArgs(null, _context.CurrentRunspace.InstanceId, GetNextEventId(), sourceIdentifier, sender, args, extraData); @@ -1293,10 +1287,10 @@ internal bool IsExecutingEventAction /// /// Get the event subscription that corresponds to an identifier + /// /// /// The identifier that identifies the source of the events /// - /// public override IEnumerable GetEventSubscribers(string sourceIdentifier) { return GetEventSubscribers(sourceIdentifier, false); @@ -1522,20 +1516,17 @@ public void Dispose() /// /// Stop the timer if it's not null. /// Unsubscribes from all events. + /// /// /// Whether to actually dispose the object. /// - /// public void Dispose(bool disposing) { if (disposing) { lock (_eventSubscribers) { - if (_timer != null) - { - _timer.Dispose(); - } + _timer?.Dispose(); foreach (PSEventSubscriber currentSubscriber in _eventSubscribers.Keys.ToArray()) { @@ -1589,6 +1580,7 @@ public override List Subscribers /// /// Creates a PowerShell event. + /// /// /// An optional identifier that identifies the source event /// @@ -1601,7 +1593,6 @@ public override List Subscribers /// /// Any additional data you wish to attach to the event /// - /// protected override PSEventArgs CreateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData) { // note that this is a local call, so we use null for the computer name @@ -1657,10 +1648,10 @@ protected internal override void ProcessNewEvent(PSEventArgs newEvent, /// /// Get the event subscription that corresponds to an identifier + /// /// /// The identifier that identifies the source of the events /// - /// public override IEnumerable GetEventSubscribers(string sourceIdentifier) { throw new NotSupportedException(EventingResources.RemoteOperationNotSupported); @@ -1668,6 +1659,7 @@ public override IEnumerable GetEventSubscribers(string source /// /// Subscribes to an event on an object. + /// /// /// The source object that defines the event /// @@ -1689,7 +1681,6 @@ public override IEnumerable GetEventSubscribers(string source /// /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions /// - /// [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent) { @@ -1698,6 +1689,7 @@ public override PSEventSubscriber SubscribeEvent(object source, string eventName /// /// Subscribes to an event on an object. + /// /// /// The source object that defines the event /// @@ -1723,7 +1715,6 @@ public override PSEventSubscriber SubscribeEvent(object source, string eventName /// Indicate how many times the subscriber should be triggered before auto-unregister it /// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered /// - /// [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent, int maxTriggerCount) { @@ -1732,6 +1723,7 @@ public override PSEventSubscriber SubscribeEvent(object source, string eventName /// /// Subscribes to an event on an object. + /// /// /// The source object that defines the event /// @@ -1753,7 +1745,6 @@ public override PSEventSubscriber SubscribeEvent(object source, string eventName /// /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions /// - /// [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent) { @@ -1762,6 +1753,7 @@ public override PSEventSubscriber SubscribeEvent(object source, string eventName /// /// Subscribes to an event on an object. + /// /// /// The source object that defines the event /// @@ -1787,7 +1779,6 @@ public override PSEventSubscriber SubscribeEvent(object source, string eventName /// Indicate how many times the subscriber should be triggered before auto-unregister it /// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered /// - /// [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent, int maxTriggerCount) { @@ -1796,10 +1787,10 @@ public override PSEventSubscriber SubscribeEvent(object source, string eventName /// /// Unsubscribes from an event on an object. + /// /// /// The subscriber associated with the event subscription /// - /// public override void UnsubscribeEvent(PSEventSubscriber subscriber) { throw new NotSupportedException(EventingResources.RemoteOperationNotSupported); @@ -2042,12 +2033,25 @@ private ScriptBlock CreateBoundScriptBlock(ScriptBlock scriptAction) #region IComparable Members + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// + /// if the specified object is equal to the current object; + /// otherwise, . + /// + public override bool Equals(object obj) + { + return obj is PSEventSubscriber es && Equals(es); + } + /// /// Determines if two PSEventSubscriber instances are equal + /// /// /// The PSEventSubscriber to which to compare this instance /// - /// public bool Equals(PSEventSubscriber other) { if (other == null) @@ -2091,6 +2095,7 @@ public PSEventHandler() /// /// Creates a new instance of the PsEventHandler class for a given /// event manager, source identifier, and extra data + /// /// /// The event manager to which we forward events. /// @@ -2104,7 +2109,6 @@ public PSEventHandler() /// /// Any additional data you wish to attach to the event /// - /// public PSEventHandler(PSEventManager eventManager, object sender, string sourceIdentifier, PSObject extraData) { this.eventManager = eventManager; @@ -2361,10 +2365,7 @@ public class PSEventArgsCollection : IEnumerable /// Don't add events to the collection directly; use the EventManager instead internal void Add(PSEventArgs eventToAdd) { - if (eventToAdd == null) - { - throw new ArgumentNullException(nameof(eventToAdd)); - } + ArgumentNullException.ThrowIfNull(eventToAdd); _eventCollection.Add(eventToAdd); @@ -2459,6 +2460,7 @@ public class PSEventJob : Job { /// /// Creates a new instance of the PSEventJob class. + /// /// /// The event manager that controls the event subscriptions /// @@ -2471,7 +2473,6 @@ public class PSEventJob : Job /// /// The name of the job /// - /// public PSEventJob( PSEventManager eventManager, PSEventSubscriber subscriber, @@ -2479,10 +2480,9 @@ public PSEventJob( string name) : base(action?.ToString(), name) { - if (eventManager == null) - throw new ArgumentNullException(nameof(eventManager)); - if (subscriber == null) - throw new ArgumentNullException(nameof(subscriber)); + ArgumentNullException.ThrowIfNull(eventManager); + + ArgumentNullException.ThrowIfNull(subscriber); UsesResultsCollection = true; ScriptBlock = action; @@ -2549,13 +2549,13 @@ public override string Location /// /// Invoke the script block + /// /// /// The subscriber that generated this event /// /// /// The context of this event /// - /// internal void Invoke(PSEventSubscriber eventSubscriber, PSEventArgs eventArgs) { if (IsFinishedState(JobStateInfo.State)) diff --git a/src/System.Management.Automation/engine/ExecutionContext.cs b/src/System.Management.Automation/engine/ExecutionContext.cs index 3857de9ba38..ac39eade17b 100644 --- a/src/System.Management.Automation/engine/ExecutionContext.cs +++ b/src/System.Management.Automation/engine/ExecutionContext.cs @@ -53,22 +53,12 @@ internal ScriptDebugger Debugger /// internal void ResetManagers() { - if (_debugger != null) - { - _debugger.ResetDebugger(); - } - - if (Events != null) - { - Events.Dispose(); - } + _debugger?.ResetDebugger(); + Events?.Dispose(); Events = new PSLocalEventManager(this); - if (this.transactionManager != null) - { - this.transactionManager.Dispose(); - } + this.transactionManager?.Dispose(); this.transactionManager = new PSTransactionManager(); } /// @@ -114,10 +104,7 @@ internal bool PSDebugTraceStep // Helper for generated code to handle running w/ no execution context internal static bool IsStrictVersion(ExecutionContext context, int majorVersion) { - if (context == null) - { - context = LocalPipeline.GetExecutionContextFromTLS(); - } + context ??= LocalPipeline.GetExecutionContextFromTLS(); return (context != null) && context.IsStrictVersion(majorVersion); } @@ -205,40 +192,6 @@ internal bool ShouldTraceStatement /// internal string ModuleBeingProcessed { get; set; } - private bool _responsibilityForModuleAnalysisAppDomainOwned; - - internal bool TakeResponsibilityForModuleAnalysisAppDomain() - { - if (_responsibilityForModuleAnalysisAppDomainOwned) - { - return false; - } - - Diagnostics.Assert(AppDomainForModuleAnalysis == null, "Invalid module analysis app domain state"); - _responsibilityForModuleAnalysisAppDomainOwned = true; - return true; - } - - internal void ReleaseResponsibilityForModuleAnalysisAppDomain() - { - Diagnostics.Assert(_responsibilityForModuleAnalysisAppDomainOwned, "Invalid module analysis app domain state"); - - if (AppDomainForModuleAnalysis != null) - { - AppDomain.Unload(AppDomainForModuleAnalysis); - AppDomainForModuleAnalysis = null; - } - - _responsibilityForModuleAnalysisAppDomainOwned = false; - } - - /// - /// The AppDomain currently being used for module analysis. It should only be created if needed, - /// but various callers need to take responsibility for unloading the domain via - /// the TakeResponsibilityForModuleAnalysisAppDomain. - /// - internal AppDomain AppDomainForModuleAnalysis { get; set; } - /// /// Authorization manager for this runspace. /// @@ -253,10 +206,7 @@ internal ProviderNames ProviderNames { get { - if (_providerNames == null) - { - _providerNames = new SingleShellProviderNames(); - } + _providerNames ??= new SingleShellProviderNames(); return _providerNames; } @@ -413,8 +363,7 @@ internal static bool IsMarkedAsUntrusted(object value) var baseValue = PSObject.Base(value); if (baseValue != null && baseValue != NullString.Value) { - object unused; - result = UntrustedObjects.TryGetValue(baseValue, out unused); + result = UntrustedObjects.TryGetValue(baseValue, out _); } return result; @@ -430,7 +379,7 @@ internal static void MarkObjectAsUntrusted(object value) if (baseValue != null && baseValue != NullString.Value) { // It's actually setting a key value pair when the key doesn't exist - UntrustedObjects.GetValue(baseValue, key => null); + UntrustedObjects.GetValue(baseValue, static key => null); try { @@ -528,7 +477,6 @@ internal LocationGlobber LocationGlobber /// The assemblies that have been loaded for this runspace. /// internal Dictionary AssemblyCache { get; private set; } - #endregion Properties #region Engine State @@ -559,9 +507,7 @@ internal object GetVariableValue(VariablePath path) /// internal object GetVariableValue(VariablePath path, object defaultValue) { - CmdletProviderContext context; - SessionStateScope scope; - return EngineSessionState.GetVariableValue(path, out context, out scope) ?? defaultValue; + return EngineSessionState.GetVariableValue(path, out _, out _) ?? defaultValue; } /// @@ -646,19 +592,15 @@ private void CheckActionPreference(VariablePath preferenceVariablePath, ActionPr /// internal bool GetBooleanPreference(VariablePath preferenceVariablePath, bool defaultPref, out bool defaultUsed) { - CmdletProviderContext context = null; - SessionStateScope scope = null; - object val = EngineSessionState.GetVariableValue(preferenceVariablePath, out context, out scope); - if (val == null) + object val = EngineSessionState.GetVariableValue(preferenceVariablePath, out _, out _); + if (val is null) { defaultUsed = true; return defaultPref; } - bool converted = defaultPref; - defaultUsed = !LanguagePrimitives.TryConvertTo - (val, out converted); - return (defaultUsed) ? defaultPref : converted; + defaultUsed = !LanguagePrimitives.TryConvertTo(val, out bool converted); + return defaultUsed ? defaultPref : converted; } #endregion GetSetVariable methods @@ -691,12 +633,13 @@ internal HelpSystem HelpSystem /// /// The name of the command to lookup. /// + /// /// The command processor object. - 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) { @@ -823,11 +766,6 @@ internal Pipe RedirectErrorPipe(Pipe newPipe) return oldPipe; } - internal void RestoreErrorPipe(Pipe pipe) - { - ShellFunctionErrorOutputPipe = pipe; - } - /// /// Reset all of the redirection book keeping variables. This routine should be called when starting to /// execute a script. @@ -880,15 +818,13 @@ internal void ResetRedirection() internal void AppendDollarError(object obj) { ErrorRecord objAsErrorRecord = obj as ErrorRecord; - if (objAsErrorRecord == null && obj is not Exception) + if (objAsErrorRecord is null && obj is not Exception) { Diagnostics.Assert(false, "Object to append was neither an ErrorRecord nor an Exception in ExecutionContext.AppendDollarError"); return; } - object old = this.DollarErrorVariable; - ArrayList arraylist = old as ArrayList; - if (arraylist == null) + if (DollarErrorVariable is not ArrayList arraylist) { Diagnostics.Assert(false, "$error should be a global constant ArrayList"); return; @@ -1209,22 +1145,12 @@ internal void RunspaceClosingNotification() { EngineSessionState.RunspaceClosingNotification(); - if (_debugger != null) - { - _debugger.Dispose(); - } - - if (Events != null) - { - Events.Dispose(); - } + _debugger?.Dispose(); + Events?.Dispose(); Events = null; - if (this.transactionManager != null) - { - this.transactionManager.Dispose(); - } + this.transactionManager?.Dispose(); this.transactionManager = null; } @@ -1316,55 +1242,151 @@ internal PSTransactionManager TransactionManager internal PSTransactionManager transactionManager; - internal Assembly AddAssembly(string name, string filename, out Exception error) - { - Assembly loadedAssembly = LoadAssembly(name, filename, out error); - - if (loadedAssembly == null) - return null; - - if (AssemblyCache.ContainsKey(loadedAssembly.FullName)) + /// + /// This method is used for assembly loading requests stemmed from 'InitialSessionState' binding and module loading. + /// + /// Source of the assembly loading request, should be a module name when specified. + /// Name of the assembly to be loaded. + /// Path of the assembly to be loaded. + /// Exception that is caught when the loading fails. + internal Assembly AddAssembly(string source, string assemblyName, string filePath, out Exception error) + { + // Search the cache by the path, and return the assembly if we find it. + // It's common to have two loading requests for the same assembly when loading a module -- the first time for + // resolving a binary module path, and the second time for actually processing that module. + // + // That's not a problem when all the module assemblies are loaded into the default ALC. But in a scenario where + // a module tries to hide its nested/root binary modules in a custom ALC, that will become a problem. This is + // because: + // in that scenario, the module will usually setup a handler to load the specific assemblies to the custom ALC, + // and that will be how the first loading request gets served. However, after the module path is resolved with + // the first loading, the path will be used for the second loading upon real module processing. Since we prefer + // loading-by-path over loading-by-name in the 'LoadAssembly' call, we will end up loading the same assembly in + // the default ALC (because we use 'Assembly.LoadFrom' which always loads an assembly to the default ALC) if we + // do not search in the cache first. That will break the scenario, because the module means to isolate all its + // dependencies from the default ALC, and it failed to do so. + // + // Therefore, we need to search the cache first. The reason we use path as the key is to make sure the request + // is for exactly the same assembly. The same assembly file should not be loaded into different ALC's by module + // loading within the same PowerShell session (Runspace). + // + // An example module targeting the abovementioned scenario will likely have the following file structure: + // IsolatedModule + // │ IsolatedModule.psd1 (has 'NestedModules = @('Test.Isolated.Init.dll', 'Test.Isolated.Nested.dll')') + // │ Test.Isolated.Init.dll (contains the custom ALC and code to setup 'Resolving' handler) + // └───Dependencies (folder under module base) + // Newtonsoft.Json.dll (version 10.0.0.0 dependency) + // Test.Isolated.Nested.dll (nested binary module referencing the particular dependency) + // + // In this example, the following events will happen in sequence: + // 1. PowerShell is able to find 'Test.Isolated.Init.dll' under module base folder, so it will be loaded into + // the default ALC as expected and setup the 'Resolving' handler via the 'OnImport' call. + // 2. PowerShell cannot find 'Test.Isolated.Nested.dll' under the module base folder, so it will call the method + // 'FixFileName(.., bool canLoadAssembly)' to resolve the path of this binary module. + // This particular overload will attempt to load the assembly by name, which will be served by the 'Resolving' + // handler that was setup in the step 1. So, the assembly will be loaded into the custom ALC and insert to the + // assembly cache. + // 3. Path of the nested module 'Test.Isolated.Init.dll' now has been resolved by the step 2 (assembly.Location). + // Now it's time to actually load this binary module for processing in the method 'LoadBinaryModule', which + // will make a call to this method with the resolved assembly file path. + // At this poin, we will have to query the cache first, instead of calling 'LoadAssembly' directly, to make sure + // that the assembly instance loaded in the custom ALC in step 2 gets returned back. Otherwise, the same assembly + // file will be loaded in the default ALC because 'Assembly.LoadFrom' is used in 'LoadAssembly' and that API will + // always load an assembly file to the default ALC, and that will break this scenario. + if (TryGetFromAssemblyCache(source, filePath, out Assembly loadedAssembly)) { - // we should ignore this assembly. + error = null; return loadedAssembly; } - // We will cache the assembly by both full name and - // file name - AssemblyCache.Add(loadedAssembly.FullName, loadedAssembly); - if (AssemblyCache.ContainsKey(loadedAssembly.GetName().Name)) + // Attempt to load the requested assembly, first by path then by name. + loadedAssembly = LoadAssembly(assemblyName, filePath, out error); + if (loadedAssembly is not null) { - // we should ignore this assembly. - return loadedAssembly; + AddToAssemblyCache(source, loadedAssembly); } - AssemblyCache.Add(loadedAssembly.GetName().Name, loadedAssembly); return loadedAssembly; } - internal void RemoveAssembly(string name) + /// + /// Add a loaded assembly to the 'AssemblyCache'. + /// The is used as a prefix for the key to make it easy to remove all associated + /// assemblies from the cache when a module gets unloaded. + /// + /// The source where the assembly comes from, should be a module name when specified. + /// The assembly we try to cache. + internal void AddToAssemblyCache(string source, Assembly assembly) { - Assembly loadedAssembly; - if (AssemblyCache.TryGetValue(name, out loadedAssembly) && loadedAssembly != null) + // Try caching the assembly by its location if possible. + // When it's a dynamic assembly, we use it's full name. This could happen with 'Import-Module -Assembly'. + string key = string.IsNullOrEmpty(assembly.Location) ? assembly.FullName : assembly.Location; + + // When the assembly is from a module loading, we prefix the key with the source, + // so we can remove it from the cache when the module gets unloaded. + if (!string.IsNullOrEmpty(source)) { - AssemblyCache.Remove(name); + // Both 'source' and 'key' are of the string type, so no need to specify 'InvariantCulture'. + key = $"{source}@{key}"; + } - AssemblyCache.Remove(loadedAssembly.GetName().Name); + AssemblyCache.TryAdd(key, assembly); + } + + /// + /// Remove all cache entries that are associated with the specified source. + /// + internal void RemoveFromAssemblyCache(string source) + { + if (string.IsNullOrEmpty(source)) + { + return; } + + var keysToRemove = new List(); + string prefix = $"{source}@"; + + foreach (string key in AssemblyCache.Keys) + { + if (key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + keysToRemove.Add(key); + } + } + + foreach (string key in keysToRemove) + { + AssemblyCache.Remove(key); + } + } + + /// + /// Try to get an assembly from the cache. + /// + private bool TryGetFromAssemblyCache(string source, string filePath, out Assembly assembly) + { + if (string.IsNullOrEmpty(filePath)) + { + assembly = null; + return false; + } + + // Both 'source' and 'filePath' are of the string type, so no need to specify 'InvariantCulture'. + string key = string.IsNullOrEmpty(source) ? filePath : $"{source}@{filePath}"; + return AssemblyCache.TryGetValue(key, out assembly); } - [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadWithPartialName")] - [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom")] - internal static Assembly LoadAssembly(string name, string filename, out Exception error) + private static Assembly LoadAssembly(string name, string filePath, out Exception error) { // First we try to load the assembly based on the filename Assembly loadedAssembly = null; error = null; - if (!string.IsNullOrEmpty(filename)) + if (!string.IsNullOrEmpty(filePath)) { try { - loadedAssembly = Assembly.LoadFrom(filename); + // 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; } catch (FileNotFoundException fileNotFound) @@ -1451,9 +1473,17 @@ internal void ReportEngineStartupError(string resourceString, params object[] ar else { PSHost host = EngineHostInterface; - if (host == null) return; + if (host == null) + { + return; + } + PSHostUserInterface ui = host.UI; - if (ui == null) return; + if (ui == null) + { + return; + } + ui.WriteErrorLine( StringUtil.Format(resourceString, arguments)); } @@ -1481,9 +1511,17 @@ internal void ReportEngineStartupError(string error) else { PSHost host = EngineHostInterface; - if (host == null) return; + if (host == null) + { + return; + } + PSHostUserInterface ui = host.UI; - if (ui == null) return; + if (ui == null) + { + return; + } + ui.WriteErrorLine(error); } } @@ -1516,9 +1554,17 @@ internal void ReportEngineStartupError(Exception e) else { PSHost host = EngineHostInterface; - if (host == null) return; + if (host == null) + { + return; + } + PSHostUserInterface ui = host.UI; - if (ui == null) return; + if (ui == null) + { + return; + } + ui.WriteErrorLine(e.Message); } } @@ -1536,17 +1582,24 @@ internal void ReportEngineStartupError(ErrorRecord errorRecord) try { Cmdlet currentRunningModuleCommand; - string unused; - if (IsModuleCommandCurrentlyRunning(out currentRunningModuleCommand, out unused)) + if (IsModuleCommandCurrentlyRunning(out currentRunningModuleCommand, out _)) { currentRunningModuleCommand.WriteError(errorRecord); } else { PSHost host = EngineHostInterface; - if (host == null) return; + if (host == null) + { + return; + } + PSHostUserInterface ui = host.UI; - if (ui == null) return; + if (ui == null) + { + return; + } + ui.WriteErrorLine(errorRecord.ToString()); } } @@ -1602,23 +1655,6 @@ internal ExecutionContext(AutomationEngine engine, PSHost hostInterface, Initial private void InitializeCommon(AutomationEngine engine, PSHost hostInterface) { Engine = engine; -#if !CORECLR// System.AppDomain is not in CoreCLR - // Set the assembly resolve handler if it isn't already set... - if (!_assemblyEventHandlerSet) - { - // we only want to set the event handler once for the entire app domain... - lock (lockObject) - { - // Need to check again inside the lock due to possibility of a race condition... - if (!_assemblyEventHandlerSet) - { - AppDomain currentAppDomain = AppDomain.CurrentDomain; - currentAppDomain.AssemblyResolve += new ResolveEventHandler(PowerShellAssemblyResolveHandler); - _assemblyEventHandlerSet = true; - } - } - } -#endif Events = new PSLocalEventManager(this); transactionManager = new PSTransactionManager(); _debugger = new ScriptDebugger(this); @@ -1626,52 +1662,20 @@ private void InitializeCommon(AutomationEngine engine, PSHost hostInterface) EngineHostInterface = hostInterface as InternalHost ?? new InternalHost(hostInterface, this); // Hook up the assembly cache - AssemblyCache = new Dictionary(); + AssemblyCache = new Dictionary(StringComparer.OrdinalIgnoreCase); // Initialize the fixed toplevel session state and the current session state TopLevelSessionState = EngineSessionState = new SessionStateInternal(this); - if (AuthorizationManager == null) - { - // if authorizationmanager==null, this means the configuration - // explicitly asked for dummy authorization manager. - AuthorizationManager = new AuthorizationManager(null); - } + // if authorizationmanager==null, this means the configuration + // explicitly asked for dummy authorization manager. + AuthorizationManager ??= new AuthorizationManager(null); // Set up the module intrinsics Modules = new ModuleIntrinsics(this); } private static readonly object lockObject = new object(); - -#if !CORECLR // System.AppDomain is not in CoreCLR - private static bool _assemblyEventHandlerSet = false; - - /// - /// AssemblyResolve event handler that will look in the assembly cache to see - /// if the named assembly has been loaded. This is necessary so that assemblies loaded - /// with LoadFrom, which are in a different loaded context than Load, can still be used to - /// resolve types. - /// - /// The event sender. - /// The event args. - /// The resolve assembly or null if not found. - private static Assembly PowerShellAssemblyResolveHandler(object sender, ResolveEventArgs args) - { - ExecutionContext ecFromTLS = Runspaces.LocalPipeline.GetExecutionContextFromTLS(); - if (ecFromTLS != null) - { - if (ecFromTLS.AssemblyCache != null) - { - Assembly assembly; - ecFromTLS.AssemblyCache.TryGetValue(args.Name, out assembly); - return assembly; - } - } - - return null; - } -#endif } /// diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/EnableDisableExperimentalFeatureCommand.cs b/src/System.Management.Automation/engine/ExperimentalFeature/EnableDisableExperimentalFeatureCommand.cs index 7a76a18ddb9..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 /// The command AST. /// The fake bound parameters. /// List of Completion Results. - public IEnumerable CompleteArgument(string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters) + public IEnumerable 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 expirmentalFeatures = new(StringComparer.OrdinalIgnoreCase); - HashSet names = new HashSet(); - var results = ps.Invoke(); - foreach (var result in results) + foreach (ExperimentalFeature feature in GetExperimentalFeatures()) { - names.Add(result.Name); + expirmentalFeatures.Add(feature.Name); } - return names.OrderBy(name => name).Select(name => new CompletionResult(name, name, CompletionResultType.Text, name)); + return CompletionHelpers.GetMatchingResults(wordToComplete, expirmentalFeatures); + } + + private static Collection GetExperimentalFeatures() + { + using var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); + ps.AddCommand("Get-ExperimentalFeature"); + return ps.Invoke(); } } } diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index 5eeaf814e10..dd26e609641 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -21,7 +21,10 @@ public class ExperimentalFeature #region Const Members internal const string EngineSource = "PSEngine"; - internal const string PSAnsiProgressFeatureName = "PSAnsiProgress"; + internal const string PSFeedbackProvider = "PSFeedbackProvider"; + internal const string PSNativeWindowsTildeExpansion = nameof(PSNativeWindowsTildeExpansion); + internal const string PSRedirectToVariable = "PSRedirectToVariable"; + internal const string PSSerializeJSONLongEnumAsNumber = nameof(PSSerializeJSONLongEnumAsNumber); #endregion @@ -105,42 +108,30 @@ static ExperimentalFeature() description: "Replace the old FileSystemProvider with cleaner design and faster code"), */ new ExperimentalFeature( - name: "PSImplicitRemotingBatching", - description: "Batch implicit remoting proxy commands to improve performance"), - new ExperimentalFeature( - name: "PSCommandNotFoundSuggestion", - description: "Recommend potential commands based on fuzzy search on a CommandNotFoundException"), -#if UNIX - new ExperimentalFeature( - name: "PSUnixFileStat", - description: "Provide unix permission information for files and directories"), -#endif - new ExperimentalFeature( - name: "PSCultureInvariantReplaceOperator", - description: "Use culture invariant to-string convertor for lval in replace operator"), - new ExperimentalFeature( - name: "PSNativePSPathResolution", - description: "Convert PSPath to filesystem path, if possible, for native commands"), + name: "PSSubsystemPluginModel", + description: "A plugin model for registering and un-registering PowerShell subsystems"), new ExperimentalFeature( - name: "PSNotApplyErrorActionToStderr", - description: "Don't have $ErrorActionPreference affect stderr output"), + name: "PSLoadAssemblyFromNativeCode", + description: "Expose an API to allow assembly loading from native code"), new ExperimentalFeature( - name: "PS7DscSupport", - description: "Support the cross-platform class-based DSC"), + name: PSFeedbackProvider, + description: "Replace the hard-coded suggestion framework with the extensible feedback provider"), new ExperimentalFeature( - name: "PSSubsystemPluginModel", - description: "A plugin model for registering and un-registering PowerShell subsystems"), + name: PSNativeWindowsTildeExpansion, + description: "On windows, expand unquoted tilde (`~`) with the user's current home folder."), new ExperimentalFeature( - name: "PSAnsiRendering", - description: "Enable $PSStyle variable to control ANSI rendering of strings"), + name: PSRedirectToVariable, + description: "Add support for redirecting to the variable drive"), new ExperimentalFeature( - name: PSAnsiProgressFeatureName, - description: "Enable lightweight progress bar that leverages ANSI codes for rendering"), + name: PSSerializeJSONLongEnumAsNumber, + description: "Serialize enums based on long or ulong as an numeric value rather than the string representation when using ConvertTo-Json." + ) }; + EngineExperimentalFeatures = new ReadOnlyCollection(engineFeatures); // Initialize the readonly dictionary 'EngineExperimentalFeatureMap'. - var engineExpFeatureMap = engineFeatures.ToDictionary(f => f.Name, StringComparer.OrdinalIgnoreCase); + var engineExpFeatureMap = engineFeatures.ToDictionary(static f => f.Name, StringComparer.OrdinalIgnoreCase); EngineExperimentalFeatureMap = new ReadOnlyDictionary(engineExpFeatureMap); // Initialize the readonly hashset 'EnabledExperimentalFeatureNames'. @@ -160,6 +151,20 @@ static ExperimentalFeature() EnabledExperimentalFeatureNames = ProcessEnabledFeatures(enabledFeatures); } + /// + /// We need to notify which features were not enabled. + /// + private static void SendTelemetryForDeactivatedFeatures(ReadOnlyBag enabledFeatures) + { + foreach (var feature in EngineExperimentalFeatures) + { + if (!enabledFeatures.Contains(feature.Name)) + { + ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ExperimentalEngineFeatureDeactivation, feature.Name); + } + } + } + /// /// Process the array of enabled feature names retrieved from configuration. /// Ignore invalid feature names and unavailable engine feature names, and @@ -167,7 +172,10 @@ static ExperimentalFeature() /// private static ReadOnlyBag ProcessEnabledFeatures(string[] enabledFeatures) { - if (enabledFeatures.Length == 0) { return ReadOnlyBag.Empty; } + if (enabledFeatures.Length == 0) + { + return ReadOnlyBag.Empty; + } var list = new List(enabledFeatures.Length); foreach (string name in enabledFeatures) @@ -198,7 +206,9 @@ private static ReadOnlyBag ProcessEnabledFeatures(string[] enabledFeatur } } - return new ReadOnlyBag(new HashSet(list, StringComparer.OrdinalIgnoreCase)); + ReadOnlyBag features = new(new HashSet(list, StringComparer.OrdinalIgnoreCase)); + SendTelemetryForDeactivatedFeatures(features); + return features; } /// diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/GetExperimentalFeatureCommand.cs b/src/System.Management.Automation/engine/ExperimentalFeature/GetExperimentalFeatureCommand.cs index d87669000de..38525cb54ef 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/GetExperimentalFeatureCommand.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/GetExperimentalFeatureCommand.cs @@ -88,17 +88,26 @@ private IEnumerable GetValidModuleFiles(HashSet moduleNamesToFin foreach (string path in ModuleIntrinsics.GetModulePath(includeSystemModulePath: false, Context)) { string uniquePath = path.TrimEnd(Utils.Separators.Directory); - if (!modulePaths.Add(uniquePath)) { continue; } + if (!modulePaths.Add(uniquePath)) + { + continue; + } foreach (string moduleFile in ModuleUtils.GetDefaultAvailableModuleFiles(uniquePath)) { // We only care about module manifest files because that's where experimental features are declared. - if (!moduleFile.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase)) { continue; } + if (!moduleFile.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase)) + { + continue; + } if (moduleNamesToFind != null) { string currentModuleName = ModuleIntrinsics.GetModuleName(moduleFile); - if (!moduleNamesToFind.Contains(currentModuleName)) { continue; } + if (!moduleNamesToFind.Contains(currentModuleName)) + { + continue; + } } yield return moduleFile; diff --git a/src/System.Management.Automation/engine/ExtendedTypeSystemException.cs b/src/System.Management.Automation/engine/ExtendedTypeSystemException.cs index ac99127ab29..06271728e00 100644 --- a/src/System.Management.Automation/engine/ExtendedTypeSystemException.cs +++ b/src/System.Management.Automation/engine/ExtendedTypeSystemException.cs @@ -10,7 +10,6 @@ namespace System.Management.Automation /// /// Defines the exception thrown for all Extended type system related errors. /// - [Serializable] public class ExtendedTypeSystemException : RuntimeException { #region ctor @@ -36,7 +35,7 @@ public ExtendedTypeSystemException(string message) /// Initializes a new instance of ExtendedTypeSystemException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. + /// The exception's inner exception. public ExtendedTypeSystemException(string message, Exception innerException) : base(message, innerException) { @@ -67,8 +66,9 @@ internal ExtendedTypeSystemException( /// /// Serialization information. /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ExtendedTypeSystemException(SerializationInfo info, StreamingContext context) - : base(info, context) + : base(info, context) { } #endregion Serialization @@ -80,7 +80,6 @@ protected ExtendedTypeSystemException(SerializationInfo info, StreamingContext c /// /// Defines the exception thrown for Method related errors. /// - [Serializable] public class MethodException : ExtendedTypeSystemException { internal const string MethodArgumentCountExceptionMsg = "MethodArgumentCountException"; @@ -112,7 +111,7 @@ public MethodException(string message) /// Initializes a new instance of MethodException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. + /// The exception's inner exception. public MethodException(string message, Exception innerException) : base(message, innerException) { @@ -140,9 +139,10 @@ internal MethodException( /// /// Serialization information. /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected MethodException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion Serialization @@ -153,7 +153,6 @@ protected MethodException(SerializationInfo info, StreamingContext context) /// /// Defines the exception thrown for Method invocation exceptions. /// - [Serializable] public class MethodInvocationException : MethodException { internal const string MethodInvocationExceptionMsg = "MethodInvocationException"; @@ -183,7 +182,7 @@ public MethodInvocationException(string message) /// Initializes a new instance of MethodInvocationException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. + /// The exception's inner exception. public MethodInvocationException(string message, Exception innerException) : base(message, innerException) { @@ -211,9 +210,10 @@ internal MethodInvocationException( /// /// Serialization information. /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected MethodInvocationException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion Serialization @@ -224,7 +224,6 @@ protected MethodInvocationException(SerializationInfo info, StreamingContext con /// /// Defines the exception thrown for errors getting the value of properties. /// - [Serializable] public class GetValueException : ExtendedTypeSystemException { internal const string GetWithoutGetterExceptionMsg = "GetWithoutGetterException"; @@ -252,7 +251,7 @@ public GetValueException(string message) /// Initializes a new instance of GetValueException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. + /// The exception's inner exception. public GetValueException(string message, Exception innerException) : base(message, innerException) { @@ -280,9 +279,10 @@ internal GetValueException( /// /// Serialization information. /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected GetValueException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion Serialization @@ -293,7 +293,6 @@ protected GetValueException(SerializationInfo info, StreamingContext context) /// /// Defines the exception thrown for errors getting the value of properties. /// - [Serializable] public class PropertyNotFoundException : ExtendedTypeSystemException { #region ctor @@ -319,7 +318,7 @@ public PropertyNotFoundException(string message) /// Initializes a new instance of GetValueException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. + /// The exception's inner exception. public PropertyNotFoundException(string message, Exception innerException) : base(message, innerException) { @@ -347,12 +346,12 @@ internal PropertyNotFoundException( /// /// Serialization information. /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PropertyNotFoundException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion Serialization - #endregion ctor } @@ -360,7 +359,6 @@ protected PropertyNotFoundException(SerializationInfo info, StreamingContext con /// /// Defines the exception thrown for exceptions thrown by property getters. /// - [Serializable] public class GetValueInvocationException : GetValueException { internal const string ExceptionWhenGettingMsg = "ExceptionWhenGetting"; @@ -388,7 +386,7 @@ public GetValueInvocationException(string message) /// Initializes a new instance of GetValueInvocationException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. + /// The exception's inner exception. public GetValueInvocationException(string message, Exception innerException) : base(message, innerException) { @@ -416,9 +414,10 @@ internal GetValueInvocationException( /// /// Serialization information. /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected GetValueInvocationException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion Serialization @@ -429,7 +428,6 @@ protected GetValueInvocationException(SerializationInfo info, StreamingContext c /// /// Defines the exception thrown for errors setting the value of properties. /// - [Serializable] public class SetValueException : ExtendedTypeSystemException { #region ctor @@ -455,7 +453,7 @@ public SetValueException(string message) /// Initializes a new instance of SetValueException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. + /// The exception's inner exception. public SetValueException(string message, Exception innerException) : base(message, innerException) { @@ -483,9 +481,10 @@ internal SetValueException( /// /// Serialization information. /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected SetValueException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion Serialization @@ -496,7 +495,6 @@ protected SetValueException(SerializationInfo info, StreamingContext context) /// /// Defines the exception thrown for exceptions thrown by property setters. /// - [Serializable] public class SetValueInvocationException : SetValueException { #region ctor @@ -522,7 +520,7 @@ public SetValueInvocationException(string message) /// Initializes a new instance of SetValueInvocationException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. + /// The exception's inner exception. public SetValueInvocationException(string message, Exception innerException) : base(message, innerException) { @@ -550,9 +548,10 @@ internal SetValueInvocationException( /// /// Serialization information. /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected SetValueInvocationException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion Serialization @@ -563,40 +562,19 @@ protected SetValueInvocationException(SerializationInfo info, StreamingContext c /// /// Defines the exception thrown for type conversion errors. /// - [Serializable] public class PSInvalidCastException : InvalidCastException, IContainsErrorRecord { - #region Serialization - - /// - /// Populates a with the - /// data needed to serialize the PSInvalidCastException object. - /// - /// The to populate with data. - /// The destination for this serialization. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - info.AddValue("ErrorId", _errorId); - } /// /// Initializes a new instance of PSInvalidCastException with serialization parameters. /// /// Serialization information. /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSInvalidCastException(SerializationInfo info, StreamingContext context) - : base(info, context) { - _errorId = info.GetString("ErrorId"); + throw new NotSupportedException(); } - #endregion Serialization - /// /// Initializes a new instance of PSInvalidCastException with the message set /// to typeof(PSInvalidCastException).FullName. @@ -617,7 +595,7 @@ public PSInvalidCastException(string message) /// Initializes a new instance of PSInvalidCastException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. + /// The exception's inner exception. public PSInvalidCastException(string message, Exception innerException) : base(message, innerException) { @@ -647,14 +625,11 @@ public ErrorRecord ErrorRecord { get { - if (_errorRecord == null) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - _errorId, - ErrorCategory.InvalidArgument, - null); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + _errorId, + ErrorCategory.InvalidArgument, + null); return _errorRecord; } diff --git a/src/System.Management.Automation/engine/ExternalScriptInfo.cs b/src/System.Management.Automation/engine/ExternalScriptInfo.cs index bbd49c76d36..e8c8c2b1d54 100644 --- a/src/System.Management.Automation/engine/ExternalScriptInfo.cs +++ b/src/System.Management.Automation/engine/ExternalScriptInfo.cs @@ -14,7 +14,7 @@ namespace System.Management.Automation { /// - /// Provides information for MSH scripts that are directly executable by MSH + /// Provides information for scripts that are directly executable by PowerShell /// but are not built into the runspace configuration. /// public class ExternalScriptInfo : CommandInfo, IScriptCommandInfo @@ -103,14 +103,21 @@ private void CommonInitialization() // Get the lock down policy with no handle. This only impacts command discovery, // as the real language mode assignment will be done when we read the script // contents. - SystemEnforcementMode scriptSpecificPolicy = SystemPolicy.GetLockdownPolicy(_path, null); - if (scriptSpecificPolicy != SystemEnforcementMode.Enforce) + switch (SystemPolicy.GetLockdownPolicy(_path, null)) { - this.DefiningLanguageMode = PSLanguageMode.FullLanguage; - } - else - { - this.DefiningLanguageMode = PSLanguageMode.ConstrainedLanguage; + case SystemEnforcementMode.None: + DefiningLanguageMode = PSLanguageMode.FullLanguage; + break; + + case SystemEnforcementMode.Audit: + // For policy audit mode, language mode is set to CL but audit messages are emitted to log + // instead of applying restrictions. + DefiningLanguageMode = PSLanguageMode.ConstrainedLanguage; + break; + + case SystemEnforcementMode.Enforce: + DefiningLanguageMode = PSLanguageMode.ConstrainedLanguage; + break; } } } @@ -188,7 +195,10 @@ public override SessionStateEntryVisibility Visibility { get { - if (Context == null) return SessionStateEntryVisibility.Public; + if (Context == null) + { + return SessionStateEntryVisibility.Public; + } return Context.EngineSessionState.CheckScriptVisibility(_path); } @@ -384,7 +394,7 @@ internal override bool ImplementsDynamicParameters // If we got here, there was some sort of parsing exception. We'll just // ignore it and assume the script does not implement dynamic parameters. - // Futhermore, we'll clear out the fields so that the next attempt to + // Furthermore, we'll clear out the fields so that the next attempt to // access ScriptBlock will result in an exception that doesn't get ignored. _scriptBlock = null; _scriptContents = null; @@ -455,15 +465,6 @@ internal uint PSVersionLineNumber get { return 0; } } - internal IEnumerable RequiresPSSnapIns - { - get - { - var data = GetRequiresData(); - return data?.RequiresPSSnapIns; - } - } - /// /// Gets the original contents of the script. /// @@ -515,33 +516,54 @@ private void ReadScriptContents() { using (FileStream readerStream = new FileStream(_path, FileMode.Open, FileAccess.Read)) { - Encoding defaultEncoding = ClrFacade.GetDefaultEncoding(); - Microsoft.Win32.SafeHandles.SafeFileHandle safeFileHandle = readerStream.SafeFileHandle; - - using (StreamReader scriptReader = new StreamReader(readerStream, defaultEncoding)) + using (StreamReader scriptReader = new StreamReader(readerStream, Encoding.Default)) { _scriptContents = scriptReader.ReadToEnd(); _originalEncoding = scriptReader.CurrentEncoding; - // Check if this came from a trusted path. If so, set its language mode to FullLanguage. - if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.None) - { - SystemEnforcementMode scriptSpecificPolicy = SystemPolicy.GetLockdownPolicy(_path, safeFileHandle); - if (scriptSpecificPolicy != SystemEnforcementMode.Enforce) - { - this.DefiningLanguageMode = PSLanguageMode.FullLanguage; - } - else - { - this.DefiningLanguageMode = PSLanguageMode.ConstrainedLanguage; - } - } - else + // Check this file against any system wide enforcement policies. + SystemScriptFileEnforcement filePolicyEnforcement = SystemPolicy.GetFilePolicyEnforcement(_path, readerStream); + switch (filePolicyEnforcement) { - if (this.Context != null) - { - this.DefiningLanguageMode = this.Context.LanguageMode; - } + case SystemScriptFileEnforcement.None: + if (Context != null) + { + DefiningLanguageMode = Context.LanguageMode; + } + break; + + case SystemScriptFileEnforcement.Allow: + DefiningLanguageMode = PSLanguageMode.FullLanguage; + break; + + case SystemScriptFileEnforcement.AllowConstrained: + DefiningLanguageMode = PSLanguageMode.ConstrainedLanguage; + break; + + case SystemScriptFileEnforcement.AllowConstrainedAudit: + SystemPolicy.LogWDACAuditMessage( + context: Context, + title: SecuritySupportStrings.ExternalScriptWDACLogTitle, + message: string.Format(Globalization.CultureInfo.CurrentUICulture, SecuritySupportStrings.ExternalScriptWDACLogMessage, _path), + fqid: "ScriptFileNotTrustedByPolicy"); + // We set the language mode to Constrained Language, even though in policy audit mode no restrictions are applied + // and instead an audit log message is generated wherever a restriction would be applied. + DefiningLanguageMode = PSLanguageMode.ConstrainedLanguage; + break; + + case SystemScriptFileEnforcement.Block: + throw new PSSecurityException( + string.Format( + Globalization.CultureInfo.CurrentUICulture, + SecuritySupportStrings.ScriptFileBlockedBySystemPolicy, + _path)); + + default: + throw new PSSecurityException( + string.Format( + Globalization.CultureInfo.CurrentUICulture, + SecuritySupportStrings.UnknownSystemScriptFileEnforcement, + filePolicyEnforcement)); } } } @@ -589,7 +611,6 @@ internal ScriptRequiresSyntaxException(string message) /// /// Defines the name and version tuple of a PSSnapin. /// - [Serializable] public class PSSnapInSpecification { internal PSSnapInSpecification(string psSnapinName) diff --git a/src/System.Management.Automation/engine/GetCommandCommand.cs b/src/System.Management.Automation/engine/GetCommandCommand.cs index 9007bed247c..2ecf19ccaa9 100644 --- a/src/System.Management.Automation/engine/GetCommandCommand.cs +++ b/src/System.Management.Automation/engine/GetCommandCommand.cs @@ -12,6 +12,7 @@ using System.Management.Automation; using System.Management.Automation.Internal; using System.Management.Automation.Language; +using static System.Management.Automation.Verbs; using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Commands @@ -71,6 +72,7 @@ public string[] Name /// Gets or sets the verb parameter to the cmdlet. /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "CmdletSet")] + [ArgumentCompleter(typeof(VerbArgumentCompleter))] public string[] Verb { get @@ -80,10 +82,7 @@ public string[] Verb set { - if (value == null) - { - value = Array.Empty(); - } + value ??= Array.Empty(); _verbs = value; _verbPatterns = null; @@ -106,10 +105,7 @@ public string[] Noun set { - if (value == null) - { - value = Array.Empty(); - } + value ??= Array.Empty(); _nouns = value; _nounPatterns = null; @@ -132,10 +128,7 @@ public string[] Module set { - if (value == null) - { - value = Array.Empty(); - } + value ??= Array.Empty(); _modules = value; _modulePatterns = null; @@ -147,6 +140,28 @@ public string[] Module private string[] _modules = Array.Empty(); private bool _isModuleSpecified = false; + /// + /// Gets or sets the ExcludeModule parameter to the cmdlet. + /// + [Parameter()] + public string[] ExcludeModule + { + get + { + return _excludedModules; + } + + set + { + value ??= Array.Empty(); + + _excludedModules = value; + _excludedModulePatterns = null; + } + } + + private string[] _excludedModules = Array.Empty(); + /// /// Gets or sets the FullyQualifiedModule parameter to the cmdlet. /// @@ -287,10 +302,7 @@ public string[] ParameterName set { - if (value == null) - { - throw new ArgumentNullException("value"); - } + ArgumentNullException.ThrowIfNull(value); _parameterNames = value; _parameterNameWildcards = SessionStateUtilities.CreateWildcardsFromStrings( @@ -317,10 +329,7 @@ public PSTypeName[] ParameterType set { - if (value == null) - { - throw new ArgumentNullException("value"); - } + ArgumentNullException.ThrowIfNull(value); // if '...CimInstance#Win32_Process' is specified, then exclude '...CimInstance' List filteredParameterTypes = new List(value.Length); @@ -353,7 +362,14 @@ public PSTypeName[] ParameterType [Parameter(ParameterSetName = "AllCommandSet")] public SwitchParameter UseFuzzyMatching { get; set; } - private readonly List _commandScores = new List(); + /// + /// Gets or sets the minimum fuzzy matching distance. + /// + [Parameter(ParameterSetName = "AllCommandSet")] + public uint FuzzyMinimumDistance { get; set; } = 5; + + private FuzzyMatcher _fuzzyMatcher; + private List _commandScores; /// /// Gets or sets the parameter that determines if return cmdlets based on abbreviation expansion. @@ -375,7 +391,11 @@ protected override void BeginProcessing() #if LEGACYTELEMETRY _timer.Start(); #endif - base.BeginProcessing(); + if (UseFuzzyMatching) + { + _fuzzyMatcher = new FuzzyMatcher(FuzzyMinimumDistance); + _commandScores = new List(); + } if (ShowCommandInfo.IsPresent && Syntax.IsPresent) { @@ -405,10 +425,8 @@ protected override void ProcessRecord() } // Initialize the module patterns - if (_modulePatterns == null) - { - _modulePatterns = SessionStateUtilities.CreateWildcardsFromStrings(Module, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); - } + _modulePatterns ??= SessionStateUtilities.CreateWildcardsFromStrings(Module, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); + _excludedModulePatterns ??= SessionStateUtilities.CreateWildcardsFromStrings(ExcludeModule, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); switch (ParameterSetName) { @@ -487,7 +505,7 @@ protected override void EndProcessing() if ((_names == null) || (_nameContainsWildcard)) { // Use the stable sorting to sort the result list - _accumulatedResults = _accumulatedResults.OrderBy(a => a, new CommandInfoComparer()).ToList(); + _accumulatedResults = _accumulatedResults.Order(new CommandInfoComparer()).ToList(); } OutputResultsHelper(_accumulatedResults); @@ -514,17 +532,17 @@ protected override void EndProcessing() private void OutputResultsHelper(IEnumerable results) { - CommandOrigin origin = this.MyInvocation.CommandOrigin; + CommandOrigin origin = MyInvocation.CommandOrigin; if (UseFuzzyMatching) { - results = _commandScores.OrderBy(x => x.Score).Select(x => x.Command).ToList(); + _commandScores = _commandScores.OrderBy(static x => x.Score).ToList(); + results = _commandScores.Select(static x => x.Command); } int count = 0; foreach (CommandInfo result in results) { - count += 1; // Only write the command if it is visible to the requestor if (SessionState.IsVisible(origin, result)) { @@ -549,11 +567,21 @@ private void OutputResultsHelper(IEnumerable results) } else { - // Write output as normal command info object. - WriteObject(result); + if (UseFuzzyMatching) + { + PSObject obj = new PSObject(result); + obj.Properties.Add(new PSNoteProperty("Score", _commandScores[count].Score)); + WriteObject(obj); + } + else + { + WriteObject(result); + } } } } + + count += 1; } #if LEGACYTELEMETRY @@ -561,7 +589,7 @@ private void OutputResultsHelper(IEnumerable results) // No telemetry here - capturing the name of a command which we are not familiar with // may be confidential customer information - // We want telementry on commands people look for but don't exist - this should give us an idea + // We want telemetry on commands people look for but don't exist - this should give us an idea // what sort of commands people expect but either don't exist, or maybe should be installed by default. // The StartsWith is to avoid logging telemetry when suggestion mode checks the // current directory for scripts/exes in the current directory and '.' is not in the path. @@ -650,7 +678,7 @@ private PSObject GetSyntaxObject(CommandInfo command) /// /// The comparer to sort CommandInfo objects in the result list. /// - private class CommandInfoComparer : IComparer + private sealed class CommandInfoComparer : IComparer { /// /// Compare two CommandInfo objects first by their command types, and if they @@ -691,18 +719,19 @@ private bool IsNounVerbMatch(CommandInfo command) do // false loop { - if (_verbPatterns == null) - { - _verbPatterns = SessionStateUtilities.CreateWildcardsFromStrings(Verb, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); - } + _verbPatterns ??= SessionStateUtilities.CreateWildcardsFromStrings(Verb, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); - if (_nounPatterns == null) - { - _nounPatterns = SessionStateUtilities.CreateWildcardsFromStrings(Noun, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); - } + _nounPatterns ??= SessionStateUtilities.CreateWildcardsFromStrings(Noun, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); 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( @@ -788,11 +817,6 @@ private void AccumulateMatchingCommands(IEnumerable commandNames) options |= SearchResolutionOptions.UseAbbreviationExpansion; } - if (UseFuzzyMatching) - { - options |= SearchResolutionOptions.FuzzyMatch; - } - if ((this.CommandType & CommandTypes.Alias) != 0) { options |= SearchResolutionOptions.ResolveAliasPatterns; @@ -865,24 +889,25 @@ private void AccumulateMatchingCommands(IEnumerable commandNames) IEnumerable commands; if (UseFuzzyMatching) { - foreach (var commandScore in System.Management.Automation.Internal.ModuleUtils.GetFuzzyMatchingCommands( + foreach (var commandScore in ModuleUtils.GetFuzzyMatchingCommands( plainCommandName, - this.Context, - this.MyInvocation.CommandOrigin, + Context, + MyInvocation.CommandOrigin, + _fuzzyMatcher, rediscoverImportedModules: true, moduleVersionRequired: _isFullyQualifiedModuleSpecified)) { _commandScores.Add(commandScore); } - commands = _commandScores.Select(x => x.Command).ToList(); + commands = _commandScores.Select(static x => x.Command); } else { - commands = System.Management.Automation.Internal.ModuleUtils.GetMatchingCommands( + commands = ModuleUtils.GetMatchingCommands( plainCommandName, - this.Context, - this.MyInvocation.CommandOrigin, + Context, + MyInvocation.CommandOrigin, rediscoverImportedModules: true, moduleVersionRequired: _isFullyQualifiedModuleSpecified, useAbbreviationExpansion: UseAbbreviationExpansion); @@ -943,12 +968,12 @@ private void AccumulateMatchingCommands(IEnumerable commandNames) private bool FindCommandForName(SearchResolutionOptions options, string commandName, bool isPattern, bool emitErrors, ref int currentCount, out bool isDuplicate) { - CommandSearcher searcher = - new CommandSearcher( - commandName, - options, - this.CommandType, - this.Context); + var searcher = new CommandSearcher( + commandName, + options, + CommandType, + Context, + _fuzzyMatcher); bool resultFound = false; isDuplicate = false; @@ -1036,8 +1061,10 @@ private bool FindCommandForName(SearchResolutionOptions options, string commandN if (UseFuzzyMatching) { - int score = FuzzyMatcher.GetDamerauLevenshteinDistance(current.Name, commandName); - _commandScores.Add(new CommandScore(current, score)); + if (_fuzzyMatcher.IsFuzzyMatch(current.Name, commandName, out int score)) + { + _commandScores.Add(new CommandScore(current, score)); + } } _accumulatedResults.Add(current); @@ -1159,10 +1186,7 @@ private bool IsParameterMatch(CommandInfo commandInfo) return true; } - if (_matchedParameterNames == null) - { - _matchedParameterNames = new HashSet(StringComparer.OrdinalIgnoreCase); - } + _matchedParameterNames ??= new HashSet(StringComparer.OrdinalIgnoreCase); IEnumerable commandParameters = null; try @@ -1277,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; @@ -1536,6 +1567,7 @@ private bool IsCommandInResult(CommandInfo command) private Collection _verbPatterns; private Collection _nounPatterns; private Collection _modulePatterns; + private Collection _excludedModulePatterns; #if LEGACYTELEMETRY private Stopwatch _timer = new Stopwatch(); @@ -1662,40 +1694,50 @@ private static PSObject GetParameterType(Type parameterType) } /// + /// Provides argument completion for Noun parameter. /// public class NounArgumentCompleter : IArgumentCompleter { + /// + /// Returns completion results for Noun parameter. + /// + /// The command name. + /// The parameter name. + /// The word to complete. + /// The command AST. + /// The fake bound parameters. + /// List of completion results. + public IEnumerable CompleteArgument( + string commandName, + string parameterName, + string wordToComplete, + CommandAst commandAst, + IDictionary fakeBoundParameters) => CompletionHelpers.GetMatchingResults( + wordToComplete, + possibleCompletionValues: GetCommandNouns(fakeBoundParameters)); + /// + /// Get sorted set of command nouns using Get-Command. /// - public IEnumerable CompleteArgument(string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters) + /// The fake bound parameters. + /// Sorted set of command nouns. + private static SortedSet 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 commands = CompletionCompleters.GetCommandInfo(fakeBoundParameters, "Module", "Verb"); + SortedSet nouns = new(StringComparer.OrdinalIgnoreCase); - HashSet nouns = new HashSet(); - var results = ps.Invoke(); - 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.OrderBy(noun => noun).Select(noun => new CompletionResult(noun, noun, CompletionResultType.Text, noun)); + return nouns; } } } diff --git a/src/System.Management.Automation/engine/ICommandRuntime.cs b/src/System.Management.Automation/engine/ICommandRuntime.cs index 6f0b3ce9d57..49b1104e047 100644 --- a/src/System.Management.Automation/engine/ICommandRuntime.cs +++ b/src/System.Management.Automation/engine/ICommandRuntime.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System.Management.Automation.Host; namespace System.Management.Automation @@ -13,8 +15,8 @@ namespace System.Management.Automation /// When a cmdlet is instantiated and run directly, all calls to the stream APIs will be proxied /// through to an instance of this class. For example, when a cmdlet calls WriteObject, the /// WriteObject implementation on the instance of the class implementing this interface will be - /// called. The Monad implementation provides a default implementation of this class for use with - /// standalone cmdlets as well as the implementation provided for running in the monad engine itself. + /// called. PowerShell implementation provides a default implementation of this class for use with + /// standalone cmdlets as well as the implementation provided for running in the engine itself. /// /// If you do want to run Cmdlet instances standalone and capture their output with more /// fidelity than is provided for with the default implementation, then you should create your own @@ -26,7 +28,7 @@ public interface ICommandRuntime /// /// Returns an instance of the PSHost implementation for this environment. /// - PSHost Host { get; } + PSHost? Host { get; } #region Write /// /// Display debug information. @@ -65,7 +67,7 @@ public interface ICommandRuntime /// When the cmdlet wants to write a single object out, it will call this /// API. It is up to the implementation to decide what to do with these objects. /// - void WriteObject(object sendToPipeline); + void WriteObject(object? sendToPipeline); /// /// Called to write one or more objects to the output pipe. @@ -83,7 +85,7 @@ public interface ICommandRuntime /// When the cmdlet wants to write multiple objects out, it will call this /// API. It is up to the implementation to decide what to do with these objects. /// - void WriteObject(object sendToPipeline, bool enumerateCollection); + void WriteObject(object? sendToPipeline, bool enumerateCollection); /// /// Called by the cmdlet to display progress information. @@ -172,7 +174,7 @@ public interface ICommandRuntime /// pipeline execution log. /// /// If LogPipelineExecutionDetail is turned on, this information will be written - /// to monad log under log category "Pipeline execution detail" + /// to PowerShell log under log category "Pipeline execution detail" /// /// /// @@ -219,7 +221,7 @@ public interface ICommandRuntime /// /// /// - bool ShouldProcess(string target); + bool ShouldProcess(string? target); /// /// Called by a cmdlet to confirm the operation with the user. Cmdlets which make changes @@ -265,7 +267,7 @@ public interface ICommandRuntime /// /// /// - bool ShouldProcess(string target, string action); + bool ShouldProcess(string? target, string? action); /// /// Called by a cmdlet to confirm the operation with the user. Cmdlets which make changes @@ -319,7 +321,7 @@ public interface ICommandRuntime /// /// /// - bool ShouldProcess(string verboseDescription, string verboseWarning, string caption); + bool ShouldProcess(string? verboseDescription, string? verboseWarning, string? caption); /// /// Called by a cmdlet to confirm the operation with the user. Cmdlets which make changes @@ -379,7 +381,7 @@ public interface ICommandRuntime /// /// /// - bool ShouldProcess(string verboseDescription, string verboseWarning, string caption, out ShouldProcessReason shouldProcessReason); + bool ShouldProcess(string? verboseDescription, string? verboseWarning, string? caption, out ShouldProcessReason shouldProcessReason); /// /// Called by a cmdlet to confirm an operation or grouping of operations with the user. @@ -436,7 +438,7 @@ public interface ICommandRuntime /// /// /// - bool ShouldContinue(string query, string caption); + bool ShouldContinue(string? query, string? caption); /// /// Called to confirm an operation or grouping of operations with the user. @@ -455,11 +457,11 @@ public interface ICommandRuntime /// It may be displayed by some hosts, but not all. /// /// - /// true iff user selects YesToAll. If this is already true, + /// true if-and-only-if user selects YesToAll. If this is already true, /// ShouldContinue will bypass the prompt and return true. /// /// - /// true iff user selects NoToAll. If this is already true, + /// true if-and-only-if user selects NoToAll. If this is already true, /// ShouldContinue will bypass the prompt and return false. /// /// @@ -501,7 +503,7 @@ public interface ICommandRuntime /// /// /// - bool ShouldContinue(string query, string caption, ref bool yesToAll, ref bool noToAll); + bool ShouldContinue(string? query, string? caption, ref bool yesToAll, ref bool noToAll); #endregion Should @@ -515,7 +517,7 @@ public interface ICommandRuntime /// Gets an object that surfaces the current PowerShell transaction. /// When this object is disposed, PowerShell resets the active transaction. /// - PSTransactionContext CurrentPSTransaction { get; } + PSTransactionContext? CurrentPSTransaction { get; } #endregion Transaction Support #region Misc @@ -549,6 +551,7 @@ public interface ICommandRuntime /// if any information is to be added. It should encapsulate the /// error record into an exception and then throw that exception. /// + [System.Diagnostics.CodeAnalysis.DoesNotReturn] void ThrowTerminatingError(ErrorRecord errorRecord); #endregion ThrowTerminatingError #endregion misc @@ -560,7 +563,6 @@ public interface ICommandRuntime /// execute an instance of a Cmdlet. ICommandRuntime2 extends the ICommandRuntime interface /// by adding support for the informational data stream. /// -#nullable enable public interface ICommandRuntime2 : ICommandRuntime { /// @@ -590,11 +592,11 @@ public interface ICommandRuntime2 : ICommandRuntime /// the default option selected in the selection menu is 'No'. /// /// - /// true iff user selects YesToAll. If this is already true, + /// true if-and-only-if user selects YesToAll. If this is already true, /// ShouldContinue will bypass the prompt and return true. /// /// - /// true iff user selects NoToAll. If this is already true, + /// true if-and-only-if user selects NoToAll. If this is already true, /// ShouldContinue will bypass the prompt and return false. /// /// diff --git a/src/System.Management.Automation/engine/InformationRecord.cs b/src/System.Management.Automation/engine/InformationRecord.cs index 310c34a8809..87a3d9cda9d 100644 --- a/src/System.Management.Automation/engine/InformationRecord.cs +++ b/src/System.Management.Automation/engine/InformationRecord.cs @@ -14,7 +14,7 @@ namespace System.Management.Automation /// which, according to host or user preference, forwards that information on to the host for rendering to the user. /// /// - [DataContract()] + [DataContract] public class InformationRecord { /// @@ -96,15 +96,13 @@ public string User { get { - if (this._user == null) - { - // domain\user on Windows, just user on Unix + // domain\user on Windows, just user on Unix + this._user ??= #if UNIX - this._user = Environment.UserName; + Environment.UserName; #else - this._user = Environment.UserDomainName + "\\" + Environment.UserName; + Environment.UserDomainName + "\\" + Environment.UserName; #endif - } return _user; } diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index 257cb79ce7b..84f513d8450 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -44,22 +44,28 @@ internal static void Init() // We shouldn't create too many tasks. #if !UNIX - // Amsi initialize can be a little slow + // Amsi initialize can be a little slow. Task.Run(() => AmsiUtils.WinScanContent(content: string.Empty, sourceMetadata: string.Empty, warmUp: true)); #endif + // Initialize the types 'Compiler', 'CachedReflectionInfo', and 'ExpressionCache'. + // Their type initializers do a lot of reflection operations. + // We will access 'Compiler' members when creating the first session state. + Task.Run(() => _ = Compiler.DottedLocalsTupleType); // One other task for other stuff that's faster, but still a little slow. Task.Run(() => { - // Loading the resources for System.Management.Automation can be expensive, so force that to - // happen early on a background thread. + // Loading the resources for System.Management.Automation can be expensive, + // so force that to happen early on a background thread. _ = RunspaceInit.OutputEncodingDescription; // This will init some tables and could load some assemblies. - _ = TypeAccelerators.builtinTypeAccelerators; + // We will access 'LanguagePrimitives' when binding built-in variables for the Runspace. + LanguagePrimitives.GetEnumerator(null); // This will init some tables and could load some assemblies. - LanguagePrimitives.GetEnumerator(null); + // We will access 'TypeAccelerators' when auto-loading the PSReadLine module, which happens last. + _ = TypeAccelerators.builtinTypeAccelerators; }); } } @@ -410,7 +416,7 @@ public SessionStateAssemblyEntry(string name) /// The cloned object. public override InitialSessionStateEntry Clone() { - SessionStateAssemblyEntry entry = new SessionStateAssemblyEntry(Name, FileName); + var entry = new SessionStateAssemblyEntry(Name, FileName); entry.SetPSSnapIn(this.PSSnapIn); entry.SetModule(this.Module); return entry; @@ -646,7 +652,7 @@ public override InitialSessionStateEntry Clone() public string Description { get; } = string.Empty; /// - /// Options controling scope visibility and setability for this entry. + /// Options controlling scope visibility and setability for this entry. /// public ScopedItemOptions Options { get; } = ScopedItemOptions.None; } @@ -803,7 +809,7 @@ internal void SetHelpFile(string help) internal ScriptBlock ScriptBlock { get; set; } /// - /// Options controling scope visibility and setability for this entry. + /// Options controlling scope visibility and setability for this entry. /// public ScopedItemOptions Options { get; } = ScopedItemOptions.None; @@ -974,10 +980,7 @@ public InitialSessionStateEntryCollection() /// public InitialSessionStateEntryCollection(IEnumerable items) { - if (items == null) - { - throw new ArgumentNullException(nameof(items)); - } + ArgumentNullException.ThrowIfNull(items); _internalCollection = new Collection(); @@ -1148,10 +1151,7 @@ public void Clear() /// The type of object to remove, can be null to remove any type. public void Remove(string name, object type) { - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } + ArgumentNullException.ThrowIfNull(name); lock (_syncObject) { @@ -1186,10 +1186,7 @@ public void Remove(string name, object type) /// The item to add... public void Add(T item) { - if (item == null) - { - throw new ArgumentNullException(nameof(item)); - } + ArgumentNullException.ThrowIfNull(item); lock (_syncObject) { @@ -1203,10 +1200,7 @@ public void Add(T item) /// public void Add(IEnumerable items) { - if (items == null) - { - throw new ArgumentNullException(nameof(items)); - } + ArgumentNullException.ThrowIfNull(items); lock (_syncObject) { @@ -1312,7 +1306,7 @@ private static void MakeDisallowedEntriesPrivate(InitialSessionStateEntryColl /// Creates an initial session state from a PSSC configuration file. /// /// The path to the PSSC session configuration file. - /// + /// InitialSessionState object. public static InitialSessionState CreateFromSessionConfigurationFile(string path) { return CreateFromSessionConfigurationFile(path, null); @@ -1327,10 +1321,48 @@ public static InitialSessionState CreateFromSessionConfigurationFile(string path /// target session. If you have a WindowsPrincipal for a user, for example, create a Function that /// checks windowsPrincipal.IsInRole(). /// - /// - public static InitialSessionState CreateFromSessionConfigurationFile(string path, Func roleVerifier) + /// InitialSessionState object. + public static InitialSessionState CreateFromSessionConfigurationFile( + string path, + Func roleVerifier) + { + return CreateFromSessionConfigurationFile(path, roleVerifier, validateFile: false); + } + + /// + /// Creates an initial session state from a PSSC configuration file. + /// + /// The path to the PSSC session configuration file. + /// + /// The verifier that PowerShell should call to determine if groups in the Role entry apply to the + /// target session. If you have a WindowsPrincipal for a user, for example, create a Function that + /// checks windowsPrincipal.IsInRole(). + /// + /// Validates the file contents for supported SessionState options. + /// InitialSessionState object. + public static InitialSessionState CreateFromSessionConfigurationFile( + string path, + Func roleVerifier, + bool validateFile) { - Remoting.DISCPowerShellConfiguration discConfiguration = new Remoting.DISCPowerShellConfiguration(path, roleVerifier); + if (path is null) + { + throw new PSArgumentNullException(nameof(path)); + } + + if (!File.Exists(path)) + { + throw new PSInvalidOperationException( + StringUtil.Format(ConsoleInfoErrorStrings.ConfigurationFileDoesNotExist, path)); + } + + if (!path.EndsWith(".pssc", StringComparison.OrdinalIgnoreCase)) + { + throw new PSInvalidOperationException( + StringUtil.Format(ConsoleInfoErrorStrings.NotConfigurationFile, path)); + } + + Remoting.DISCPowerShellConfiguration discConfiguration = new Remoting.DISCPowerShellConfiguration(path, roleVerifier, validateFile); return discConfiguration.GetInitialSessionState(null); } @@ -1434,9 +1466,6 @@ private static InitialSessionState CreateRestrictedForRemoteServer() return iss; } - // Porting note: moved to Platform so we have one list to maintain - private static readonly string[] s_PSCoreFormatFileNames = Platform.FormatFileNames.ToArray(); - private static void IncludePowerShellCoreFormats(InitialSessionState iss) { string psHome = Utils.DefaultPowerShellAppBase; @@ -1446,7 +1475,7 @@ private static void IncludePowerShellCoreFormats(InitialSessionState iss) } iss.Formats.Clear(); - foreach (var coreFormat in s_PSCoreFormatFileNames) + foreach (var coreFormat in Platform.FormatFileNames) { iss.Formats.Add(new SessionStateFormatEntry(Path.Combine(psHome, coreFormat))); } @@ -1477,7 +1506,7 @@ public static InitialSessionState Create() // be causing test failures - i suspect due to lack test isolation - brucepay Mar 06/2008 #if false // Add the default variables and make them private... - iss.Variables.Add(BuiltInVariables); + iss.AddVariables(BuiltInVariables); foreach (SessionStateVariableEntry v in iss.Variables) { v.Visibility = SessionStateEntryVisibility.Private; @@ -1540,14 +1569,10 @@ public static InitialSessionState CreateDefault() string assembly = ss.Assemblies[i].FileName; if (!string.IsNullOrEmpty(assembly)) { - if (assemblyList.Contains(assembly)) + if (!assemblyList.Add(assembly)) { ss.Assemblies.RemoveItem(i); } - else - { - assemblyList.Add(assembly); - } } } @@ -1666,11 +1691,6 @@ public InitialSessionState Clone() ss.DisableFormatUpdates = this.DisableFormatUpdates; - foreach (var s in this.defaultSnapins) - { - ss.defaultSnapins.Add(s); - } - foreach (var s in ImportedSnapins) { ss.ImportedSnapins.Add(s.Key, s.Value); @@ -1834,10 +1854,7 @@ public Microsoft.PowerShell.ExecutionPolicy ExecutionPolicy /// public void ImportPSModule(params string[] name) { - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } + ArgumentNullException.ThrowIfNull(name); foreach (string n in name) { @@ -1862,10 +1879,7 @@ internal void ClearPSModules() /// public void ImportPSModule(IEnumerable modules) { - if (modules == null) - { - throw new ArgumentNullException(nameof(modules)); - } + ArgumentNullException.ThrowIfNull(modules); foreach (var moduleSpecification in modules) { @@ -1893,10 +1907,7 @@ public void ImportPSModulesFromPath(string path) /// internal void ImportPSCoreModule(string[] name) { - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } + ArgumentNullException.ThrowIfNull(name); foreach (string n in name) { @@ -2413,9 +2424,14 @@ private void Bind_LoadAssemblies(ExecutionContext context) // Load the assemblies and initialize the assembly cache... foreach (SessionStateAssemblyEntry ssae in Assemblies) { - if (etwEnabled) RunspaceEventSource.Log.LoadAssemblyStart(ssae.Name, ssae.FileName); - Exception error = null; - Assembly asm = context.AddAssembly(ssae.Name, ssae.FileName, out error); + if (etwEnabled) + { + RunspaceEventSource.Log.LoadAssemblyStart(ssae.Name, ssae.FileName); + } + + // Specify the source only if this is for module loading. + // The source is used for proper cleaning of the assembly cache when a module is unloaded. + Assembly asm = context.AddAssembly(ssae.Module?.Name, ssae.Name, ssae.FileName, out Exception error); if (asm == null || error != null) { @@ -2740,7 +2756,7 @@ private static void ProcessCommandModification(Hashtable commandModification, Co break; case "ValidatePattern": - string pattern = "^(" + string.Join("|", parameterValidationValues) + ")$"; + string pattern = "^(" + string.Join('|', parameterValidationValues) + ")$"; ValidatePatternAttribute validatePattern = new ValidatePatternAttribute(pattern); metadata.Parameters[parameterName].Attributes.Add(validatePattern); break; @@ -2844,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); } @@ -2901,7 +2917,7 @@ private Exception ProcessPowerShellCommand(PowerShell psToInvoke, Runspace initi } finally { - // Restore the langauge mode, but not if it was altered by the startup script itself. + // Restore the language mode, but not if it was altered by the startup script itself. if (initializedRunspace.SessionStateProxy.LanguageMode == PSLanguageMode.FullLanguage) { initializedRunspace.SessionStateProxy.LanguageMode = originalLanguageMode; @@ -2946,13 +2962,20 @@ private RunspaceOpenModuleLoadException ProcessModulesToImport( HashSet unresolvedCmdsToExpose) { RunspaceOpenModuleLoadException exceptionToReturn = null; + List processedModules = new List(); foreach (object module in moduleList) { string moduleName = module as string; if (moduleName != null) { - exceptionToReturn = ProcessOneModule(initializedRunspace, moduleName, null, path, publicCommands); + exceptionToReturn = ProcessOneModule( + initializedRunspace: initializedRunspace, + name: moduleName, + moduleInfoToLoad: null, + path: path, + publicCommands: publicCommands, + processedModules: processedModules); } else { @@ -2963,7 +2986,13 @@ private RunspaceOpenModuleLoadException ProcessModulesToImport( { // if only name is specified in the module spec, just try import the module // ie., don't take the performance overhead of calling GetModule. - exceptionToReturn = ProcessOneModule(initializedRunspace, moduleSpecification.Name, null, path, publicCommands); + exceptionToReturn = ProcessOneModule( + initializedRunspace: initializedRunspace, + name: moduleSpecification.Name, + moduleInfoToLoad: null, + path: path, + publicCommands: publicCommands, + processedModules: processedModules); } else { @@ -2971,7 +3000,13 @@ private RunspaceOpenModuleLoadException ProcessModulesToImport( if (moduleInfos != null && moduleInfos.Count > 0) { - exceptionToReturn = ProcessOneModule(initializedRunspace, moduleSpecification.Name, moduleInfos[0], path, publicCommands); + exceptionToReturn = ProcessOneModule( + initializedRunspace: initializedRunspace, + name: moduleSpecification.Name, + moduleInfoToLoad: moduleInfos[0], + path: path, + publicCommands: publicCommands, + processedModules: processedModules); } else { @@ -3020,7 +3055,11 @@ private RunspaceOpenModuleLoadException ProcessModulesToImport( string commandToMakeVisible = Utils.ParseCommandName(unresolvedCommand, out moduleName); bool found = false; - foreach (CommandInfo cmd in LookupCommands(commandToMakeVisible, moduleName, initializedRunspace.ExecutionContext)) + foreach (CommandInfo cmd in LookupCommands( + commandPattern: commandToMakeVisible, + moduleName: moduleName, + context: initializedRunspace.ExecutionContext, + processedModules: processedModules)) { if (!found) { @@ -3071,11 +3110,13 @@ private RunspaceOpenModuleLoadException ProcessModulesToImport( /// /// /// + /// /// private static IEnumerable LookupCommands( string commandPattern, string moduleName, - ExecutionContext context) + ExecutionContext context, + List processedModules) { bool isWildCardPattern = WildcardPattern.ContainsWildcardCharacters(commandPattern); var searchOptions = isWildCardPattern ? @@ -3089,7 +3130,11 @@ private static IEnumerable LookupCommands( CommandOrigin cmdOrigin = CommandOrigin.Runspace; while (true) { - foreach (CommandInfo commandInfo in context.SessionState.InvokeCommand.GetCommands(commandPattern, CommandTypes.All, searchOptions, cmdOrigin)) + foreach (CommandInfo commandInfo in context.SessionState.InvokeCommand.GetCommands( + name: commandPattern, + commandTypes: CommandTypes.All, + options: searchOptions, + commandOrigin: cmdOrigin)) { // If module name is provided then use it to restrict returned results. if (haveModuleName && !moduleName.Equals(commandInfo.ModuleName, StringComparison.OrdinalIgnoreCase)) @@ -3119,13 +3164,43 @@ private static IEnumerable LookupCommands( // Next try internal search. cmdOrigin = CommandOrigin.Internal; } + + // If the command is associated with a module, try finding the command in the imported module list. + // The SessionState function table holds only one command name, and if two or more modules contain + // a command with the same name, only one of them will appear in the function table search above. + if (!found && haveModuleName) + { + var pattern = new WildcardPattern(commandPattern); + + foreach (PSModuleInfo moduleInfo in processedModules) + { + if (moduleInfo.Name.Equals(moduleName, StringComparison.OrdinalIgnoreCase)) + { + foreach (var cmd in moduleInfo.ExportedCommands.Values) + { + if (pattern.IsMatch(cmd.Name)) + { + yield return cmd; + } + } + + break; + } + } + } } /// /// If is null, import module using . Otherwise, /// import module using /// - private RunspaceOpenModuleLoadException ProcessOneModule(Runspace initializedRunspace, string name, PSModuleInfo moduleInfoToLoad, string path, HashSet publicCommands) + private RunspaceOpenModuleLoadException ProcessOneModule( + Runspace initializedRunspace, + string name, + PSModuleInfo moduleInfoToLoad, + string path, + HashSet publicCommands, + List processedModules) { using (PowerShell pse = PowerShell.Create()) { @@ -3162,6 +3237,11 @@ private RunspaceOpenModuleLoadException ProcessOneModule(Runspace initializedRun c = new CmdletInfo("Out-Default", typeof(OutDefaultCommand), null, null, initializedRunspace.ExecutionContext); pse.AddCommand(new Command(c)); } + else + { + // For runspace init module processing, pass back the PSModuleInfo to the output pipeline. + cmd.Parameters.Add("PassThru"); + } pse.Runspace = initializedRunspace; // Module import should be run in FullLanguage mode since it is running in @@ -3170,7 +3250,10 @@ private RunspaceOpenModuleLoadException ProcessOneModule(Runspace initializedRun pse.Runspace.ExecutionContext.LanguageMode = PSLanguageMode.FullLanguage; try { - pse.Invoke(); + // For runspace init module processing, collect the imported PSModuleInfo returned in the output pipeline. + // In other cases, this collection will be empty. + Collection moduleInfos = pse.Invoke(); + processedModules.AddRange(moduleInfos); } finally { @@ -3365,8 +3448,7 @@ internal static void SetSessionStateDrive(ExecutionContext context, bool setLoca { // If we can't access the Environment.CurrentDirectory, we may be in an AppContainer. Set the // default drive to $pshome - System.Diagnostics.Process currentProcess = System.Diagnostics.Process.GetCurrentProcess(); - string defaultPath = System.IO.Path.GetDirectoryName(PsUtils.GetMainModule(currentProcess).FileName); + string defaultPath = System.IO.Path.GetDirectoryName(Environment.ProcessPath); context.EngineSessionState.SetLocation(defaultPath, providerContext); } } @@ -3383,105 +3465,6 @@ internal static void CreateQuestionVariable(ExecutionContext context) context.EngineSessionState.SetVariableAtScope(qv, "global", true, CommandOrigin.Internal); } - /// - /// Remove anything that would have been bound by this ISS instance. - /// At this point, it removes assemblies and cmdlet entries at the top level. - /// It also removes types and formats. - /// The other entry types - functions, variables, aliases - /// are not removed by this function. - /// - /// - internal void Unbind(ExecutionContext context) - { - lock (_syncObject) - { - SessionStateInternal ss = context.EngineSessionState; - - // Remove the assemblies from the assembly cache... - foreach (SessionStateAssemblyEntry ssae in Assemblies) - { - context.RemoveAssembly(ssae.Name); - } - - // Remove all of the commands from the top-level session state. - foreach (SessionStateCommandEntry cmd in Commands) - { - SessionStateCmdletEntry ssce = cmd as SessionStateCmdletEntry; - if (ssce != null) - { - List matches; - if (context.TopLevelSessionState.GetCmdletTable().TryGetValue(ssce.Name, out matches)) - { - // Remove the name from the list... - for (int i = matches.Count - 1; i >= 0; i--) - { - if (matches[i].ModuleName.Equals(cmd.PSSnapIn.Name)) - { - string name = matches[i].Name; - matches.RemoveAt(i); - context.TopLevelSessionState.RemoveCmdlet(name, i, /*force*/ true); - } - } - // And remove the entry if the list is now empty... - if (matches.Count == 0) - { - context.TopLevelSessionState.RemoveCmdletEntry(ssce.Name, true); - } - } - - continue; - } - } - - // Remove all of the providers from the top-level provider table. - if (_providers != null && _providers.Count > 0) - { - Dictionary> providerTable = context.TopLevelSessionState.Providers; - - foreach (SessionStateProviderEntry sspe in _providers) - { - List pl; - if (providerTable.TryGetValue(sspe.Name, out pl)) - { - Diagnostics.Assert(pl != null, "There should never be a null list of entries in the provider table"); - // For each provider with the same name... - for (int i = pl.Count - 1; i >= 0; i--) - { - ProviderInfo pi = pl[i]; - - // If it was implemented by this entry, remove it - if (pi.ImplementingType == sspe.ImplementingType) - { - RemoveAllDrivesForProvider(pi, context.TopLevelSessionState); - pl.RemoveAt(i); - } - } - - // If there are no providers left with this name, remove the key. - if (pl.Count == 0) - { - providerTable.Remove(sspe.Name); - } - } - } - } - - List formatFilesToRemove = new List(); - if (this.Formats != null) - { - formatFilesToRemove.AddRange(this.Formats.Select(f => f.FileName)); - } - - List typeFilesToRemove = new List(); - if (this.Types != null) - { - typeFilesToRemove.AddRange(this.Types.Select(t => t.FileName)); - } - - RemoveTypesAndFormats(context, formatFilesToRemove, typeFilesToRemove); - } - } - internal static void RemoveTypesAndFormats(ExecutionContext context, IList formatFilesToRemove, IList typeFilesToRemove) { // The formats and types tables are implemented in such a way that @@ -3600,8 +3583,7 @@ internal void UpdateTypes(ExecutionContext context, bool updateOnly) moduleName = sste.PSSnapIn.Name; } - bool unused; - context.TypeTable.Update(moduleName, sste.FileName, errors, context.AuthorizationManager, context.EngineHostInterface, out unused); + context.TypeTable.Update(moduleName, sste.FileName, errors, context.AuthorizationManager, context.EngineHostInterface, out _); } } else if (sste.TypeTable != null) @@ -3792,7 +3774,7 @@ public PSSnapInInfo ImportPSSnapIn(string name, out PSSnapInException warning) // implementation and should be refactored. PSSnapInInfo newPSSnapIn = PSSnapInReader.Read("2", name); - if (!Utils.IsPSVersionSupported(newPSSnapIn.PSVersion.ToString())) + if (!PSVersionInfo.IsValidPSVersion(newPSSnapIn.PSVersion)) { s_PSSnapInTracer.TraceError("MshSnapin {0} and current monad engine's versions don't match.", name); @@ -3805,34 +3787,22 @@ public PSSnapInInfo ImportPSSnapIn(string name, out PSSnapInException warning) // Now actually load the snapin... PSSnapInInfo snapin = ImportPSSnapIn(newPSSnapIn, out warning); - if (snapin != null) - { - ImportedSnapins.Add(snapin.Name, snapin); - } return snapin; } internal PSSnapInInfo ImportCorePSSnapIn() { - // Load Microsoft.PowerShell.Core as a snapin + // Load Microsoft.PowerShell.Core as a snapin. PSSnapInInfo coreSnapin = PSSnapInReader.ReadCoreEngineSnapIn(); - this.defaultSnapins.Add(coreSnapin); - try - { - PSSnapInException warning; - this.ImportPSSnapIn(coreSnapin, out warning); - } - catch (PSSnapInException) - { - throw; - } - + ImportPSSnapIn(coreSnapin, out _); return coreSnapin; } internal PSSnapInInfo ImportPSSnapIn(PSSnapInInfo psSnapInInfo, out PSSnapInException warning) { + ArgumentNullException.ThrowIfNull(psSnapInInfo); + // See if the snapin is already loaded. If has been then there will be an entry in the // Assemblies list for it already... bool reload = true; @@ -3865,12 +3835,6 @@ internal PSSnapInInfo ImportPSSnapIn(PSSnapInInfo psSnapInInfo, out PSSnapInExce Dictionary> aliases = null; Dictionary providers = null; - if (psSnapInInfo == null) - { - ArgumentNullException e = new ArgumentNullException(nameof(psSnapInInfo)); - throw e; - } - Assembly assembly = null; string helpFile = null; @@ -3927,18 +3891,9 @@ internal PSSnapInInfo ImportPSSnapIn(PSSnapInInfo psSnapInInfo, out PSSnapInExce this.Formats.Add(formatEntry); } - SessionStateAssemblyEntry assemblyEntry = new SessionStateAssemblyEntry(psSnapInInfo.AssemblyName, psSnapInInfo.AbsoluteModulePath); - + var assemblyEntry = new SessionStateAssemblyEntry(psSnapInInfo.AssemblyName, psSnapInInfo.AbsoluteModulePath); assemblyEntry.SetPSSnapIn(psSnapInInfo); - - this.Assemblies.Add(assemblyEntry); - - // entry from types.ps1xml references a type (Microsoft.PowerShell.Commands.SecurityDescriptorCommandsBase) in this assembly - if (psSnapInInfo.Name.Equals(CoreSnapin, StringComparison.OrdinalIgnoreCase)) - { - assemblyEntry = new SessionStateAssemblyEntry("Microsoft.PowerShell.Security", null); - this.Assemblies.Add(assemblyEntry); - } + Assemblies.Add(assemblyEntry); if (cmdlets != null) { @@ -3989,37 +3944,18 @@ internal PSSnapInInfo ImportPSSnapIn(PSSnapInInfo psSnapInInfo, out PSSnapInExce } } + ImportedSnapins.Add(psSnapInInfo.Name, psSnapInInfo); return psSnapInInfo; } - internal List GetPSSnapIn(string psSnapinName) + internal PSSnapInInfo GetPSSnapIn(string psSnapinName) { - List loadedSnapins = null; - foreach (var defaultSnapin in defaultSnapins) + if (ImportedSnapins.TryGetValue(psSnapinName, out PSSnapInInfo importedSnapin)) { - if (defaultSnapin.Name.Equals(psSnapinName, StringComparison.OrdinalIgnoreCase)) - { - if (loadedSnapins == null) - { - loadedSnapins = new List(); - } - - loadedSnapins.Add(defaultSnapin); - } + return importedSnapin; } - PSSnapInInfo importedSnapin = null; - if (ImportedSnapins.TryGetValue(psSnapinName, out importedSnapin)) - { - if (loadedSnapins == null) - { - loadedSnapins = new List(); - } - - loadedSnapins.Add(importedSnapin); - } - - return loadedSnapins; + return null; } [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom")] @@ -4041,26 +3977,18 @@ internal static Assembly LoadAssemblyFromFile(string fileName) internal void ImportCmdletsFromAssembly(Assembly assembly, PSModuleInfo module) { - if (assembly == null) - { - ArgumentNullException e = new ArgumentNullException(nameof(assembly)); - throw e; - } - - Dictionary cmdlets = null; - Dictionary> aliases = null; - Dictionary providers = null; + ArgumentNullException.ThrowIfNull(assembly); string assemblyPath = assembly.Location; - PSSnapInHelpers.AnalyzePSSnapInAssembly(assembly, assemblyPath, psSnapInInfo: null, module, out cmdlets, out aliases, out providers, helpFile: out _); - - // If this is an in-memory assembly, don't added it to the list of AssemblyEntries - // since it can't be loaded by path or name - if (!string.IsNullOrEmpty(assembly.Location)) - { - SessionStateAssemblyEntry assemblyEntry = new SessionStateAssemblyEntry(assembly.FullName, assemblyPath); - this.Assemblies.Add(assemblyEntry); - } + PSSnapInHelpers.AnalyzePSSnapInAssembly( + assembly, + assemblyPath, + psSnapInInfo: null, + module, + out Dictionary cmdlets, + out Dictionary> aliases, + out Dictionary providers, + helpFile: out _); if (cmdlets != null) { @@ -4109,8 +4037,10 @@ internal void ImportCmdletsFromAssembly(Assembly assembly, PSModuleInfo module) #> [CmdletBinding(DefaultParameterSetName = 'ScriptInputSet')] +[OutputType([System.Management.Automation.CommandCompletion])] Param( [Parameter(ParameterSetName = 'ScriptInputSet', Mandatory = $true, Position = 0)] + [AllowEmptyString()] [string] $inputScript, [Parameter(ParameterSetName = 'ScriptInputSet', Position = 1)] @@ -4148,7 +4078,7 @@ internal void ImportCmdletsFromAssembly(Assembly assembly, PSModuleInfo module) <#options#> $options) } } - "; +"; /// /// This is the default function to use for clear-host. @@ -4183,16 +4113,16 @@ internal static string GetClearHostFunctionText() } } - /// - /// This is the default function to use for man/help. It uses - /// splatting to pass in the parameters. - /// - internal static string GetHelpPagingFunctionText() +#if UNIX + internal static string GetExecFunctionText() { - // We used to generate the text for this function so you could add a parameter - // to Get-Help and not worry about adding it here. That was a little slow at - // startup, so it's hard coded, with a test to make sure the parameters match. return @" +Switch-Process -WithCommand $args +"; + } +#endif + + internal const string WindowsHelpFunctionText = @" <# .FORWARDHELPTARGETNAME Get-Help .FORWARDHELPCATEGORY Cmdlet @@ -4266,14 +4196,132 @@ .FORWARDHELPCATEGORY Cmdlet elseif ($help -ne $null) { # By default use more on Windows and less on Linux. - if ($IsWindows) { - $pagerCommand = 'more.com' - $pagerArgs = $null + $pagerCommand = 'more.com' + $pagerArgs = $null + + # Respect PAGER environment variable which allows user to specify a custom pager. + # Ignore a pure whitespace PAGER value as that would cause the tokenizer to return 0 tokens. + if (![string]::IsNullOrWhitespace($env:PAGER)) { + if (Get-Command $env:PAGER -ErrorAction Ignore) { + # Entire PAGER value corresponds to a single command. + $pagerCommand = $env:PAGER + $pagerArgs = $null + } + else { + # PAGER value is not a valid command, check if PAGER command and arguments have been specified. + # Tokenize the specified $env:PAGER value. Ignore tokenizing errors since any errors may be valid + # argument syntax for the paging utility. + $errs = $null + $tokens = [System.Management.Automation.PSParser]::Tokenize($env:PAGER, [ref]$errs) + + $customPagerCommand = $tokens[0].Content + if (!(Get-Command $customPagerCommand -ErrorAction Ignore)) { + # Custom pager command is invalid, issue a warning. + Write-Warning ""Custom-paging utility command not found. Ignoring command specified in `$env:PAGER: $env:PAGER"" + } + else { + # This approach will preserve all the pagers args. + $pagerCommand = $customPagerCommand + $pagerArgs = if ($tokens.Count -gt 1) { + $env:PAGER.Substring($tokens[1].Start) + } + else { + $null + } + } + } + } + + $pagerCommandInfo = Get-Command -Name $pagerCommand -ErrorAction Ignore + if ($pagerCommandInfo -eq $null) { + $help + } + elseif ($pagerCommandInfo.CommandType -eq 'Application') { + # If the pager is an application, format the output width before sending to the app. + $consoleWidth = [System.Math]::Max([System.Console]::WindowWidth, 20) + + if ($pagerArgs) { + $help | Out-String -Stream -Width ($consoleWidth - 1) | & $pagerCommand $pagerArgs + } + else { + $help | Out-String -Stream -Width ($consoleWidth - 1) | & $pagerCommand + } } else { - $pagerCommand = 'less' - $pagerArgs = '-Ps""Page %db?B of %D:.\. Press h for help or q to quit\.$""' + # The pager command is a PowerShell function, script or alias, so pipe directly into it. + $help | & $pagerCommand $pagerArgs } + } +"; + + internal const string UnixHelpFunctionText = @" +<# +.FORWARDHELPTARGETNAME Get-Help +.FORWARDHELPCATEGORY Cmdlet +#> +[CmdletBinding(DefaultParameterSetName='AllUsersView', HelpUri='https://go.microsoft.com/fwlink/?LinkID=113316')] +param( + [Parameter(Position=0, ValueFromPipelineByPropertyName=$true)] + [string] + ${Name}, + + [string] + ${Path}, + + [ValidateSet('Alias','Cmdlet','Provider','General','FAQ','Glossary','HelpFile','ScriptCommand','Function','Filter','ExternalScript','All','DefaultHelp','DscResource','Class','Configuration')] + [string[]] + ${Category}, + + [Parameter(ParameterSetName='DetailedView', Mandatory=$true)] + [switch] + ${Detailed}, + + [Parameter(ParameterSetName='AllUsersView')] + [switch] + ${Full}, + + [Parameter(ParameterSetName='Examples', Mandatory=$true)] + [switch] + ${Examples}, + + [Parameter(ParameterSetName='Parameters', Mandatory=$true)] + [string[]] + ${Parameter}, + + [string[]] + ${Component}, + + [string[]] + ${Functionality}, + + [string[]] + ${Role}, + + [Parameter(ParameterSetName='Online', Mandatory=$true)] + [switch] + ${Online}) + + # Display the full help topic by default but only for the AllUsersView parameter set. + if (($psCmdlet.ParameterSetName -eq 'AllUsersView') -and !$Full) { + $PSBoundParameters['Full'] = $true + } + + # Linux need the default + $OutputEncoding = [System.Console]::OutputEncoding + + $help = Get-Help @PSBoundParameters + + # If a list of help is returned or AliasHelpInfo (because it is small), don't pipe to more + $psTypeNames = ($help | Select-Object -First 1).PSTypeNames + if ($psTypeNames -Contains 'HelpInfoShort' -Or $psTypeNames -Contains 'AliasHelpInfo') + { + $help + } + elseif ($help -ne $null) + { + # By default use more on Windows and less on Linux. + $pagerCommand = 'less' + $pagerArgs = '-s','-P','Page %db?B of %D:.\. Press h for help or q to quit\.' # Respect PAGER environment variable which allows user to specify a custom pager. # Ignore a pure whitespace PAGER value as that would cause the tokenizer to return 0 tokens. @@ -4312,10 +4360,7 @@ .FORWARDHELPCATEGORY Cmdlet $consoleWidth = [System.Math]::Max([System.Console]::WindowWidth, 20) if ($pagerArgs) { - # Supply pager arguments to an application without any PowerShell parsing of the arguments. - # Leave environment variable to help user debug arguments supplied in $env:PAGER. - $env:__PSPAGER_ARGS = $pagerArgs - $help | Out-String -Stream -Width ($consoleWidth - 1) | & $pagerCommand --% %__PSPAGER_ARGS% + $help | Out-String -Stream -Width ($consoleWidth - 1) | & $pagerCommand $pagerArgs } else { $help | Out-String -Stream -Width ($consoleWidth - 1) | & $pagerCommand @@ -4327,8 +4372,31 @@ .FORWARDHELPCATEGORY Cmdlet } } "; + + /// + /// This is the default function to use for man/help. It uses + /// splatting to pass in the parameters. + /// +#if !UNIX + internal static string GetHelpPagingFunctionText() + { + // We used to generate the text for this function so you could add a parameter + // to Get-Help and not worry about adding it here. That was a little slow at + // startup, so it's hard coded, with a test to make sure the parameters match. + return WindowsHelpFunctionText; } +#else + internal static string GetHelpPagingFunctionText() + { + // We used to generate the text for this function so you could add a parameter + // to Get-Help and not worry about adding it here. That was a little slow at + // startup, so it's hard coded, with a test to make sure the parameters match. + // This version removes the -ShowWindow parameter since it is not supported on Linux. + return UnixHelpFunctionText; + } +#endif + internal static string GetMkdirFunctionText() { return @" @@ -4432,154 +4500,191 @@ .ForwardHelpCategory Cmdlet internal const bool DefaultWhatIfPreference = false; internal const ConfirmImpact DefaultConfirmPreference = ConfirmImpact.High; - internal static readonly SessionStateVariableEntry[] BuiltInVariables = new SessionStateVariableEntry[] - { - // Engine variables that should be precreated before running profile - // Bug fix for Win7:2202228 Engine halts if initial command fulls up variable table - // Anytime a new variable that the engine depends on to run is added, this table - // must be updated... - new SessionStateVariableEntry(SpecialVariables.LastToken, null, string.Empty), - new SessionStateVariableEntry(SpecialVariables.FirstToken, null, string.Empty), - new SessionStateVariableEntry(SpecialVariables.StackTrace, null, string.Empty), - - // Variable which controls the output rendering - new SessionStateVariableEntry( - SpecialVariables.PSStyle, - PSStyle.Instance, - RunspaceInit.PSStyleDescription, - ScopedItemOptions.None), - - // Variable which controls the encoding for piping data to a NativeCommand - new SessionStateVariableEntry( - SpecialVariables.OutputEncoding, - Utils.utf8NoBom, - RunspaceInit.OutputEncodingDescription, - ScopedItemOptions.None, - new ArgumentTypeConverterAttribute(typeof(System.Text.Encoding))), - - // Preferences - // - // NTRAID#Windows Out Of Band Releases-931461-2006/03/13 - // ArgumentTypeConverterAttribute is applied to these variables, - // but this only reaches the global variable. If these are - // redefined in script scope etc, the type conversion - // is not applicable. - // - // Variables typed to ActionPreference - new SessionStateVariableEntry( - SpecialVariables.ConfirmPreference, - DefaultConfirmPreference, - RunspaceInit.ConfirmPreferenceDescription, - ScopedItemOptions.None, - new ArgumentTypeConverterAttribute(typeof(ConfirmImpact))), - new SessionStateVariableEntry( - SpecialVariables.DebugPreference, - DefaultDebugPreference, - RunspaceInit.DebugPreferenceDescription, - ScopedItemOptions.None, - new ArgumentTypeConverterAttribute(typeof(ActionPreference))), - new SessionStateVariableEntry( - SpecialVariables.ErrorActionPreference, - DefaultErrorActionPreference, - RunspaceInit.ErrorActionPreferenceDescription, - ScopedItemOptions.None, - new ArgumentTypeConverterAttribute(typeof(ActionPreference))), - new SessionStateVariableEntry( - SpecialVariables.ProgressPreference, - DefaultProgressPreference, - RunspaceInit.ProgressPreferenceDescription, - ScopedItemOptions.None, - new ArgumentTypeConverterAttribute(typeof(ActionPreference))), - new SessionStateVariableEntry( - SpecialVariables.VerbosePreference, - DefaultVerbosePreference, - RunspaceInit.VerbosePreferenceDescription, - ScopedItemOptions.None, - new ArgumentTypeConverterAttribute(typeof(ActionPreference))), - new SessionStateVariableEntry( - SpecialVariables.WarningPreference, - DefaultWarningPreference, - RunspaceInit.WarningPreferenceDescription, - ScopedItemOptions.None, - new ArgumentTypeConverterAttribute(typeof(ActionPreference))), - new SessionStateVariableEntry( - SpecialVariables.InformationPreference, - DefaultInformationPreference, - RunspaceInit.InformationPreferenceDescription, - ScopedItemOptions.None, - new ArgumentTypeConverterAttribute(typeof(ActionPreference))), - new SessionStateVariableEntry( - SpecialVariables.ErrorView, - DefaultErrorView, - RunspaceInit.ErrorViewDescription, - ScopedItemOptions.None, - new ArgumentTypeConverterAttribute(typeof(ErrorView))), - new SessionStateVariableEntry( - SpecialVariables.NestedPromptLevel, - 0, - RunspaceInit.NestedPromptLevelDescription), - new SessionStateVariableEntry( - SpecialVariables.WhatIfPreference, - DefaultWhatIfPreference, - RunspaceInit.WhatIfPreferenceDescription), - new SessionStateVariableEntry( - FormatEnumerationLimit, - DefaultFormatEnumerationLimit, - RunspaceInit.FormatEnumerationLimitDescription), - - // variable for PSEmailServer - new SessionStateVariableEntry( - SpecialVariables.PSEmailServer, - string.Empty, - RunspaceInit.PSEmailServerDescription), - - // Start: Variables which control remoting behavior - new SessionStateVariableEntry( - Microsoft.PowerShell.Commands.PSRemotingBaseCmdlet.DEFAULT_SESSION_OPTION, - new System.Management.Automation.Remoting.PSSessionOption(), - RemotingErrorIdStrings.PSDefaultSessionOptionDescription, - ScopedItemOptions.None), - new SessionStateVariableEntry( - SpecialVariables.PSSessionConfigurationName, - "http://schemas.microsoft.com/powershell/Microsoft.PowerShell", - RemotingErrorIdStrings.PSSessionConfigurationName, - ScopedItemOptions.None), - new SessionStateVariableEntry( - SpecialVariables.PSSessionApplicationName, - "wsman", - RemotingErrorIdStrings.PSSessionAppName, - ScopedItemOptions.None), - // End: Variables which control remoting behavior - - #region Platform - new SessionStateVariableEntry( - SpecialVariables.IsLinux, - Platform.IsLinux, - string.Empty, - ScopedItemOptions.ReadOnly | ScopedItemOptions.AllScope), - - new SessionStateVariableEntry( - SpecialVariables.IsMacOS, - Platform.IsMacOS, - string.Empty, - ScopedItemOptions.ReadOnly | ScopedItemOptions.AllScope), - - new SessionStateVariableEntry( - SpecialVariables.IsWindows, - Platform.IsWindows, - string.Empty, - ScopedItemOptions.ReadOnly | ScopedItemOptions.AllScope), - - new SessionStateVariableEntry( - SpecialVariables.IsCoreCLR, - Platform.IsCoreCLR, - string.Empty, - ScopedItemOptions.ReadOnly | ScopedItemOptions.AllScope), - #endregion - }; + static InitialSessionState() + { + var builtinVariables = new List() + { + // Engine variables that should be precreated before running profile + // Bug fix for Win7:2202228 Engine halts if initial command fulls up variable table + // Anytime a new variable that the engine depends on to run is added, this table + // must be updated... + new SessionStateVariableEntry(SpecialVariables.LastToken, null, string.Empty), + new SessionStateVariableEntry(SpecialVariables.FirstToken, null, string.Empty), + new SessionStateVariableEntry(SpecialVariables.StackTrace, null, string.Empty), + + // Variable which controls the output rendering + new SessionStateVariableEntry( + SpecialVariables.PSStyle, + PSStyle.Instance, + RunspaceInit.PSStyleDescription, + ScopedItemOptions.Constant), + + // Variable which controls the encoding for piping data to a NativeCommand + new SessionStateVariableEntry( + SpecialVariables.OutputEncoding, + Encoding.Default, + RunspaceInit.OutputEncodingDescription, + ScopedItemOptions.None, + new ArgumentTypeConverterAttribute(typeof(System.Text.Encoding))), + + // Preferences + // + // NTRAID#Windows Out Of Band Releases-931461-2006/03/13 + // ArgumentTypeConverterAttribute is applied to these variables, + // but this only reaches the global variable. If these are + // redefined in script scope etc, the type conversion + // is not applicable. + // + // Variables typed to ActionPreference + new SessionStateVariableEntry( + SpecialVariables.ConfirmPreference, + DefaultConfirmPreference, + RunspaceInit.ConfirmPreferenceDescription, + ScopedItemOptions.None, + new ArgumentTypeConverterAttribute(typeof(ConfirmImpact))), + new SessionStateVariableEntry( + SpecialVariables.DebugPreference, + DefaultDebugPreference, + RunspaceInit.DebugPreferenceDescription, + ScopedItemOptions.None, + new ArgumentTypeConverterAttribute(typeof(ActionPreference))), + new SessionStateVariableEntry( + SpecialVariables.ErrorActionPreference, + DefaultErrorActionPreference, + RunspaceInit.ErrorActionPreferenceDescription, + ScopedItemOptions.None, + new ArgumentTypeConverterAttribute(typeof(ActionPreference))), + new SessionStateVariableEntry( + SpecialVariables.ProgressPreference, + DefaultProgressPreference, + RunspaceInit.ProgressPreferenceDescription, + ScopedItemOptions.None, + new ArgumentTypeConverterAttribute(typeof(ActionPreference))), + new SessionStateVariableEntry( + SpecialVariables.VerbosePreference, + DefaultVerbosePreference, + RunspaceInit.VerbosePreferenceDescription, + ScopedItemOptions.None, + new ArgumentTypeConverterAttribute(typeof(ActionPreference))), + new SessionStateVariableEntry( + SpecialVariables.WarningPreference, + DefaultWarningPreference, + RunspaceInit.WarningPreferenceDescription, + ScopedItemOptions.None, + new ArgumentTypeConverterAttribute(typeof(ActionPreference))), + new SessionStateVariableEntry( + SpecialVariables.InformationPreference, + DefaultInformationPreference, + RunspaceInit.InformationPreferenceDescription, + ScopedItemOptions.None, + new ArgumentTypeConverterAttribute(typeof(ActionPreference))), + new SessionStateVariableEntry( + SpecialVariables.ErrorView, + DefaultErrorView, + RunspaceInit.ErrorViewDescription, + ScopedItemOptions.None, + new ArgumentTypeConverterAttribute(typeof(ErrorView))), + new SessionStateVariableEntry( + SpecialVariables.NestedPromptLevel, + 0, + RunspaceInit.NestedPromptLevelDescription), + new SessionStateVariableEntry( + SpecialVariables.WhatIfPreference, + DefaultWhatIfPreference, + RunspaceInit.WhatIfPreferenceDescription), + new SessionStateVariableEntry( + FormatEnumerationLimit, + DefaultFormatEnumerationLimit, + RunspaceInit.FormatEnumerationLimitDescription), + + // variable for PSEmailServer + new SessionStateVariableEntry( + SpecialVariables.PSEmailServer, + string.Empty, + RunspaceInit.PSEmailServerDescription), + + // Start: Variables which control remoting behavior + new SessionStateVariableEntry( + Microsoft.PowerShell.Commands.PSRemotingBaseCmdlet.DEFAULT_SESSION_OPTION, + new System.Management.Automation.Remoting.PSSessionOption(), + RemotingErrorIdStrings.PSDefaultSessionOptionDescription, + ScopedItemOptions.None), + new SessionStateVariableEntry( + SpecialVariables.PSSessionConfigurationName, + "http://schemas.microsoft.com/powershell/Microsoft.PowerShell", + RemotingErrorIdStrings.PSSessionConfigurationName, + ScopedItemOptions.None), + new SessionStateVariableEntry( + SpecialVariables.PSSessionApplicationName, + "wsman", + RemotingErrorIdStrings.PSSessionAppName, + ScopedItemOptions.None), + // End: Variables which control remoting behavior + + #region Platform + new SessionStateVariableEntry( + SpecialVariables.IsLinux, + Platform.IsLinux, + string.Empty, + ScopedItemOptions.ReadOnly | ScopedItemOptions.AllScope), + + new SessionStateVariableEntry( + SpecialVariables.IsMacOS, + Platform.IsMacOS, + string.Empty, + ScopedItemOptions.ReadOnly | ScopedItemOptions.AllScope), + + new SessionStateVariableEntry( + SpecialVariables.IsWindows, + Platform.IsWindows, + string.Empty, + ScopedItemOptions.ReadOnly | ScopedItemOptions.AllScope), + + new SessionStateVariableEntry( + SpecialVariables.IsCoreCLR, + Platform.IsCoreCLR, + string.Empty, + ScopedItemOptions.ReadOnly | ScopedItemOptions.AllScope), + #endregion + }; + + builtinVariables.Add( + new SessionStateVariableEntry( + SpecialVariables.PSNativeCommandUseErrorActionPreference, + value: false, + RunspaceInit.PSNativeCommandUseErrorActionPreferenceDescription, + ScopedItemOptions.None, + new ArgumentTypeConverterAttribute(typeof(bool)))); + + builtinVariables.Add( + new SessionStateVariableEntry( + SpecialVariables.NativeArgumentPassing, + GetPassingStyle(), + RunspaceInit.NativeCommandArgumentPassingDescription, + ScopedItemOptions.None, + new ArgumentTypeConverterAttribute(typeof(NativeArgumentPassingStyle)))); + + BuiltInVariables = builtinVariables.ToArray(); + } /// - /// Returns a new array of alias entries everytime it's called. This + /// Assigns the default behavior for native argument passing. + /// If the system is non-Windows, we will return Standard. + /// Otherwise, we will return Windows. + /// + private static NativeArgumentPassingStyle GetPassingStyle() + { +#if UNIX + return NativeArgumentPassingStyle.Standard; +#else + return NativeArgumentPassingStyle.Windows; +#endif + } + + internal static readonly SessionStateVariableEntry[] BuiltInVariables; + + /// + /// Returns a new array of alias entries every time it's called. This /// can't be static because the elements may be mutated in different session /// state objects so each session state must have a copy of the entry. /// @@ -4597,7 +4702,7 @@ internal static SessionStateAliasEntry[] BuiltInAliases const ScopedItemOptions ReadOnly_AllScope = ScopedItemOptions.ReadOnly | ScopedItemOptions.AllScope; const ScopedItemOptions ReadOnly = ScopedItemOptions.ReadOnly; - return new SessionStateAliasEntry[] { + var builtInAliases = new List { new SessionStateAliasEntry("foreach", "ForEach-Object", string.Empty, ReadOnly_AllScope), new SessionStateAliasEntry("%", "ForEach-Object", string.Empty, ReadOnly_AllScope), new SessionStateAliasEntry("where", "Where-Object", string.Empty, ReadOnly_AllScope), @@ -4628,7 +4733,7 @@ internal static SessionStateAliasEntry[] BuiltInAliases new SessionStateAliasEntry("gm", "Get-Member", string.Empty, ReadOnly), new SessionStateAliasEntry("gmo", "Get-Module", string.Empty, ReadOnly), new SessionStateAliasEntry("gp", "Get-ItemProperty", string.Empty, ReadOnly), - new SessionStateAliasEntry("gpv", "Get-ItemPropertyValue", string.Empty,ReadOnly), + new SessionStateAliasEntry("gpv", "Get-ItemPropertyValue", string.Empty, ReadOnly), new SessionStateAliasEntry("gps", "Get-Process", string.Empty, ReadOnly), new SessionStateAliasEntry("group", "Group-Object", string.Empty, ReadOnly), new SessionStateAliasEntry("gu", "Get-Unique", string.Empty, ReadOnly), @@ -4764,6 +4869,8 @@ internal static SessionStateAliasEntry[] BuiltInAliases // - do not use AllScope - this causes errors in profiles that set this somewhat commonly used alias. new SessionStateAliasEntry("sls", "Select-String"), }; + + return builtInAliases.ToArray(); } } @@ -4785,12 +4892,17 @@ internal static SessionStateAliasEntry[] BuiltInAliases // Functions that don't require full language mode SessionStateFunctionEntry.GetDelayParsedFunctionEntry("cd..", "Set-Location ..", isProductCode: true, languageMode: systemLanguageMode), SessionStateFunctionEntry.GetDelayParsedFunctionEntry("cd\\", "Set-Location \\", isProductCode: true, languageMode: systemLanguageMode), - // Win8: 320909. Retaining the original definition to ensure backward compatability. + SessionStateFunctionEntry.GetDelayParsedFunctionEntry("cd~", "Set-Location ~", isProductCode: true, languageMode: systemLanguageMode), + // Win8: 320909. Retaining the original definition to ensure backward compatibility. SessionStateFunctionEntry.GetDelayParsedFunctionEntry("Pause", - string.Concat("$null = Read-Host '", CodeGeneration.EscapeSingleQuotedStringContent(RunspaceInit.PauseDefinitionString),"'"), isProductCode: true, languageMode: systemLanguageMode), + string.Concat("$null = Read-Host '", CodeGeneration.EscapeSingleQuotedStringContent(RunspaceInit.PauseDefinitionString), "'"), isProductCode: true, languageMode: systemLanguageMode), SessionStateFunctionEntry.GetDelayParsedFunctionEntry("help", GetHelpPagingFunctionText(), isProductCode: true, languageMode: systemLanguageMode), SessionStateFunctionEntry.GetDelayParsedFunctionEntry("prompt", DefaultPromptFunctionText, isProductCode: true, languageMode: systemLanguageMode), +#if UNIX + SessionStateFunctionEntry.GetDelayParsedFunctionEntry("exec", GetExecFunctionText(), isProductCode: true, languageMode: systemLanguageMode), +#endif + // Functions that require full language mode and are trusted SessionStateFunctionEntry.GetDelayParsedFunctionEntry("Clear-Host", GetClearHostFunctionText(), isProductCode: true, languageMode: PSLanguageMode.FullLanguage), SessionStateFunctionEntry.GetDelayParsedFunctionEntry("TabExpansion2", s_tabExpansionFunctionText, isProductCode: true, languageMode: PSLanguageMode.FullLanguage), @@ -4849,7 +4961,6 @@ internal static void RemoveAllDrivesForProvider(ProviderInfo pi, SessionStateInt internal static readonly string CoreSnapin = "Microsoft.PowerShell.Core"; internal static readonly string CoreModule = "Microsoft.PowerShell.Core"; - internal Collection defaultSnapins = new Collection(); // The list of engine modules to create warnings when you try to remove them internal static readonly HashSet EngineModules = new HashSet(StringComparer.OrdinalIgnoreCase) @@ -4998,10 +5109,8 @@ internal static void AnalyzePSSnapInAssembly( out string helpFile) { helpFile = null; - if (assembly == null) - { - throw new ArgumentNullException(nameof(assembly)); - } + + ArgumentNullException.ThrowIfNull(assembly); cmdlets = null; aliases = null; @@ -5262,7 +5371,11 @@ private static void AnalyzeModuleAssemblyWithReflection( // the users of the cmdlet, instead of the author, should have control of what options applied to an alias // ('ScopedItemOptions.ReadOnly' and/or 'ScopedItemOptions.AllScopes'). var aliasEntry = new SessionStateAliasEntry(alias, cmdletName, description: string.Empty, ScopedItemOptions.None); - if (psSnapInInfo != null) { aliasEntry.SetPSSnapIn(psSnapInInfo); } + + if (psSnapInInfo != null) + { + aliasEntry.SetPSSnapIn(psSnapInInfo); + } if (moduleInfo != null) { @@ -5333,7 +5446,6 @@ private static void InitializeCoreCmdletsAndProviders( { "Enable-PSSessionConfiguration", new SessionStateCmdletEntry("Enable-PSSessionConfiguration", typeof(EnablePSSessionConfigurationCommand), helpFile) }, { "Get-PSSessionCapability", new SessionStateCmdletEntry("Get-PSSessionCapability", typeof(GetPSSessionCapabilityCommand), helpFile) }, { "Get-PSSessionConfiguration", new SessionStateCmdletEntry("Get-PSSessionConfiguration", typeof(GetPSSessionConfigurationCommand), helpFile) }, - { "New-PSSessionConfigurationFile", new SessionStateCmdletEntry("New-PSSessionConfigurationFile", typeof(NewPSSessionConfigurationFileCommand), helpFile) }, { "Receive-PSSession", new SessionStateCmdletEntry("Receive-PSSession", typeof(ReceivePSSessionCommand), helpFile) }, { "Register-PSSessionConfiguration", new SessionStateCmdletEntry("Register-PSSessionConfiguration", typeof(RegisterPSSessionConfigurationCommand), helpFile) }, { "Unregister-PSSessionConfiguration", new SessionStateCmdletEntry("Unregister-PSSessionConfiguration", typeof(UnregisterPSSessionConfigurationCommand), helpFile) }, @@ -5365,6 +5477,7 @@ private static void InitializeCoreCmdletsAndProviders( { "New-ModuleManifest", new SessionStateCmdletEntry("New-ModuleManifest", typeof(NewModuleManifestCommand), helpFile) }, { "New-PSRoleCapabilityFile", new SessionStateCmdletEntry("New-PSRoleCapabilityFile", typeof(NewPSRoleCapabilityFileCommand), helpFile) }, { "New-PSSession", new SessionStateCmdletEntry("New-PSSession", typeof(NewPSSessionCommand), helpFile) }, + { "New-PSSessionConfigurationFile", new SessionStateCmdletEntry("New-PSSessionConfigurationFile", typeof(NewPSSessionConfigurationFileCommand), helpFile) }, { "New-PSSessionOption", new SessionStateCmdletEntry("New-PSSessionOption", typeof(NewPSSessionOptionCommand), helpFile) }, { "New-PSTransportOption", new SessionStateCmdletEntry("New-PSTransportOption", typeof(NewPSTransportOptionCommand), helpFile) }, { "Out-Default", new SessionStateCmdletEntry("Out-Default", typeof(OutDefaultCommand), helpFile) }, @@ -5402,6 +5515,10 @@ private static void InitializeCoreCmdletsAndProviders( 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 + foreach (var val in cmdlets.Values) { val.SetPSSnapIn(psSnapInInfo); @@ -5443,7 +5560,7 @@ internal static IEnumerable GetAssemblyTypes(Assembly assembly, string nam try { // Return types that are public, non-abstract, non-interface and non-valueType. - return assembly.ExportedTypes.Where(t => !t.IsAbstract && !t.IsInterface && !t.IsValueType); + return assembly.ExportedTypes.Where(static t => !t.IsAbstract && !t.IsInterface && !t.IsValueType); } catch (ReflectionTypeLoadException e) { diff --git a/src/System.Management.Automation/engine/InternalCommands.cs b/src/System.Management.Automation/engine/InternalCommands.cs index b7b1d87cc82..39556db9cca 100644 --- a/src/System.Management.Automation/engine/InternalCommands.cs +++ b/src/System.Management.Automation/engine/InternalCommands.cs @@ -6,17 +6,19 @@ using System.Collections.Generic; using System.Dynamic; using System.Globalization; -using System.Linq; using System.Linq.Expressions; using System.Management.Automation; using System.Management.Automation.Internal; using System.Management.Automation.Language; using System.Management.Automation.PSTasks; +using System.Management.Automation.Security; using System.Runtime.CompilerServices; using System.Text; using System.Threading; + using CommonParamSet = System.Management.Automation.Internal.CommonParameters; using Dbg = System.Management.Automation.Diagnostics; +using NotNullWhen = System.Diagnostics.CodeAnalysis.NotNullWhenAttribute; namespace Microsoft.PowerShell.Commands { @@ -381,24 +383,14 @@ public void Dispose() private Exception _taskCollectionException; private string _currentLocationPath; - // List of Foreach-Object command names and aliases. - // TODO: Look into using SessionState.Internal.GetAliasTable() to find all user created aliases. - // But update Alias command logic to maintain reverse table that lists all aliases mapping - // to a single command definition, for performance. - private static string[] forEachNames = new string[] - { - "ForEach-Object", - "foreach", - "%" - }; - private void InitParallelParameterSet() { // The following common parameters are not (yet) supported in this parameter set. - // ErrorAction, WarningAction, InformationAction, PipelineVariable. + // ErrorAction, WarningAction, InformationAction, ProgressAction, PipelineVariable. if (MyInvocation.BoundParameters.ContainsKey(nameof(CommonParamSet.ErrorAction)) || MyInvocation.BoundParameters.ContainsKey(nameof(CommonParamSet.WarningAction)) || MyInvocation.BoundParameters.ContainsKey(nameof(CommonParamSet.InformationAction)) || + MyInvocation.BoundParameters.ContainsKey(nameof(CommonParamSet.ProgressAction)) || MyInvocation.BoundParameters.ContainsKey(nameof(CommonParamSet.PipelineVariable))) { ThrowTerminatingError( @@ -422,15 +414,14 @@ private void InitParallelParameterSet() _usingValuesMap = ScriptBlockToPowerShellConverter.GetUsingValuesForEachParallel( scriptBlock: Parallel, isTrustedInput: allowUsingExpression, - context: this.Context, - foreachNames: forEachNames); + context: this.Context); // Validate using values map, which is a map of '$using:' variables referenced in the script. // Script block variables are not allowed since their behavior is undefined outside the runspace // in which they were created. foreach (object item in _usingValuesMap.Values) { - if (item is ScriptBlock) + if (item is ScriptBlock or PSObject { BaseObject: ScriptBlock }) { ThrowTerminatingError( new ErrorRecord( @@ -695,7 +686,10 @@ private void ProcessPropertyAndMethodParameterSet() else { // if inputObject is of IDictionary, get the value - if (GetValueFromIDictionaryInput()) { return; } + if (GetValueFromIDictionaryInput()) + { + return; + } PSMemberInfo member = null; if (WildcardPattern.ContainsWildcardCharacters(_propertyOrMethodName)) @@ -711,7 +705,7 @@ private void ProcessPropertyAndMethodParameterSet() StringBuilder possibleMatches = new StringBuilder(); foreach (PSMemberInfo item in members) { - possibleMatches.AppendFormat(CultureInfo.InvariantCulture, " {0}", item.Name); + possibleMatches.Append(CultureInfo.InvariantCulture, $" {item.Name}"); } WriteError(GenerateNameParameterError("Name", InternalCommandStrings.AmbiguousPropertyOrMethodName, @@ -930,17 +924,14 @@ private void ProcessScriptBlockParameterSet() // because it allows you to parameterize a command - for example you might allow // for actions before and after the main processing script. They could be null // by default and therefore ignored then filled in later... - if (_scripts[i] != null) - { - _scripts[i].InvokeUsingCmdlet( - contextCmdlet: this, - useLocalScope: false, - errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, - dollarUnder: InputObject, - input: new object[] { InputObject }, - scriptThis: AutomationNull.Value, - args: Array.Empty()); - } + _scripts[i]?.InvokeUsingCmdlet( + contextCmdlet: this, + useLocalScope: false, + errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, + dollarUnder: InputObject, + input: new object[] { InputObject }, + scriptThis: AutomationNull.Value, + args: Array.Empty()); } } @@ -1032,7 +1023,7 @@ private void MethodCallWithArguments() StringBuilder possibleMatches = new StringBuilder(); foreach (PSMemberInfo item in methods) { - possibleMatches.AppendFormat(CultureInfo.InvariantCulture, " {0}", item.Name); + possibleMatches.Append(CultureInfo.InvariantCulture, $" {item.Name}"); } WriteError(GenerateNameParameterError( @@ -1062,7 +1053,7 @@ private void MethodCallWithArguments() StringBuilder arglist = new StringBuilder(GetStringRepresentation(_arguments[0])); for (int i = 1; i < _arguments.Length; i++) { - arglist.AppendFormat(CultureInfo.InvariantCulture, ", {0}", GetStringRepresentation(_arguments[i])); + arglist.Append(CultureInfo.InvariantCulture, $", {GetStringRepresentation(_arguments[i])}"); } string methodAction = string.Format(CultureInfo.InvariantCulture, @@ -1215,14 +1206,25 @@ private bool BlockMethodInLanguageMode(object inputObject) if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage) { object baseObject = PSObject.Base(inputObject); + var objectType = baseObject.GetType(); - if (!CoreTypes.Contains(baseObject.GetType())) + if (!CoreTypes.Contains(objectType)) { - PSInvalidOperationException exception = - new PSInvalidOperationException(ParserStrings.InvokeMethodConstrainedLanguage); + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + PSInvalidOperationException exception = + new PSInvalidOperationException(ParserStrings.InvokeMethodConstrainedLanguage); - WriteError(new ErrorRecord(exception, "MethodInvocationNotSupportedInConstrainedLanguage", ErrorCategory.InvalidOperation, null)); - return true; + WriteError(new ErrorRecord(exception, "MethodInvocationNotSupportedInConstrainedLanguage", ErrorCategory.InvalidOperation, null)); + return true; + } + + SystemPolicy.LogWDACAuditMessage( + context: Context, + title: InternalCommandStrings.WDACLogTitle, + message: StringUtil.Format(InternalCommandStrings.WDACLogMessage, objectType.FullName), + fqid: "ForEachObjectCmdletMethodInvocationNotAllowed", + dropIntoDebugger: true); } } @@ -1550,7 +1552,7 @@ public SwitchParameter LT } /// - /// Gets -sets case sensitive binary operator -clt. + /// Gets or sets case sensitive binary operator -clt. /// [Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveLessThanSet")] public SwitchParameter CLT @@ -2368,7 +2370,7 @@ private object GetValue(ref bool error) StringBuilder possibleMatches = new StringBuilder(); foreach (PSMemberInfo item in members) { - possibleMatches.AppendFormat(CultureInfo.InvariantCulture, " {0}", item.Name); + possibleMatches.Append(CultureInfo.InvariantCulture, $" {item.Name}"); } WriteError( @@ -2647,46 +2649,19 @@ public SwitchParameter Off private SwitchParameter _off; /// - /// To make it easier to specify a version, we add some conversions that wouldn't happen otherwise: - /// * A simple integer, i.e. 2 - /// * A string without a dot, i.e. "2" - /// * The string 'latest', which we interpret to be the current version of PowerShell. + /// Handle 'latest', which we interpret to be the current version of PowerShell. /// - private sealed class ArgumentToVersionTransformationAttribute : ArgumentTransformationAttribute + private sealed class ArgumentToPSVersionTransformationAttribute : ArgumentToVersionTransformationAttribute { - public override object Transform(EngineIntrinsics engineIntrinsics, object inputData) + protected override bool TryConvertFromString(string versionString, [NotNullWhen(true)] out Version version) { - object version = PSObject.Base(inputData); - - string versionStr = version as string; - if (versionStr != null) + if (string.Equals("latest", versionString, StringComparison.OrdinalIgnoreCase)) { - if (versionStr.Equals("latest", StringComparison.OrdinalIgnoreCase)) - { - return PSVersionInfo.PSVersion; - } - - if (versionStr.Contains('.')) - { - // If the string contains a '.', let the Version constructor handle the conversion. - return inputData; - } - } - - if (version is double) - { - // The conversion to int below is wrong, but the usual conversions will turn - // the double into a string, so just return the original object. - return inputData; - } - - int majorVersion; - if (LanguagePrimitives.TryConvertTo(version, out majorVersion)) - { - return new Version(majorVersion, 0); + version = PSVersionInfo.PSVersion; + return true; } - return inputData; + return base.TryConvertFromString(versionString, out version); } } @@ -2695,7 +2670,7 @@ private sealed class ValidateVersionAttribute : ValidateArgumentsAttribute protected override void Validate(object arguments, EngineIntrinsics engineIntrinsics) { Version version = arguments as Version; - if (version == null || !PSVersionInfo.IsValidPSVersion(version)) + if (!PSVersionInfo.IsValidPSVersion(version)) { // No conversion succeeded so throw and exception... throw new ValidationMetadataException( @@ -2711,7 +2686,8 @@ protected override void Validate(object arguments, EngineIntrinsics engineIntrin /// Gets or sets strict mode in the current scope. /// [Parameter(ParameterSetName = "Version", Mandatory = true)] - [ArgumentToVersionTransformation] + [ArgumentCompleter(typeof(StrictModeVersionArgumentCompleter))] + [ArgumentToPSVersionTransformation] [ValidateVersion] [Alias("v")] public Version Version @@ -2742,6 +2718,34 @@ protected override void EndProcessing() Context.EngineSessionState.CurrentScope.StrictModeVersion = _version; } } + + /// + /// Provides argument completion for StrictMode Version parameter. + /// + public class StrictModeVersionArgumentCompleter : IArgumentCompleter + { + private static readonly string[] s_strictModeVersions = new string[] { "Latest", "3.0", "2.0", "1.0" }; + + /// + /// Returns completion results for version parameter. + /// + /// The command name. + /// The parameter name. + /// The word to complete. + /// The command AST. + /// The fake bound parameters. + /// List of Completion Results. + public IEnumerable CompleteArgument( + string commandName, + string parameterName, + string wordToComplete, + CommandAst commandAst, + IDictionary fakeBoundParameters) + => CompletionHelpers.GetMatchingResults( + wordToComplete, + possibleCompletionValues: s_strictModeVersions); + } + #endregion Set-StrictMode #endregion Built-in cmdlets that are used by or require direct access to the engine. diff --git a/src/System.Management.Automation/engine/Interop/Windows/AllocConsole.cs b/src/System.Management.Automation/engine/Interop/Windows/AllocConsole.cs new file mode 100644 index 00000000000..072e5d1b18e --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/AllocConsole.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("kernel32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool AllocConsole(); + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/AssignProcessToJobObject.cs b/src/System.Management.Automation/engine/Interop/Windows/AssignProcessToJobObject.cs new file mode 100644 index 00000000000..7605420dab4 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/AssignProcessToJobObject.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#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)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool AssignProcessToJobObject( + SafeJobHandle hJob, + SafeProcessHandle hProcess); + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/CloseHandle.cs b/src/System.Management.Automation/engine/Interop/Windows/CloseHandle.cs new file mode 100644 index 00000000000..6832268272a --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/CloseHandle.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 partial class Windows + { + [LibraryImport("api-ms-win-core-handle-l1-1-0.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool CloseHandle(nint hObject); + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/CoInitializeEx.cs b/src/System.Management.Automation/engine/Interop/Windows/CoInitializeEx.cs new file mode 100644 index 00000000000..a3eae4fd596 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/CoInitializeEx.cs @@ -0,0 +1,18 @@ +// 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 const int COINIT_APARTMENTTHREADED = 0x2; + internal const int E_NOTIMPL = unchecked((int)0X80004001); + + [LibraryImport("api-ms-win-core-com-l1-1-0.dll")] + internal static partial int CoInitializeEx(nint reserve, int coinit); + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/CoUninitialize.cs b/src/System.Management.Automation/engine/Interop/Windows/CoUninitialize.cs new file mode 100644 index 00000000000..d872cf17f3e --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/CoUninitialize.cs @@ -0,0 +1,15 @@ +// 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 + { + [LibraryImport("api-ms-win-core-com-l1-1-0.dll")] + internal static partial void CoUninitialize(); + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/CreateFile.cs b/src/System.Management.Automation/engine/Interop/Windows/CreateFile.cs new file mode 100644 index 00000000000..fa1552c91c5 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/CreateFile.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.IO; +using System.Management.Automation; +using System.Runtime.InteropServices; + +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static unsafe partial class Windows + { + // dwDesiredAccess of CreateFile + [Flags] + internal enum FileDesiredAccess : uint + { + GenericZero = 0, + GenericRead = 0x80000000, + GenericWrite = 0x40000000, + GenericExecute = 0x20000000, + GenericAll = 0x10000000, + } + + // dwFlagsAndAttributes + [Flags] + internal enum FileAttributes : uint + { + Readonly = 0x00000001, + Hidden = 0x00000002, + System = 0x00000004, + Archive = 0x00000020, + Encrypted = 0x00004000, + Write_Through = 0x80000000, + Overlapped = 0x40000000, + NoBuffering = 0x20000000, + RandomAccess = 0x10000000, + SequentialScan = 0x08000000, + DeleteOnClose = 0x04000000, + BackupSemantics = 0x02000000, + PosixSemantics = 0x01000000, + OpenReparsePoint = 0x00200000, + OpenNoRecall = 0x00100000, + SessionAware = 0x00800000, + Normal = 0x00000080 + } + + // WARNING: This method does not implicitly handle long paths. Use CreateFile. + [LibraryImport("api-ms-win-core-file-l1-1-0.dll", EntryPoint = "CreateFileW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + private static unsafe partial SafeFileHandle CreateFilePrivate( + string lpFileName, + uint dwDesiredAccess, + FileShare dwShareMode, + nint lpSecurityAttributes, + FileMode dwCreationDisposition, + FileAttributes dwFlagsAndAttributes, + IntPtr hTemplateFile); + + [LibraryImport("api-ms-win-core-file-l1-1-0.dll", EntryPoint = "CreateFileW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + private static unsafe partial nint CreateFileWithPipeHandlePrivate( + string lpFileName, + uint dwDesiredAccess, + FileShare dwShareMode, + nint lpSecurityAttributes, + FileMode dwCreationDisposition, + FileAttributes dwFlagsAndAttributes, + IntPtr hTemplateFile); + + internal static unsafe SafeFileHandle CreateFileWithSafeFileHandle( + string lpFileName, + FileAccess dwDesiredAccess, + FileShare dwShareMode, + FileMode dwCreationDisposition, + FileAttributes dwFlagsAndAttributes) + { + lpFileName = Path.TrimEndingDirectorySeparator(lpFileName); + lpFileName = PathUtils.EnsureExtendedPrefixIfNeeded(lpFileName); + + return CreateFilePrivate(lpFileName, (uint)dwDesiredAccess, dwShareMode, nint.Zero, dwCreationDisposition, dwFlagsAndAttributes, nint.Zero); + } + + internal static unsafe nint CreateFileWithPipeHandle( + string lpFileName, + FileAccess dwDesiredAccess, + FileShare dwShareMode, + FileMode dwCreationDisposition, + FileAttributes dwFlagsAndAttributes) + { + return CreateFileWithPipeHandlePrivate(lpFileName, (uint)dwDesiredAccess, dwShareMode, nint.Zero, dwCreationDisposition, dwFlagsAndAttributes, nint.Zero); + } + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/CreateHardLink.cs b/src/System.Management.Automation/engine/Interop/Windows/CreateHardLink.cs new file mode 100644 index 00000000000..f27f095fc1a --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/CreateHardLink.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("api-ms-win-core-file-l2-1-0.dll", EntryPoint = "CreateHardLinkW", StringMarshalling = StringMarshalling.Utf16, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool CreateHardLink(string name, string existingFileName, nint securityAttributes); + } +} 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/CreateSymbolicLink.cs b/src/System.Management.Automation/engine/Interop/Windows/CreateSymbolicLink.cs new file mode 100644 index 00000000000..c6519f94ec4 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/CreateSymbolicLink.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static unsafe partial class Windows + { + [Flags] + internal enum SymbolicLinkFlags + { + File = 0, + Directory = 1, + AllowUnprivilegedCreate = 2, + } + + [LibraryImport("api-ms-win-core-file-l2-1-0.dll", EntryPoint = "CreateSymbolicLinkW", StringMarshalling = StringMarshalling.Utf16, SetLastError = true)] + [return: MarshalAs(UnmanagedType.I1)] + internal static partial bool CreateSymbolicLink(string name, string destination, SymbolicLinkFlags symbolicLinkFlags); + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/Errors.cs b/src/System.Management.Automation/engine/Interop/Windows/Errors.cs new file mode 100644 index 00000000000..bef9e172193 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/Errors.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +internal static partial class Interop +{ + internal static partial class Windows + { + // List of error constants https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes + internal const int ERROR_SUCCESS = 0; + internal const int ERROR_FILE_NOT_FOUND = 2; + internal const int ERROR_GEN_FAILURE = 31; + internal const int ERROR_NOT_SUPPORTED = 50; + internal const int ERROR_NO_NETWORK = 1222; + internal const int ERROR_MORE_DATA = 234; + internal const int ERROR_CONNECTION_UNAVAIL = 1201; + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/EventActivityIdControl.cs b/src/System.Management.Automation/engine/Interop/Windows/EventActivityIdControl.cs new file mode 100644 index 00000000000..8152a793149 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/EventActivityIdControl.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +#if !UNIX +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Windows + { + internal enum ActivityControl : uint + { + /// + /// Gets the ActivityId from thread local storage. + /// + Get = 1, + + /// + /// Sets the ActivityId in the thread local storage. + /// + Set = 2, + + /// + /// Creates a new activity id. + /// + Create = 3, + + /// + /// Sets the activity id in thread local storage and returns the previous value. + /// + GetSet = 4, + + /// + /// Creates a new activity id, sets thread local storage, and returns the previous value. + /// + CreateSet = 5 + } + + [LibraryImport("api-ms-win-eventing-provider-l1-1-0.dll")] + internal static unsafe partial int EventActivityIdControl(ActivityControl controlCode, Guid* activityId); + + internal static unsafe int GetEventActivityIdControl(ref Guid activityId) + { + fixed (Guid* guidPtr = &activityId) + { + return EventActivityIdControl(ActivityControl.Get, guidPtr); + } + } + } +} +#endif diff --git a/src/System.Management.Automation/engine/Interop/Windows/FindClose.cs b/src/System.Management.Automation/engine/Interop/Windows/FindClose.cs new file mode 100644 index 00000000000..a5903c72c70 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/FindClose.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("api-ms-win-core-file-l1-1-0.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool FindClose(nint handle); + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/FindExecutable.cs b/src/System.Management.Automation/engine/Interop/Windows/FindExecutable.cs new file mode 100644 index 00000000000..2184e57a519 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/FindExecutable.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +#if !UNIX +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static unsafe partial class Windows + { + // The FindExecutable API is defined in shellapi.h as + // SHSTDAPI_(HINSTANCE) FindExecutableW(LPCWSTR lpFile, LPCWSTR lpDirectory, __out_ecount(MAX_PATH) LPWSTR lpResult); + // HINSTANCE is void* so we need to use IntPtr (nint) as API return value. + [LibraryImport("shell32.dll", EntryPoint = "FindExecutableW", StringMarshalling = StringMarshalling.Utf16)] + internal static partial nint FindExecutableW(string fileName, string directoryPath, char* pathFound); + + internal static string? FindExecutable(string filename) + { + string? result = null; + + // HINSTANCE == PVOID == nint + nint resultCode = 0; + + Span buffer = stackalloc char[MAX_PATH]; + unsafe + { + fixed (char* lpBuffer = buffer) + { + resultCode = FindExecutableW(filename, string.Empty, lpBuffer); + + // If FindExecutable returns a result > 32, then it succeeded + // and we return the string that was found, otherwise we + // return null. + if (resultCode > 32) + { + result = Marshal.PtrToStringUni((IntPtr)lpBuffer); + return result; + } + } + } + + return null; + } + } +} +#endif diff --git a/src/System.Management.Automation/engine/Interop/Windows/FindFirstFile.cs b/src/System.Management.Automation/engine/Interop/Windows/FindFirstFile.cs new file mode 100644 index 00000000000..e53b0985cb6 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/FindFirstFile.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Management.Automation; +using System.Runtime.InteropServices; + +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Keep native struct names.")] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Keep native struct names.")] + internal static unsafe partial class Windows + { + internal const int MAX_PATH = 260; + + internal struct FILE_TIME + { + public uint dwLowDateTime; + public uint dwHighDateTime; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal unsafe struct WIN32_FIND_DATA + { + internal uint dwFileAttributes; + internal FILE_TIME ftCreationTime; + internal FILE_TIME ftLastAccessTime; + internal FILE_TIME ftLastWriteTime; + internal uint nFileSizeHigh; + internal uint nFileSizeLow; + internal uint dwReserved0; + internal uint dwReserved1; + internal fixed char cFileName[MAX_PATH]; + internal fixed char cAlternateFileName[14]; + } + + internal sealed class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid + { + // .NET 8 requires the default constructor to be public + public SafeFindHandle() : base(true) { } + + protected override bool ReleaseHandle() + { + return Interop.Windows.FindClose(this.handle); + } + } + + // We use 'FindFirstFileW' instead of 'FindFirstFileExW' because the latter doesn't work correctly with Unicode file names on FAT32. + // See https://github.com/PowerShell/PowerShell/issues/16804 + [LibraryImport("api-ms-win-core-file-l1-1-0.dll", EntryPoint = "FindFirstFileW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + private static partial SafeFindHandle FindFirstFileW(string lpFileName, ref WIN32_FIND_DATA lpFindFileData); + + internal static SafeFindHandle FindFirstFile(string lpFileName, ref WIN32_FIND_DATA lpFindFileData) + { + lpFileName = Path.TrimEndingDirectorySeparator(lpFileName); + lpFileName = PathUtils.EnsureExtendedPrefixIfNeeded(lpFileName); + + return FindFirstFileW(lpFileName, ref lpFindFileData); + } + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/GetConsoleWindow.cs b/src/System.Management.Automation/engine/Interop/Windows/GetConsoleWindow.cs new file mode 100644 index 00000000000..60d9229ea64 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/GetConsoleWindow.cs @@ -0,0 +1,15 @@ +// 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")] + internal static partial nint GetConsoleWindow(); + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/GetCurrentThreadId.cs b/src/System.Management.Automation/engine/Interop/Windows/GetCurrentThreadId.cs new file mode 100644 index 00000000000..80a0c7a4c43 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/GetCurrentThreadId.cs @@ -0,0 +1,15 @@ +// 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 + { + [LibraryImport("api-ms-win-core-processthreads-l1-1-0.dll")] + internal static partial uint GetCurrentThreadId(); + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/GetForegroundWindow.cs b/src/System.Management.Automation/engine/Interop/Windows/GetForegroundWindow.cs new file mode 100644 index 00000000000..2ba57535162 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/GetForegroundWindow.cs @@ -0,0 +1,15 @@ +// 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("user32.dll")] + internal static partial nint GetForegroundWindow(); + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/GetOEMCP.cs b/src/System.Management.Automation/engine/Interop/Windows/GetOEMCP.cs new file mode 100644 index 00000000000..4267deb1167 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/GetOEMCP.cs @@ -0,0 +1,15 @@ +// 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("api-ms-win-core-localization-l1-2-0.dll")] + internal static partial uint GetOEMCP(); + } +} 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/NetShareEnum.cs b/src/System.Management.Automation/engine/Interop/Windows/NetShareEnum.cs new file mode 100644 index 00000000000..7efad887f1a --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/NetShareEnum.cs @@ -0,0 +1,26 @@ +// 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 MAX_PREFERRED_LENGTH = -1; + internal const int STYPE_DISKTREE = 0; + internal const int STYPE_MASK = 0x000000FF; + + [LibraryImport("Netapi32.dll", StringMarshalling = StringMarshalling.Utf16)] + internal static partial int NetShareEnum( + string serverName, + int level, + out nint bufptr, + int prefMaxLen, + out uint entriesRead, + out uint totalEntries, + ref uint resumeHandle); + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/NtQueryInformationProcess.cs b/src/System.Management.Automation/engine/Interop/Windows/NtQueryInformationProcess.cs new file mode 100644 index 00000000000..1a839529737 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/NtQueryInformationProcess.cs @@ -0,0 +1,31 @@ +// 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 + { + [StructLayout(LayoutKind.Sequential)] + internal struct PROCESS_BASIC_INFORMATION + { + public nint ExitStatus; + public nint PebBaseAddress; + public nint AffinityMask; + public nint BasePriority; + public nint UniqueProcessId; + public nint InheritedFromUniqueProcessId; + } + + [LibraryImport("ntdll.dll")] + internal static partial int NtQueryInformationProcess( + nint processHandle, + int processInformationClass, + out PROCESS_BASIC_INFORMATION processInformation, + int processInformationLength, + out int returnLength); + } +} 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 new file mode 100644 index 00000000000..27907bd0135 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/QueryDosDevice.cs @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Buffers; +using System.ComponentModel; +using System.Management.Automation; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Windows + { + [LibraryImport(PinvokeDllNames.QueryDosDeviceDllName, EntryPoint = "QueryDosDeviceW", StringMarshalling = StringMarshalling.Utf16, SetLastError = true)] + internal static partial int QueryDosDevice(Span lpDeviceName, Span lpTargetPath, uint ucchMax); + + internal static string GetDosDeviceForNetworkPath(char deviceName) + { + // By default buffer size is set to 300 which would generally be sufficient in most of the cases. + const int StartLength = +#if DEBUG + // In debug, validate ArrayPool growth. + 1; +#else + 300; +#endif + + Span buffer = stackalloc char[StartLength + 1]; + Span fullDeviceName = stackalloc char[3] { deviceName, ':', '\0' }; + char[]? rentedArray = null; + + try + { + while (true) + { + uint length = (uint)buffer.Length; + int retValue = QueryDosDevice(fullDeviceName, buffer, length); + if (retValue > 0) + { + if (buffer.StartsWith("\\??\\")) + { + // QueryDosDevice always return array of NULL-terminating strings with additional final NULL + // so the buffer has always two NULL-s on end. + // + // "\\??\\UNC\\localhost\\c$\\tmp\0\0" -> "UNC\\localhost\\c$\\tmp\0\0" + Span res = buffer.Slice(4); + if (res.StartsWith("UNC")) + { + // -> "C\\localhost\\c$\\tmp\0\0" -> "\\\\localhost\\c$\\tmp" + // + // We need to take only first null-terminated string as QueryDosDevice() docs say. + int i = 3; + for (; i < res.Length; i++) + { + if (res[i] == '\0') + { + break; + } + } + + Diagnostics.Assert(i < res.Length, "Broken QueryDosDevice() buffer."); + + res = res.Slice(2, i); + res[0] = '\\'; + + // If we want always to have terminating slash -> "\\\\localhost\\c$\\tmp\\" + // res = res.Slice(2, retValue - 3); + // res[0] = '\\'; + // res[^1] = '\\'; + } + // else if (res[^3] == ':') + // { + // Diagnostics.Assert(false, "Really it is a dead code since GetDosDevice() is called only if PSDrive.DriveType == DriveType.Network"); + + // // The substed path is the root path of a drive. For example: subst Y: C:\ + // // -> "C:\0\0" -> "C:\" + // res = res.Slice(0, retValue - 1); + // res[^1] = '\\'; + // } + else + { + throw new Exception("GetDosDeviceForNetworkPath() can be called only if PSDrive.DriveType == DriveType.Network."); + } + + return res.ToString(); + } + else + { + Diagnostics.Assert(false, "Really it is a dead code since GetDosDevice() is called only if PSDrive.DriveType == DriveType.Network"); + + // The drive name is not a substed path, then we return the root path of the drive + // "C:\0" -> "C:\\" + fullDeviceName[^1] = '\\'; + return fullDeviceName.ToString(); + } + } + + const int ERROR_INSUFFICIENT_BUFFER = 122; + int errorCode = Marshal.GetLastPInvokeError(); + if (errorCode != ERROR_INSUFFICIENT_BUFFER) + { + throw new Win32Exception((int)errorCode); + } + + char[]? toReturn = rentedArray; + buffer = rentedArray = ArrayPool.Shared.Rent(buffer.Length * 2); + if (toReturn is not null) + { + ArrayPool.Shared.Return(toReturn); + } + } + } + finally + { + if (rentedArray is not null) + { + ArrayPool.Shared.Return(rentedArray); + } + } + } + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/RtlQueryProcessPlaceholderCompatibilityMode.cs b/src/System.Management.Automation/engine/Interop/Windows/RtlQueryProcessPlaceholderCompatibilityMode.cs new file mode 100644 index 00000000000..9e82f2c66c1 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/RtlQueryProcessPlaceholderCompatibilityMode.cs @@ -0,0 +1,25 @@ +// 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 sbyte PHCM_APPLICATION_DEFAULT = 0; + internal const sbyte PHCM_DISGUISE_PLACEHOLDER = 1; + internal const sbyte PHCM_EXPOSE_PLACEHOLDERS = 2; + internal const sbyte PHCM_MAX = 2; + internal const sbyte PHCM_ERROR_INVALID_PARAMETER = -1; + internal const sbyte PHCM_ERROR_NO_TEB = -2; + + [LibraryImport("ntdll.dll")] + internal static partial sbyte RtlQueryProcessPlaceholderCompatibilityMode(); + + [LibraryImport("ntdll.dll")] + internal static partial sbyte RtlSetProcessPlaceholderCompatibilityMode(sbyte pcm); + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/SHGetFileInfo.cs b/src/System.Management.Automation/engine/Interop/Windows/SHGetFileInfo.cs new file mode 100644 index 00000000000..a6deaa39c41 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/SHGetFileInfo.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Keep native struct names.")] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Keep native struct names.")] + internal static partial class Windows + { + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct SHFILEINFO + { + internal nint hIcon; + internal int iIcon; + internal uint dwAttributes; + internal fixed char szDisplayName[260]; + internal fixed char szTypeName[80]; + + public static readonly uint s_Size = (uint)sizeof(SHFILEINFO); + } + + [LibraryImport("shell32.dll", EntryPoint = "SHGetFileInfoW", StringMarshalling = StringMarshalling.Utf16)] + internal static partial nint SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags); + + internal static int SHGetFileInfo(string pszPath) + { + // flag used to ask to return exe type + const uint SHGFI_EXETYPE = 0x000002000; + var shinfo = new SHFILEINFO(); + return (int)SHGetFileInfo(pszPath, 0, ref shinfo, SHFILEINFO.s_Size, SHGFI_EXETYPE); + } + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/SetForegroundWindow.cs b/src/System.Management.Automation/engine/Interop/Windows/SetForegroundWindow.cs new file mode 100644 index 00000000000..5945fac9608 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/SetForegroundWindow.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("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool SetForegroundWindow(nint hWnd); + } +} 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()); + } + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/ShowWindow.cs b/src/System.Management.Automation/engine/Interop/Windows/ShowWindow.cs new file mode 100644 index 00000000000..d33b0a0e244 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/ShowWindow.cs @@ -0,0 +1,32 @@ +// 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 SW_HIDE = 0; + internal const int SW_SHOWNORMAL = 1; + internal const int SW_NORMAL = 1; + internal const int SW_SHOWMINIMIZED = 2; + internal const int SW_SHOWMAXIMIZED = 3; + internal const int SW_MAXIMIZE = 3; + internal const int SW_SHOWNOACTIVATE = 4; + internal const int SW_SHOW = 5; + internal const int SW_MINIMIZE = 6; + internal const int SW_SHOWMINNOACTIVE = 7; + internal const int SW_SHOWNA = 8; + internal const int SW_RESTORE = 9; + internal const int SW_SHOWDEFAULT = 10; + internal const int SW_FORCEMINIMIZE = 11; + internal const int SW_MAX = 11; + + [LibraryImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool ShowWindow(nint hWnd, int nCmdShow); + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/VariantClear.cs b/src/System.Management.Automation/engine/Interop/Windows/VariantClear.cs new file mode 100644 index 00000000000..414a0912c81 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/VariantClear.cs @@ -0,0 +1,15 @@ +// 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 + { + [LibraryImport("oleaut32.dll")] + internal static partial void VariantClear(nint pVariant); + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/WNetAddConnection2.cs b/src/System.Management.Automation/engine/Interop/Windows/WNetAddConnection2.cs new file mode 100644 index 00000000000..df66e743897 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/WNetAddConnection2.cs @@ -0,0 +1,73 @@ +// 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 const int CONNECT_NOPERSIST = 0x00000000; + internal const int CONNECT_UPDATE_PROFILE = 0x00000001; + internal const int RESOURCE_GLOBALNET = 0x00000002; + internal const int RESOURCETYPE_ANY = 0x00000000; + internal const int RESOURCEDISPLAYTYPE_GENERIC = 0x00000000; + internal const int RESOURCEUSAGE_CONNECTABLE = 0x00000001; + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct NETRESOURCEW + { + public int Scope; + public int Type; + public int DisplayType; + public int Usage; + public char* LocalName; + public char* RemoteName; + public char* Comment; + public char* Provider; + } + + [LibraryImport("mpr.dll", EntryPoint = "WNetAddConnection2W", StringMarshalling = StringMarshalling.Utf16)] + internal static partial int WNetAddConnection2(ref NETRESOURCEW netResource, byte[] password, string userName, int flags); + + internal static unsafe int WNetAddConnection2(string localName, string remoteName, byte[] password, string userName, int connectType) + { + if (s_WNetApiNotAvailable) + { + return ERROR_NOT_SUPPORTED; + } + + int errorCode = ERROR_NO_NETWORK; + + fixed (char* pinnedLocalName = localName) + fixed (char* pinnedRemoteName = remoteName) + { + NETRESOURCEW resource = new NETRESOURCEW() + { + Comment = null, + DisplayType = RESOURCEDISPLAYTYPE_GENERIC, + LocalName = pinnedLocalName, + Provider = null, + RemoteName = pinnedRemoteName, + Scope = RESOURCE_GLOBALNET, + Type = RESOURCETYPE_ANY, + Usage = RESOURCEUSAGE_CONNECTABLE + }; + + try + { + errorCode = WNetAddConnection2(ref resource, password, userName, connectType); + } + catch (System.DllNotFoundException) + { + s_WNetApiNotAvailable = true; + return ERROR_NOT_SUPPORTED; + } + } + + return errorCode; + } + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/WNetCancelConnection2.cs b/src/System.Management.Automation/engine/Interop/Windows/WNetCancelConnection2.cs new file mode 100644 index 00000000000..0ff720ffd3e --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/WNetCancelConnection2.cs @@ -0,0 +1,37 @@ +// 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("mpr.dll", EntryPoint = "WNetCancelConnection2W", StringMarshalling = StringMarshalling.Utf16)] + internal static partial int WNetCancelConnection2W(string driveName, int flags, [MarshalAs(UnmanagedType.Bool)] bool force); + + internal static int WNetCancelConnection2(string driveName, int flags, bool force) + { + if (s_WNetApiNotAvailable) + { + return ERROR_NOT_SUPPORTED; + } + + int errorCode = ERROR_NO_NETWORK; + + try + { + errorCode = WNetCancelConnection2W(driveName, flags, force: true); + } + catch (System.DllNotFoundException) + { + s_WNetApiNotAvailable = true; + return ERROR_NOT_SUPPORTED; + } + + return errorCode; + } + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/WNetGetConnection.cs b/src/System.Management.Automation/engine/Interop/Windows/WNetGetConnection.cs new file mode 100644 index 00000000000..88ec4386a14 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/WNetGetConnection.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Buffers; +using System.Runtime.InteropServices; +using System.Management.Automation.Internal; + +internal static partial class Interop +{ + 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 localName, Span remoteName, ref int remoteNameLength); + + internal static int GetUNCForNetworkDrive(char drive, out string? uncPath) + { + uncPath = null; + if (s_WNetApiNotAvailable) + { + return ERROR_NOT_SUPPORTED; + } + + ReadOnlySpan driveName = stackalloc char[] { drive, ':', '\0' }; + int bufferSize = MAX_PATH; + Span uncBuffer = stackalloc char[MAX_PATH]; + if (InternalTestHooks.WNetGetConnectionBufferSize > 0 && InternalTestHooks.WNetGetConnectionBufferSize <= MAX_PATH) + { + bufferSize = InternalTestHooks.WNetGetConnectionBufferSize; + uncBuffer = uncBuffer.Slice(0, bufferSize); + } + + char[]? rentedArray = null; + while (true) + { + int errorCode; + try + { + try + { + errorCode = WNetGetConnection(driveName, uncBuffer, ref bufferSize); + } + catch (DllNotFoundException) + { + s_WNetApiNotAvailable = true; + return ERROR_NOT_SUPPORTED; + } + + if (errorCode == ERROR_SUCCESS) + { + // 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 + { + if (rentedArray is not null) + { + ArrayPool.Shared.Return(rentedArray); + } + } + + if (errorCode == ERROR_MORE_DATA) + { + uncBuffer = rentedArray = ArrayPool.Shared.Rent(bufferSize); + } + else + { + return errorCode; + } + } + } + } +} diff --git a/src/System.Management.Automation/engine/InvocationInfo.cs b/src/System.Management.Automation/engine/InvocationInfo.cs index 6020b6e53a0..402d7c8d4a6 100644 --- a/src/System.Management.Automation/engine/InvocationInfo.cs +++ b/src/System.Management.Automation/engine/InvocationInfo.cs @@ -271,6 +271,18 @@ public string Line } } + /// + /// The full text of the invocation statement, may span multiple lines. + /// + /// Statement that was entered to invoke this command. + public string Statement + { + get + { + return ScriptPosition.Text; + } + } + /// /// Formatted message indicating where the cmdlet appeared /// in the line. @@ -440,11 +452,11 @@ internal void ToPSObjectForRemoting(PSObject psObject) if (extent != null) { extent.ToPSObjectForRemoting(psObject); - RemotingEncoder.AddNoteProperty(psObject, "SerializeExtent", () => true); + RemotingEncoder.AddNoteProperty(psObject, "SerializeExtent", static () => true); } else { - RemotingEncoder.AddNoteProperty(psObject, "SerializeExtent", () => false); + RemotingEncoder.AddNoteProperty(psObject, "SerializeExtent", static () => false); } RemoteCommandInfo.ToPSObjectForRemoting(this.MyCommand, psObject); diff --git a/src/System.Management.Automation/engine/LanguagePrimitives.cs b/src/System.Management.Automation/engine/LanguagePrimitives.cs index 83361fe9662..6644223d7c6 100644 --- a/src/System.Management.Automation/engine/LanguagePrimitives.cs +++ b/src/System.Management.Automation/engine/LanguagePrimitives.cs @@ -9,11 +9,11 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; -using System.Linq; using System.Linq.Expressions; using System.Management.Automation.Internal; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; +using System.Management.Automation.Security; using System.Numerics; using System.Reflection; using System.Reflection.Emit; @@ -21,12 +21,12 @@ using System.Text; using System.Text.RegularExpressions; using System.Xml; +using System.Security; using Dbg = System.Management.Automation.Diagnostics; using MethodCacheEntry = System.Management.Automation.DotNetAdapter.MethodCacheEntry; #if !UNIX using System.DirectoryServices; -using System.Management; #endif #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings @@ -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); } - /// /// Returns false, since this converter is not designed to be used to /// convert from the type associated with the converted to other types. @@ -311,9 +310,11 @@ public static class LanguagePrimitives internal static void CreateMemberNotFoundError(PSObject pso, DictionaryEntry property, Type resultType) { - string availableProperties = GetAvailableProperties(pso); + string settableProperties = GetSettableProperties(pso); - string message = StringUtil.Format(ExtendedTypeSystem.PropertyNotFound, property.Key.ToString(), resultType.FullName, availableProperties); + string message = settableProperties == string.Empty + ? StringUtil.Format(ExtendedTypeSystem.NoSettableProperty, property.Key.ToString(), resultType.FullName) + : StringUtil.Format(ExtendedTypeSystem.PropertyNotFound, property.Key.ToString(), resultType.FullName, settableProperties); typeConversion.WriteLine("Issuing an error message about not being able to create an object from hashtable."); throw new InvalidOperationException(message); @@ -339,12 +340,13 @@ internal static void UpdateTypeConvertFromTypeTable(string typeName) { lock (s_converterCache) { - var toRemove = s_converterCache.Keys.Where( - conv => string.Equals(conv.to.FullName, typeName, StringComparison.OrdinalIgnoreCase) || - string.Equals(conv.from.FullName, typeName, StringComparison.OrdinalIgnoreCase)).ToArray(); - foreach (var k in toRemove) + foreach (var key in s_converterCache.Keys) { - s_converterCache.Remove(k); + if (string.Equals(key.to.FullName, typeName, StringComparison.OrdinalIgnoreCase) + || string.Equals(key.from.FullName, typeName, StringComparison.OrdinalIgnoreCase)) + { + s_converterCache.Remove(key); + } } // Note we do not clear possibleTypeConverter even when removing. @@ -362,7 +364,7 @@ internal static void UpdateTypeConvertFromTypeTable(string typeName) /// implementation of an object when we can't use it's non-generic /// implementation. /// - private class EnumerableTWrapper : IEnumerable + private sealed class EnumerableTWrapper : IEnumerable { private readonly object _enumerable; private readonly Type _enumerableType; @@ -632,10 +634,7 @@ public static bool Equals(object first, object second, bool ignoreCase, IFormatP // If second can be converted to the type of the first, it does so and returns first.Equals(secondConverted) // Otherwise false is returned - if (formatProvider == null) - { - formatProvider = CultureInfo.InvariantCulture; - } + formatProvider ??= CultureInfo.InvariantCulture; if (!(formatProvider is CultureInfo culture)) { @@ -779,10 +778,7 @@ public static int Compare(object first, object second, bool ignoreCase) /// public static int Compare(object first, object second, bool ignoreCase, IFormatProvider formatProvider) { - if (formatProvider == null) - { - formatProvider = CultureInfo.InvariantCulture; - } + formatProvider ??= CultureInfo.InvariantCulture; if (!(formatProvider is CultureInfo culture)) { @@ -899,10 +895,7 @@ public static bool TryCompare(object first, object second, bool ignoreCase, out public static bool TryCompare(object first, object second, bool ignoreCase, IFormatProvider formatProvider, out int result) { result = 0; - if (formatProvider == null) - { - formatProvider = CultureInfo.InvariantCulture; - } + formatProvider ??= CultureInfo.InvariantCulture; if (formatProvider is not CultureInfo culture) { @@ -1889,7 +1882,7 @@ public override object ConvertFrom(object sourceValue, Type destinationType, IFo internal class EnumSingleTypeConverter : PSTypeConverter { - private class EnumHashEntry + private sealed class EnumHashEntry { internal EnumHashEntry(string[] names, Array values, UInt64 allValues, bool hasNegativeValue, bool hasFlagsAttribute) { @@ -2083,6 +2076,22 @@ internal static string EnumValues(Type enumType) return string.Join(CultureInfo.CurrentUICulture.TextInfo.ListSeparator, enumHashEntry.names); } + /// + /// Returns all names for the provided enum type. + /// + /// The enum type to retrieve names from. + /// Array of enum names for the specified type. + internal static string[] GetEnumNames(Type enumType) + => EnumSingleTypeConverter.GetEnumHashEntry(enumType).names; + + /// + /// Returns all values for the provided enum type. + /// + /// The enum type to retrieve values from. + /// Array of enum values for the specified type. + internal static Array GetEnumValues(Type enumType) + => EnumSingleTypeConverter.GetEnumHashEntry(enumType).values; + public override object ConvertFrom(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase) { return EnumSingleTypeConverter.BaseConvertFrom(sourceValue, destinationType, formatProvider, ignoreCase, false); @@ -2153,7 +2162,7 @@ protected static object BaseConvertFrom(object sourceValue, Type destinationType } else { - sourceValueEntries = sourceValueString.Split(Utils.Separators.Comma); + sourceValueEntries = sourceValueString.Split(','); fromValuePatterns = new WildcardPattern[sourceValueEntries.Length]; for (int i = 0; i < sourceValueEntries.Length; i++) { @@ -2933,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); } @@ -3684,7 +3688,7 @@ private static object ConvertEnumerableToEnum(object valueToConvert, return ConvertStringToEnum(sbResult.ToString(), resultType, recursion, originalValueToConvert, formatProvider, backupTable); } - private class PSMethodToDelegateConverter + private sealed class PSMethodToDelegateConverter { // Index of the matching overload method. private readonly int _matchIndex; @@ -3744,7 +3748,7 @@ internal Delegate Convert(object valueToConvert, } } - private class ConvertViaParseMethod + private sealed class ConvertViaParseMethod { // TODO - use an ETS wrapper that generates a dynamic method internal MethodInfo parse; @@ -3810,7 +3814,7 @@ internal object ConvertWithoutCulture(object valueToConvert, } } - private class ConvertViaConstructor + private sealed class ConvertViaConstructor { internal Func TargetCtorLambda; @@ -3849,12 +3853,12 @@ internal object Convert(object valueToConvert, /// Create a IList to hold all elements, and use the IList to create the object of the resultType. /// The reason for using IList is that it can work on constructors that takes IEnumerable[T], ICollection[T] or IList[T]. /// - /// + /// /// When get to this method, we know the fromType and the toType meet the following two conditions: /// 1. toType is a closed generic type and it has a constructor that takes IEnumerable[T], ICollection[T] or IList[T] /// 2. fromType is System.Array, System.Object[] or it's the same as the element type of toType - /// - private class ConvertViaIEnumerableConstructor + /// + private sealed class ConvertViaIEnumerableConstructor { internal Func ListCtorLambda; internal Func TargetCtorLambda; @@ -3947,7 +3951,7 @@ internal object Convert(object valueToConvert, } } - private class ConvertViaNoArgumentConstructor + private sealed class ConvertViaNoArgumentConstructor { private readonly Func _constructor; @@ -3984,32 +3988,46 @@ internal object Convert(object valueToConvert, // - It's in FullLanguage but not because it's part of a parameter binding that is transitioning from ConstrainedLanguage to FullLanguage // When this is invoked from a parameter binding in transition from ConstrainedLanguage environment to FullLanguage command, we disallow // the property conversion because it's dangerous. - if (ecFromTLS == null || (ecFromTLS.LanguageMode == PSLanguageMode.FullLanguage && !ecFromTLS.LanguageModeTransitionInParameterBinding)) + bool canProceedWithConversion = ecFromTLS == null || (ecFromTLS.LanguageMode == PSLanguageMode.FullLanguage && !ecFromTLS.LanguageModeTransitionInParameterBinding); + if (!canProceedWithConversion) { - result = _constructor(); - var psobject = valueToConvert as PSObject; - if (psobject != null) + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) { - // Use PSObject properties to perform conversion. - SetObjectProperties(result, psobject, resultType, CreateMemberNotFoundError, CreateMemberSetValueError, formatProvider, recursion, ignoreUnknownMembers); - } - else - { - // Use provided property dictionary to perform conversion. - // The method invocation is disabled for "Hashtable to Object conversion" (Win8:649519), but we need to keep it enabled for New-Object for compatibility to PSv2 - IDictionary properties = valueToConvert as IDictionary; - SetObjectProperties(result, properties, resultType, CreateMemberNotFoundError, CreateMemberSetValueError, enableMethodCall: false); + throw InterpreterError.NewInterpreterException( + valueToConvert, + typeof(RuntimeException), + errorPosition: null, + "HashtableToObjectConversionNotSupportedInDataSection", + ParserStrings.HashtableToObjectConversionNotSupportedInDataSection, + resultType.ToString()); } - typeConversion.WriteLine("Constructor result: \"{0}\".", result); + // When in audit mode, we report but don't enforce, so we will proceed with the conversion. + SystemPolicy.LogWDACAuditMessage( + context: ecFromTLS, + title: ExtendedTypeSystem.WDACHashTypeLogTitle, + message: StringUtil.Format(ExtendedTypeSystem.WDACHashTypeLogMessage, resultType.FullName), + fqid: "LanguageHashtableConversionNotAllowed", + dropIntoDebugger: true); + } + + result = _constructor(); + var psobject = valueToConvert as PSObject; + if (psobject != null) + { + // Use PSObject properties to perform conversion. + SetObjectProperties(result, psobject, resultType, CreateMemberNotFoundError, CreateMemberSetValueError, formatProvider, recursion, ignoreUnknownMembers); } else { - RuntimeException rte = InterpreterError.NewInterpreterException(valueToConvert, typeof(RuntimeException), null, - "HashtableToObjectConversionNotSupportedInDataSection", ParserStrings.HashtableToObjectConversionNotSupportedInDataSection, resultType.ToString()); - throw rte; + // Use provided property dictionary to perform conversion. + // The method invocation is disabled for "Hashtable to Object conversion" (Win8:649519), but we need to keep it enabled for New-Object for compatibility to PSv2 + IDictionary properties = valueToConvert as IDictionary; + SetObjectProperties(result, properties, resultType, CreateMemberNotFoundError, CreateMemberSetValueError, enableMethodCall: false); } + typeConversion.WriteLine("Constructor result: \"{0}\".", result); + return result; } catch (TargetInvocationException ex) @@ -4050,7 +4068,7 @@ internal object Convert(object valueToConvert, } } - private class ConvertViaCast + private sealed class ConvertViaCast { internal MethodInfo cast; @@ -4130,7 +4148,7 @@ private static object ConvertNumericIConvertible(object valueToConvert, } } - private class ConvertCheckingForCustomConverter + private sealed class ConvertCheckingForCustomConverter { internal PSConverter tryfirstConverter; internal PSConverter fallbackConverter; @@ -4673,7 +4691,7 @@ internal static PSObject SetObjectProperties(object o, IDictionary properties, T Type propType; if (TypeResolver.TryResolveType(property.TypeNameOfValue, out propType)) { - if (formatProvider == null) { formatProvider = CultureInfo.InvariantCulture; } + formatProvider ??= CultureInfo.InvariantCulture; try { @@ -4698,6 +4716,12 @@ internal static PSObject SetObjectProperties(object o, IDictionary properties, T } } + // treat AutomationNull.Value as null for consistency + if (propValue == AutomationNull.Value) + { + propValue = null; + } + property.Value = propValue; } else @@ -4735,18 +4759,23 @@ internal static PSObject SetObjectProperties(object o, IDictionary properties, T return pso; } - private static string GetAvailableProperties(PSObject pso) + private static string GetSettableProperties(PSObject pso) { + if (pso is null || pso.Properties is null) + { + return string.Empty; + } + StringBuilder availableProperties = new StringBuilder(); bool first = true; - if (pso != null && pso.Properties != null) + foreach (PSPropertyInfo p in pso.Properties) { - foreach (PSPropertyInfo p in pso.Properties) + if (p.IsSettable) { if (!first) { - availableProperties.Append(" , "); + availableProperties.Append(", "); } availableProperties.Append("[" + p.Name + " <" + p.TypeNameOfValue + ">]"); @@ -4894,8 +4923,26 @@ internal static Tuple GetInvalidCastMessages(object valueToConve typeConversion.WriteLine("Type Conversion failed."); errorId = "ConvertToFinalInvalidCastException"; - errorMsg = StringUtil.Format(ExtendedTypeSystem.InvalidCastException, valueToConvert.ToString(), - ObjectToTypeNameString(valueToConvert), resultType.ToString()); + + string valueToConvertTypeName = ObjectToTypeNameString(valueToConvert); + string resultTypeName = resultType.ToString(); + + if (resultType == typeof(SecureString) || resultType == typeof(PSCredential)) + { + errorMsg = StringUtil.Format( + ExtendedTypeSystem.InvalidCastExceptionWithoutValue, + valueToConvertTypeName, + resultTypeName); + } + else + { + errorMsg = StringUtil.Format( + ExtendedTypeSystem.InvalidCastException, + valueToConvert.ToString(), + valueToConvertTypeName, + resultTypeName); + } + return Tuple.Create(errorId, errorMsg); } @@ -5633,33 +5680,33 @@ internal static IConversionData FigureConversion(Type fromType, Type toType) PSConverter converter = null; ConversionRank rank = ConversionRank.None; - // If we've ever used ConstrainedLanguage, check if the target type is allowed + // If we've ever used ConstrainedLanguage, check if the target type is allowed. if (ExecutionContext.HasEverUsedConstrainedLanguage) { var context = LocalPipeline.GetExecutionContextFromTLS(); - - if ((context != null) && (context.LanguageMode == PSLanguageMode.ConstrainedLanguage)) + if (context?.LanguageMode == PSLanguageMode.ConstrainedLanguage) { - if ((toType != typeof(object)) && - (toType != typeof(object[])) && - (!CoreTypes.Contains(toType))) + if (toType != typeof(object) && + toType != typeof(object[]) && + !CoreTypes.Contains(toType)) { - converter = ConvertNotSupportedConversion; - rank = ConversionRank.None; - return CacheConversion(fromType, toType, converter, rank); + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + converter = ConvertNotSupportedConversion; + rank = ConversionRank.None; + return CacheConversion(fromType, toType, converter, rank); + } + + SystemPolicy.LogWDACAuditMessage( + context: context, + title: ExtendedTypeSystem.WDACTypeConversionLogTitle, + message: StringUtil.Format(ExtendedTypeSystem.WDACTypeConversionLogMessage, fromType.FullName, toType.FullName), + fqid: "LanguageTypeConversionNotAllowed", + dropIntoDebugger: true); } } } - // Assemblies in CoreCLR might not allow reflection execution on their internal types. - if (!TypeResolver.IsPublic(toType) && DotNetAdapter.DisallowPrivateReflection(toType)) - { - // If the type is non-public and reflection execution is not allowed on it, then we return - // 'ConvertNoConversion', because we won't be able to invoke constructor, methods or set - // properties on an instance of this type through reflection. - return CacheConversion(fromType, toType, ConvertNoConversion, ConversionRank.None); - } - PSConverter valueDependentConversion = null; ConversionRank valueDependentRank = ConversionRank.None; IConversionData conversionData = FigureLanguageConversion(fromType, toType, out valueDependentConversion, out valueDependentRank); @@ -5745,10 +5792,7 @@ internal static IConversionData FigureConversion(Type fromType, Type toType) } } - if (converter == null) - { - converter = FigurePropertyConversion(fromType, toType, ref rank); - } + converter ??= FigurePropertyConversion(fromType, toType, ref rank); if (TypeConverterPossiblyExists(fromType) || TypeConverterPossiblyExists(toType) || (converter != null && valueDependentConversion != null)) diff --git a/src/System.Management.Automation/engine/ManagementObjectAdapter.cs b/src/System.Management.Automation/engine/ManagementObjectAdapter.cs index c591a6963ec..aa1c151e0e3 100644 --- a/src/System.Management.Automation/engine/ManagementObjectAdapter.cs +++ b/src/System.Management.Automation/engine/ManagementObjectAdapter.cs @@ -454,7 +454,7 @@ protected static CacheTable GetInstanceMethodTable(ManagementBaseObject wmiObjec // unique identifier for identifying this ManagementObject's type ManagementPath classPath = wmiObject.ClassPath; - string key = string.Format(CultureInfo.InvariantCulture, "{0}#{1}", classPath.Path, staticBinding.ToString()); + string key = string.Create(CultureInfo.InvariantCulture, $"{classPath.Path}#{staticBinding}"); typeTable = (CacheTable)s_instanceMethodCacheTable[key]; if (typeTable != null) @@ -577,8 +577,11 @@ protected static string GetEmbeddedObjectTypeName(PropertyData pData) try { string cimType = (string)pData.Qualifiers["cimtype"].Value; - result = string.Format(CultureInfo.InvariantCulture, "{0}#{1}", - typeof(ManagementObject).FullName, cimType.Replace("object:", string.Empty)); + result = string.Format( + CultureInfo.InvariantCulture, + "{0}#{1}", + typeof(ManagementObject).FullName, + cimType.Replace("object:", string.Empty)); } catch (ManagementException) { @@ -1162,9 +1165,7 @@ protected override PSProperty DoGetProperty(ManagementBaseObject wmiObject, stri PSLevel.Informational, PSTask.None, PSKeyword.UseAlwaysOperational, - string.Format(CultureInfo.InvariantCulture, - "ManagementBaseObjectAdapter::DoGetProperty::PropertyName:{0}, Exception:{1}, StackTrace:{2}", - propertyName, e.Message, e.StackTrace), + string.Create(CultureInfo.InvariantCulture, $"ManagementBaseObjectAdapter::DoGetProperty::PropertyName:{propertyName}, Exception:{e.Message}, StackTrace:{e.StackTrace}"), string.Empty, string.Empty); // ignore the exception. diff --git a/src/System.Management.Automation/engine/MergedCommandParameterMetadata.cs b/src/System.Management.Automation/engine/MergedCommandParameterMetadata.cs index f2d20c68ec9..d2b972a118d 100644 --- a/src/System.Management.Automation/engine/MergedCommandParameterMetadata.cs +++ b/src/System.Management.Automation/engine/MergedCommandParameterMetadata.cs @@ -163,6 +163,13 @@ internal Collection AddMetadataForBinder( /// private uint _nextAvailableParameterSetIndex; + /// + /// The maximum number of parameter sets allowed. Limit is set by the use + /// of a uint bitmask to store which parameter sets a parameter is included in. + /// See . + /// + private const uint MaxParameterSetCount = 32; + /// /// Gets the number of parameter sets that were declared for the command. /// @@ -228,7 +235,7 @@ private int AddParameterSetToMap(string parameterSetName) // A parameter set name should only be added once if (index == -1) { - if (_nextAvailableParameterSetIndex == uint.MaxValue) + if (_nextAvailableParameterSetIndex >= MaxParameterSetCount) { // Don't let the parameter set index overflow ParsingMetadataException parsingException = diff --git a/src/System.Management.Automation/engine/Modules/AnalysisCache.cs b/src/System.Management.Automation/engine/Modules/AnalysisCache.cs index 084c8c4674e..44dc9f1b610 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; @@ -33,13 +34,12 @@ internal static class AnalysisCache // This dictionary shouldn't see much use, so low concurrency and capacity private static readonly ConcurrentDictionary s_modulesBeingAnalyzed = - new ConcurrentDictionary( /*concurrency*/1, /*capacity*/2, StringComparer.OrdinalIgnoreCase); + new(concurrencyLevel: 1, capacity: 2, StringComparer.OrdinalIgnoreCase); - internal static readonly char[] InvalidCommandNameCharacters = new[] - { - '#', ',', '(', ')', '{', '}', '[', ']', '&', '/', '\\', '$', '^', ';', ':', - '"', '\'', '<', '>', '|', '?', '@', '`', '*', '%', '+', '=', '~' - }; + internal static readonly SearchValues InvalidCommandNameCharacters = SearchValues.Create("#,(){}[]&/\\$^;:\"'<>|?@`*%+=~"); + + internal static bool ContainsInvalidCommandNameCharacters(ReadOnlySpan text) + => text.ContainsAny(InvalidCommandNameCharacters); internal static ConcurrentDictionary GetExportedCommands(string modulePath, bool testOnly, ExecutionContext context) { @@ -345,7 +345,7 @@ private static ConcurrentDictionary AnalyzeScriptModule(st { if (SessionStateUtilities.MatchesAnyWildcardPattern(command, scriptAnalysisPatterns, true)) { - if (command.IndexOfAny(InvalidCommandNameCharacters) < 0) + if (!ContainsInvalidCommandNameCharacters(command)) { result[command] = CommandTypes.Function; } @@ -357,10 +357,10 @@ private static ConcurrentDictionary AnalyzeScriptModule(st { var commandName = pair.Key; // These are already filtered - if (commandName.IndexOfAny(InvalidCommandNameCharacters) < 0) + if (!ContainsInvalidCommandNameCharacters(commandName)) { result.AddOrUpdate(commandName, CommandTypes.Alias, - (_, existingCommandType) => existingCommandType | CommandTypes.Alias); + static (_, existingCommandType) => existingCommandType | CommandTypes.Alias); } } @@ -375,7 +375,7 @@ private static ConcurrentDictionary AnalyzeScriptModule(st { var command = Path.GetFileNameWithoutExtension(item); result.AddOrUpdate(command, CommandTypes.ExternalScript, - (_, existingCommandType) => existingCommandType | CommandTypes.ExternalScript); + static (_, existingCommandType) => existingCommandType | CommandTypes.ExternalScript); } } catch (UnauthorizedAccessException) @@ -384,8 +384,10 @@ private static ConcurrentDictionary AnalyzeScriptModule(st } } - var exportedClasses = new ConcurrentDictionary( /*concurrency*/ - 1, scriptAnalysis.DiscoveredClasses.Count, StringComparer.OrdinalIgnoreCase); + ConcurrentDictionary exportedClasses = new( + concurrencyLevel: 1, + capacity: scriptAnalysis.DiscoveredClasses.Count, + StringComparer.OrdinalIgnoreCase); foreach (var exportedClass in scriptAnalysis.DiscoveredClasses) { exportedClasses[exportedClass.Name] = exportedClass.TypeAttributes; @@ -640,7 +642,7 @@ private static bool GetModuleEntryFromCache(string modulePath, out DateTime last } } - internal class AnalysisCacheData + internal sealed class AnalysisCacheData { private static byte[] GetHeader() { @@ -1090,7 +1092,7 @@ static AnalysisCacheData() // When multiple copies of pwsh are on the system, they should use their own copy of the cache. // Append hash of `$PSHOME` to cacheFileName. string hashString = CRC32Hash.ComputeHash(Utils.DefaultPowerShellAppBase); - cacheFileName = string.Format(CultureInfo.InvariantCulture, "{0}-{1}", cacheFileName, hashString); + cacheFileName = string.Create(CultureInfo.InvariantCulture, $"{cacheFileName}-{hashString}"); if (ExperimentalFeature.EnabledExperimentalFeatureNames.Count > 0) { @@ -1116,7 +1118,7 @@ static AnalysisCacheData() // Use CRC32 because it's faster. // It's very unlikely to get collision from hashing the combinations of enabled features names. hashString = CRC32Hash.ComputeHash(allNames); - cacheFileName = string.Format(CultureInfo.InvariantCulture, "{0}-{1}", cacheFileName, hashString); + cacheFileName = string.Create(CultureInfo.InvariantCulture, $"{cacheFileName}-{hashString}"); } s_cacheStoreLocation = Path.Combine(Platform.CacheDirectory, cacheFileName); diff --git a/src/System.Management.Automation/engine/Modules/ExportModuleMemberCommand.cs b/src/System.Management.Automation/engine/Modules/ExportModuleMemberCommand.cs index 8520e39258b..3b47bc37f10 100644 --- a/src/System.Management.Automation/engine/Modules/ExportModuleMemberCommand.cs +++ b/src/System.Management.Automation/engine/Modules/ExportModuleMemberCommand.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Management.Automation; using System.Management.Automation.Internal; +using System.Management.Automation.Security; // // Now define the set of commands for manipulating modules. @@ -167,9 +168,19 @@ protected override void ProcessRecord() if (Context.EngineSessionState.Module?.LanguageMode != null && Context.LanguageMode != Context.EngineSessionState.Module.LanguageMode) { - var se = new PSSecurityException(Modules.CannotExportMembersAccrossLanguageBoundaries); - var er = new ErrorRecord(se, "Modules_CannotExportMembersAccrossLanguageBoundaries", ErrorCategory.SecurityError, this); - ThrowTerminatingError(er); + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + var se = new PSSecurityException(Modules.CannotExportMembersAccrossLanguageBoundaries); + var er = new ErrorRecord(se, "Modules_CannotExportMembersAccrossLanguageBoundaries", ErrorCategory.SecurityError, this); + ThrowTerminatingError(er); + } + + SystemPolicy.LogWDACAuditMessage( + context: Context, + title: Modules.WDACExportModuleCommandLogTitle, + message: StringUtil.Format(Modules.WDACExportModuleCommandLogMessage, Context.EngineSessionState.Module.Name, Context.EngineSessionState.Module.LanguageMode, Context.LanguageMode), + fqid: "ExportModuleMemberCmdletNotAllowed", + dropIntoDebugger: true); } ModuleIntrinsics.ExportModuleMembers(this, diff --git a/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs b/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs index 663e1f4b0fe..ca48c5c698c 100644 --- a/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs @@ -265,7 +265,7 @@ private IEnumerable GetAvailableViaCimSessionCore(IEnumerable remoteModuleInfos = remoteModules .Select(cimModule => this.ConvertCimModuleInfoToPSModuleInfo(cimModule, cimSession.ComputerName)) - .Where(moduleInfo => moduleInfo != null); + .Where(static moduleInfo => moduleInfo != null); return remoteModuleInfos; } @@ -382,8 +382,8 @@ protected override void ProcessRecord() FullyQualifiedName[modSpecIndex] = FullyQualifiedName[modSpecIndex].WithNormalizedName(Context, SessionState.Path.CurrentLocation.Path); } - moduleSpecTable = FullyQualifiedName.ToDictionary(moduleSpecification => moduleSpecification.Name, StringComparer.OrdinalIgnoreCase); - strNames.AddRange(FullyQualifiedName.Select(spec => spec.Name)); + moduleSpecTable = FullyQualifiedName.ToDictionary(static moduleSpecification => moduleSpecification.Name, StringComparer.OrdinalIgnoreCase); + strNames.AddRange(FullyQualifiedName.Select(static spec => spec.Name)); } string[] names = strNames.Count > 0 ? strNames.ToArray() : null; @@ -515,7 +515,7 @@ private IEnumerable FilterModulesForEditionAndSpecification( // Edition check only applies to Windows System32 module path if (!SkipEditionCheck && ListAvailable && !All) { - modules = modules.Where(module => module.IsConsideredEditionCompatible); + modules = modules.Where(static module => module.IsConsideredEditionCompatible); } #endif @@ -586,24 +586,25 @@ private static IEnumerable GetCandidateModuleSpecs( } /// - /// PSEditionArgumentCompleter for PowerShell Edition names. + /// Provides argument completion for PSEdition parameter. /// public class PSEditionArgumentCompleter : IArgumentCompleter { /// - /// CompleteArgument. + /// Returns completion results for PSEdition parameter. /// - public IEnumerable 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); - } - } - } + /// The command name. + /// The parameter name. + /// The word to complete. + /// The command AST. + /// The fake bound parameters. + /// List of completion results. + public IEnumerable 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 365d6e7642c..15e8bfe6a9a 100644 --- a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs @@ -548,32 +548,15 @@ private void ImportModule_ViaLocalModuleInfo(ImportModuleOptions importModuleOpt private void ImportModule_ViaAssembly(ImportModuleOptions importModuleOptions, Assembly suppliedAssembly) { bool moduleLoaded = false; + string moduleName = "dynamic_code_module_" + suppliedAssembly.FullName; + // Loop through Module Cache to ensure that the module is not already imported. - if (suppliedAssembly != null && Context.Modules.ModuleTable != null) + foreach (KeyValuePair pair in Context.Modules.ModuleTable) { - foreach (KeyValuePair pair in Context.Modules.ModuleTable) + if (pair.Value.Path == string.Empty) { - // if the module in the moduleTable is an assembly module without path, the moduleName is the key. - string moduleName = "dynamic_code_module_" + suppliedAssembly; - if (pair.Value.Path == string.Empty) - { - if (pair.Key.Equals(moduleName, StringComparison.OrdinalIgnoreCase)) - { - moduleLoaded = true; - if (BasePassThru) - { - WriteObject(pair.Value); - } - - break; - } - else - { - continue; - } - } - - if (pair.Value.Path.Equals(suppliedAssembly.Location, StringComparison.OrdinalIgnoreCase)) + // If the module in the moduleTable is an assembly module without path, the moduleName is the key. + if (pair.Key.Equals(moduleName, StringComparison.OrdinalIgnoreCase)) { moduleLoaded = true; if (BasePassThru) @@ -583,13 +566,26 @@ private void ImportModule_ViaAssembly(ImportModuleOptions importModuleOptions, A break; } + + continue; + } + + if (pair.Value.Path.Equals(suppliedAssembly.Location, StringComparison.OrdinalIgnoreCase)) + { + moduleLoaded = true; + if (BasePassThru) + { + WriteObject(pair.Value); + } + + break; } } if (!moduleLoaded) { PSModuleInfo module = LoadBinaryModule( - trySnapInName: false, + parentModule: null, moduleName: null, fileName: null, suppliedAssembly, @@ -597,15 +593,13 @@ private void ImportModule_ViaAssembly(ImportModuleOptions importModuleOptions, A ss: null, importModuleOptions, ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError, - this.BasePrefix, - loadTypes: false, - loadFormats: false, + BasePrefix, out bool found); - if (found && module != null) + if (found && module is not null) { // Add it to all module tables ... - AddModuleToModuleTables(this.Context, this.TargetSessionState.Internal, module); + AddModuleToModuleTables(Context, TargetSessionState.Internal, module); if (BasePassThru) { WriteObject(module); @@ -625,7 +619,7 @@ private PSModuleInfo ImportModule_LocallyViaName_WithTelemetry(ImportModuleOptio // avoid double reporting for WinCompat modules that go through CommandDiscovery\AutoloadSpecifiedModule if (!foundModule.IsWindowsPowerShellCompatModule) { - ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, foundModule.Name); + ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, foundModule); #if LEGACYTELEMETRY TelemetryAPI.ReportModuleLoad(foundModule); #endif @@ -637,6 +631,8 @@ private PSModuleInfo ImportModule_LocallyViaName_WithTelemetry(ImportModuleOptio private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModuleOptions, string name) { + bool shallWriteError = !importModuleOptions.SkipSystem32ModulesAndSuppressError; + try { bool found = false; @@ -665,21 +661,18 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul } } - if (rootedPath == null) + // If null check for full-qualified paths - either absolute or relative + rootedPath ??= ResolveRootedFilePath(name, this.Context); + + bool alreadyLoaded = false; + var manifestProcessingFlags = ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.NullOnFirstError; + if (shallWriteError) { - // Check for full-qualified paths - either absolute or relative - rootedPath = ResolveRootedFilePath(name, this.Context); + manifestProcessingFlags |= ManifestProcessingFlags.WriteErrors; } - bool alreadyLoaded = false; if (!string.IsNullOrEmpty(rootedPath)) { - // TODO/FIXME: use IsModuleAlreadyLoaded to get consistent behavior - // TODO/FIXME: (for example checking ModuleType != Manifest below seems incorrect - cdxml modules also declare their own version) - // PSModuleInfo alreadyLoadedModule = null; - // TryGetFromModuleTable(rootedPath, out alreadyLoadedModule); - // if (!BaseForce && IsModuleAlreadyLoaded(alreadyLoadedModule)) - // If the module has already been loaded, just emit it and continue... if (!BaseForce && TryGetFromModuleTable(rootedPath, out PSModuleInfo module)) { @@ -726,9 +719,14 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul RemoveModule(moduleToRemove); } - foundModule = LoadModule(rootedPath, null, this.BasePrefix, null, ref importModuleOptions, - ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError, - out found); + foundModule = LoadModule( + fileName: rootedPath, + moduleBase: null, + prefix: BasePrefix, + ss: null, /*SessionState*/ + ref importModuleOptions, + manifestProcessingFlags, + out found); } else if (Directory.Exists(rootedPath)) { @@ -739,21 +737,24 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul } // Load the latest valid version if it is a multi-version module directory - foundModule = LoadUsingMultiVersionModuleBase(rootedPath, - ManifestProcessingFlags.LoadElements | - ManifestProcessingFlags.WriteErrors | - ManifestProcessingFlags.NullOnFirstError, - importModuleOptions, out found); + foundModule = LoadUsingMultiVersionModuleBase(rootedPath, manifestProcessingFlags, importModuleOptions, out found); if (!found) { // If the path is a directory, double up the end of the string // then try to load that using extensions... rootedPath = Path.Combine(rootedPath, Path.GetFileName(rootedPath)); - foundModule = LoadUsingExtensions(null, rootedPath, rootedPath, null, null, this.BasePrefix, /*SessionState*/ null, - importModuleOptions, - ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError, - out found); + foundModule = LoadUsingExtensions( + parentModule: null, + moduleName: rootedPath, + fileBaseName: rootedPath, + extension: null, + moduleBase: null, + prefix: BasePrefix, + ss: null, /*SessionState*/ + importModuleOptions, + manifestProcessingFlags, + out found); } } } @@ -763,7 +764,7 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul // Check if module could be a snapin. This was the case for PowerShell version 2 engine modules. if (InitialSessionState.IsEngineModule(name)) { - PSSnapInInfo snapin = ModuleCmdletBase.GetEngineSnapIn(Context, name); + PSSnapInInfo snapin = Context.CurrentRunspace.InitialSessionState.GetPSSnapIn(name); // Return the command if we found a module if (snapin != null) @@ -787,16 +788,28 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul // If there is no extension, we'll have to search using the extensions if (!string.IsNullOrEmpty(Path.GetExtension(name))) { - foundModule = LoadModule(name, null, this.BasePrefix, null, ref importModuleOptions, - ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError, - out found); + foundModule = LoadModule( + fileName: name, + moduleBase: null, + prefix: BasePrefix, + ss: null, /*SessionState*/ + ref importModuleOptions, + manifestProcessingFlags, + out found); } else { - foundModule = LoadUsingExtensions(null, name, name, null, null, this.BasePrefix, /*SessionState*/ null, - importModuleOptions, - ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError, - out found); + foundModule = LoadUsingExtensions( + parentModule: null, + moduleName: name, + fileBaseName: name, + extension: null, + moduleBase: null, + prefix: BasePrefix, + ss: null, /*SessionState*/ + importModuleOptions, + manifestProcessingFlags, + out found); } } else @@ -808,14 +821,17 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul this.AddToAppDomainLevelCache = true; } - found = LoadUsingModulePath(found, modulePath, name, /* SessionState*/ null, - importModuleOptions, - ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError, - out foundModule); + found = LoadUsingModulePath( + modulePath, + name, + ss: null, /* SessionState*/ + importModuleOptions, + manifestProcessingFlags, + out foundModule); } } - if (!found) + if (!found && shallWriteError) { ErrorRecord er = null; string message = null; @@ -857,8 +873,10 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul } catch (PSInvalidOperationException e) { - ErrorRecord er = new ErrorRecord(e.ErrorRecord, e); - WriteError(er); + if (shallWriteError) + { + WriteError(new ErrorRecord(e.ErrorRecord, e)); + } } return null; @@ -875,7 +893,7 @@ private PSModuleInfo ImportModule_LocallyViaFQName(ImportModuleOptions importMod if (foundModule != null) { - ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, foundModule.Name); + ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, foundModule); SetModuleBaseForEngineModules(foundModule.Name, this.Context); } @@ -917,7 +935,7 @@ private IList ImportModule_RemotelyViaPsrpSession( // Send telemetry on the imported modules foreach (PSModuleInfo moduleInfo in remotelyImportedModules) { - ApplicationInsightsTelemetry.SendTelemetryMetric(usingWinCompat ? TelemetryType.WinCompatModuleLoad : TelemetryType.ModuleLoad, moduleInfo.Name); + ApplicationInsightsTelemetry.SendModuleTelemetryMetric(usingWinCompat ? TelemetryType.WinCompatModuleLoad : TelemetryType.ModuleLoad, moduleInfo); } return remotelyImportedModules; @@ -977,7 +995,7 @@ private IList ImportModule_RemotelyViaPsrpSession( string errorMessageTemplate = string.Format( CultureInfo.InvariantCulture, Modules.RemoteDiscoveryRemotePsrpCommandFailed, - string.Format(CultureInfo.InvariantCulture, "Import-Module -Name '{0}'", moduleName)); + string.Create(CultureInfo.InvariantCulture, $"Import-Module -Name '{moduleName}'")); remotelyImportedModules = RemoteDiscoveryHelper.InvokePowerShell( powerShell, this, @@ -1308,8 +1326,8 @@ private void ImportModule_RemotelyViaCimSession( this, this.CancellationToken).ToList(); - IEnumerable remotePsCimModules = remoteModules.Where(cimModule => cimModule.IsPsCimModule); - IEnumerable remotePsrpModuleNames = remoteModules.Where(cimModule => !cimModule.IsPsCimModule).Select(cimModule => cimModule.ModuleName); + IEnumerable remotePsCimModules = remoteModules.Where(static cimModule => cimModule.IsPsCimModule); + IEnumerable remotePsrpModuleNames = remoteModules.Where(static cimModule => !cimModule.IsPsCimModule).Select(static cimModule => cimModule.ModuleName); foreach (string psrpModuleName in remotePsrpModuleNames) { string errorMessage = string.Format( @@ -1327,7 +1345,7 @@ private void ImportModule_RemotelyViaCimSession( // // report an error if some modules were not found // - IEnumerable allFoundModuleNames = remoteModules.Select(cimModule => cimModule.ModuleName).ToList(); + IEnumerable allFoundModuleNames = remoteModules.Select(static cimModule => cimModule.ModuleName).ToList(); foreach (string requestedModuleName in moduleNames) { var wildcardPattern = WildcardPattern.Get(requestedModuleName, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); @@ -1348,20 +1366,32 @@ private void ImportModule_RemotelyViaCimSession( foreach (RemoteDiscoveryHelper.CimModule remoteCimModule in remotePsCimModules) { ImportModule_RemotelyViaCimModuleData(importModuleOptions, remoteCimModule, cimSession); - ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, remoteCimModule.ModuleName); + // we don't know the version of the module + ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, remoteCimModule.ModuleName); } } private bool IsPs1xmlFileHelper_IsPresentInEntries(RemoteDiscoveryHelper.CimModuleFile cimModuleFile, IEnumerable manifestEntries) { - if (manifestEntries.Any(s => s.EndsWith(cimModuleFile.FileName, StringComparison.OrdinalIgnoreCase))) + const string ps1xmlExt = ".ps1xml"; + string fileName = cimModuleFile.FileName; + + foreach (string entry in manifestEntries) { - return true; + if (entry.EndsWith(fileName, StringComparison.OrdinalIgnoreCase)) + { + return true; + } } - if (manifestEntries.Any(s => FixupFileName(string.Empty, s, ".ps1xml", isImportingModule: true).EndsWith(cimModuleFile.FileName, StringComparison.OrdinalIgnoreCase))) + foreach (string entry in manifestEntries) { - return true; + string tempName = entry.EndsWith(ps1xmlExt, StringComparison.OrdinalIgnoreCase) ? entry : entry + ps1xmlExt; + string resolvedPath = ResolveRootedFilePath(tempName, Context); + if (resolvedPath is not null && resolvedPath.EndsWith(fileName, StringComparison.OrdinalIgnoreCase)) + { + return true; + } } return false; @@ -1380,10 +1410,7 @@ private bool IsPs1xmlFileHelper(RemoteDiscoveryHelper.CimModuleFile cimModuleFil goodEntries = new List(); } - if (goodEntries == null) - { - goodEntries = new List(); - } + goodEntries ??= new List(); List badEntries; if (!this.GetListOfStringsFromData(manifestData, null, badKey, 0, out badEntries)) @@ -1391,10 +1418,7 @@ private bool IsPs1xmlFileHelper(RemoteDiscoveryHelper.CimModuleFile cimModuleFil badEntries = new List(); } - if (badEntries == null) - { - badEntries = new List(); - } + badEntries ??= new List(); bool presentInGoodEntries = IsPs1xmlFileHelper_IsPresentInEntries(cimModuleFile, goodEntries); bool presentInBadEntries = IsPs1xmlFileHelper_IsPresentInEntries(cimModuleFile, badEntries); @@ -1842,7 +1866,7 @@ protected override void ProcessRecord() // of doing Get-Module -list foreach (PSModuleInfo module in ModuleInfo) { - ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, module.Name); + ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, module); RemoteDiscoveryHelper.DispatchModuleInfoProcessing( module, localAction: () => @@ -1868,13 +1892,11 @@ protected override void ProcessRecord() else if (this.ParameterSetName.Equals(ParameterSet_Assembly, StringComparison.OrdinalIgnoreCase)) { // Now load all of the supplied assemblies... - if (Assembly != null) + foreach (Assembly suppliedAssembly in Assembly) { - foreach (Assembly suppliedAssembly in Assembly) - { - ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, suppliedAssembly.GetName().Name); - ImportModule_ViaAssembly(importModuleOptions, suppliedAssembly); - } + // we don't know what the version of the module is. + ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, suppliedAssembly.GetName().Name); + ImportModule_ViaAssembly(importModuleOptions, suppliedAssembly); } } else if (this.ParameterSetName.Equals(ParameterSet_Name, StringComparison.OrdinalIgnoreCase)) @@ -1904,7 +1926,7 @@ protected override void ProcessRecord() ImportModule_RemotelyViaPsrpSession(importModuleOptions, null, FullyQualifiedName, this.PSSession); foreach (ModuleSpecification modulespec in FullyQualifiedName) { - ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, modulespec.Name); + ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, modulespec.Name); } } else if (this.ParameterSetName.Equals(ParameterSet_ViaWinCompat, StringComparison.OrdinalIgnoreCase) @@ -1946,27 +1968,26 @@ private bool IsModuleInDenyList(string[] moduleDenyList, string moduleName, Modu return match; } - private List FilterModuleCollection(IEnumerable moduleCollection) + private IEnumerable FilterModuleCollection(IEnumerable moduleCollection) { - List filteredModuleCollection = null; - if (moduleCollection != null) + if (moduleCollection is null) { - // the ModuleDeny list is cached in PowerShellConfig object - string[] moduleDenyList = PowerShellConfig.Instance.GetWindowsPowerShellCompatibilityModuleDenyList(); - if (moduleDenyList?.Any() != true) - { - filteredModuleCollection = new List(moduleCollection); - } - else + return null; + } + + // The ModuleDeny list is cached in PowerShellConfig object + string[] moduleDenyList = PowerShellConfig.Instance.GetWindowsPowerShellCompatibilityModuleDenyList(); + if (moduleDenyList is null || moduleDenyList.Length == 0) + { + return moduleCollection; + } + + var filteredModuleCollection = new List(); + foreach (var module in moduleCollection) + { + if (!IsModuleInDenyList(moduleDenyList, module as string, module as ModuleSpecification)) { - filteredModuleCollection = new List(); - foreach (var module in moduleCollection) - { - if (!IsModuleInDenyList(moduleDenyList, module as string, module as ModuleSpecification)) - { - filteredModuleCollection.Add(module); - } - } + filteredModuleCollection.Add(module); } } @@ -1978,38 +1999,73 @@ private void PrepareNoClobberWinCompatModuleImport(string moduleName, ModuleSpec Debug.Assert(string.IsNullOrEmpty(moduleName) ^ (moduleSpec == null), "Either moduleName or moduleSpec must be specified"); // moduleName can be just a module name and it also can be a full path to psd1 from which we need to extract the module name - string coreModuleToLoad = ModuleIntrinsics.GetModuleName(moduleSpec == null ? moduleName : moduleSpec.Name); + string moduleToLoad = ModuleIntrinsics.GetModuleName(moduleSpec is null ? moduleName : moduleSpec.Name); + + var isBuiltInModule = BuiltInModules.TryGetValue(moduleToLoad, out string normalizedName); + if (isBuiltInModule) + { + moduleToLoad = normalizedName; + } - var isModuleToLoadEngineModule = InitialSessionState.IsEngineModule(coreModuleToLoad); string[] noClobberModuleList = PowerShellConfig.Instance.GetWindowsPowerShellCompatibilityNoClobberModuleList(); - if (isModuleToLoadEngineModule || ((noClobberModuleList != null) && noClobberModuleList.Contains(coreModuleToLoad, StringComparer.OrdinalIgnoreCase))) + if (isBuiltInModule || noClobberModuleList?.Contains(moduleToLoad, StringComparer.OrdinalIgnoreCase) == true) { - // if it is one of engine modules - first try to load it from $PSHOME\Modules - // otherwise rely on $env:PSModulePath (in which WinPS module location has to go after CorePS module location) - if (isModuleToLoadEngineModule) + bool shouldLoadModuleLocally = true; + if (isBuiltInModule) { - string expectedCoreModulePath = Path.Combine(ModuleIntrinsics.GetPSHomeModulePath(), coreModuleToLoad); - if (Directory.Exists(expectedCoreModulePath)) + PSSnapInInfo loadedSnapin = Context.CurrentRunspace.InitialSessionState.GetPSSnapIn(moduleToLoad); + shouldLoadModuleLocally = loadedSnapin is null; + + if (shouldLoadModuleLocally) { - coreModuleToLoad = expectedCoreModulePath; + // If it is one of built-in modules, first try loading it from $PSHOME\Modules, otherwise rely on $env:PSModulePath. + string expectedCoreModulePath = Path.Combine(ModuleIntrinsics.GetPSHomeModulePath(), moduleToLoad); + if (Directory.Exists(expectedCoreModulePath)) + { + moduleToLoad = expectedCoreModulePath; + } } } - if (moduleSpec == null) - { - ImportModule_LocallyViaName_WithTelemetry(importModuleOptions, coreModuleToLoad); - } - else + if (shouldLoadModuleLocally) { - ModuleSpecification tmpModuleSpec = new ModuleSpecification() + // Here we want to load a core-edition compatible version of the module, so the loading procedure will skip + // the 'System32' module path when searching. Also, we want to suppress writing out errors in case that a + // core-compatible version of the module cannot be found, because: + // 1. that's OK as long as it's not a PowerShell built-in module such as the 'Utility' moudle; + // 2. the error message will be confusing to the user. + bool savedValue = importModuleOptions.SkipSystem32ModulesAndSuppressError; + importModuleOptions.SkipSystem32ModulesAndSuppressError = true; + + PSModuleInfo moduleInfo = moduleSpec is null + ? ImportModule_LocallyViaName_WithTelemetry(importModuleOptions, moduleToLoad) + : ImportModule_LocallyViaFQName( + importModuleOptions, + new ModuleSpecification() + { + Guid = moduleSpec.Guid, + MaximumVersion = moduleSpec.MaximumVersion, + Version = moduleSpec.Version, + RequiredVersion = moduleSpec.RequiredVersion, + Name = moduleToLoad + }); + + // If we failed to load a core-compatible version of a built-in module, we should stop trying to load the + // module in 'WinCompat' mode and report an error. This could happen when a user didn't correctly deploy + // the built-in modules, which would result in very confusing errors when the module auto-loading silently + // attempts to load those built-in modules in 'WinCompat' mode from the 'System32' module path. + // + // If the loading failed but it's NOT a built-in module, then it's fine to ignore this failure and continue + // to load the module in 'WinCompat' mode. + if (moduleInfo is null && isBuiltInModule) { - Guid = moduleSpec.Guid, - MaximumVersion = moduleSpec.MaximumVersion, - Version = moduleSpec.Version, - RequiredVersion = moduleSpec.RequiredVersion, - Name = coreModuleToLoad - }; - ImportModule_LocallyViaFQName(importModuleOptions, tmpModuleSpec); + throw new InvalidOperationException( + StringUtil.Format( + Modules.CannotFindCoreCompatibleBuiltInModule, + moduleToLoad)); + } + + importModuleOptions.SkipSystem32ModulesAndSuppressError = savedValue; } importModuleOptions.NoClobberExportPSSession = true; @@ -2021,28 +2077,15 @@ internal override IList ImportModulesUsingWinCompat(IEnumerable moduleProxyList = new List(); #if !UNIX // one of the two parameters can be passed: either ModuleNames (most of the time) or ModuleSpecifications (they are used in different parameter sets) - List filteredModuleNames = FilterModuleCollection(moduleNames); - List filteredModuleFullyQualifiedNames = FilterModuleCollection(moduleFullyQualifiedNames); + IEnumerable filteredModuleNames = FilterModuleCollection(moduleNames); + IEnumerable filteredModuleFullyQualifiedNames = FilterModuleCollection(moduleFullyQualifiedNames); // do not setup WinCompat resources if we have no modules to import - if ((filteredModuleNames?.Any() != true) && (filteredModuleFullyQualifiedNames?.Any() != true)) + if (filteredModuleNames?.Any() != true && filteredModuleFullyQualifiedNames?.Any() != true) { return moduleProxyList; } - var winPSVersionString = Utils.GetWindowsPowerShellVersionFromRegistry(); - if (!winPSVersionString.StartsWith("5.1", StringComparison.OrdinalIgnoreCase)) - { - string errorMessage = string.Format(CultureInfo.InvariantCulture, Modules.WinCompatRequredVersionError, winPSVersionString); - throw new InvalidOperationException(errorMessage); - } - - PSSession WindowsPowerShellCompatRemotingSession = CreateWindowsPowerShellCompatResources(); - if (WindowsPowerShellCompatRemotingSession == null) - { - return new List(); - } - // perform necessary preparations if module has to be imported with NoClobber mode if (filteredModuleNames != null) { @@ -2060,13 +2103,26 @@ internal override IList ImportModulesUsingWinCompat(IEnumerable(); + } + // perform the module import / proxy generation moduleProxyList = ImportModule_RemotelyViaPsrpSession(importModuleOptions, filteredModuleNames, filteredModuleFullyQualifiedNames, WindowsPowerShellCompatRemotingSession, usingWinCompat: true); foreach (PSModuleInfo moduleProxy in moduleProxyList) { moduleProxy.IsWindowsPowerShellCompatModule = true; - System.Threading.Interlocked.Increment(ref s_WindowsPowerShellCompatUsageCounter); + Interlocked.Increment(ref s_WindowsPowerShellCompatUsageCounter); string message = StringUtil.Format(Modules.WinCompatModuleWarning, moduleProxy.Name, WindowsPowerShellCompatRemotingSession.Name); WriteWarning(message); diff --git a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs index 7e7549c6589..f47f9b45e48 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs @@ -16,6 +16,7 @@ using System.Management.Automation.Runspaces; using System.Management.Automation.Security; using System.Reflection; +using System.Runtime.InteropServices; using System.Text; using System.Xml; using System.Diagnostics; @@ -105,6 +106,12 @@ protected internal struct ImportModuleOptions /// Historically -AllowClobber in these scenarios was set as True. /// internal bool NoClobberExportPSSession; + + /// + /// Flag that controls skipping the System32 module path when searching a module in module paths. It also suppresses + /// writing out errors when specified. + /// + internal bool SkipSystem32ModulesAndSuppressError; } /// @@ -129,17 +136,12 @@ protected internal struct ImportModuleOptions internal SessionState TargetSessionState { - get - { - if (BaseGlobal) - { - return this.Context.TopLevelSessionState.PublicSessionState; - } - else - { - return this.Context.SessionState; - } - } + // Module loading could happen during tab completion triggered by PSReadLine, + // but that doesn't mean the module should be loaded targeting the PSReadLine + // module's session state. In that case, use Global session state instead. + get => BaseGlobal || Context.EngineSessionState.Module?.Name is "PSReadLine" + ? Context.TopLevelSessionState.PublicSessionState + : Context.SessionState; } /// @@ -280,6 +282,21 @@ internal List MatchAll "ModuleVersion" }; + /// + /// List of PowerShell built-in modules that are shipped with PowerShell only, not on PS Gallery. + /// + protected static readonly HashSet BuiltInModules = new(StringComparer.OrdinalIgnoreCase) + { + "CimCmdlets", + "Microsoft.PowerShell.Diagnostics", + "Microsoft.PowerShell.Host", + "Microsoft.PowerShell.Management", + "Microsoft.PowerShell.Security", + "Microsoft.PowerShell.Utility", + "Microsoft.WSMan.Management", + "PSDiagnostics", + }; + /// /// When module manifests lack a CompatiblePSEditions field, /// they will be treated as if they have this value. @@ -307,14 +324,25 @@ internal List MatchAll private readonly Dictionary _currentlyProcessingModules = new Dictionary(); - internal bool LoadUsingModulePath(bool found, IEnumerable modulePath, string name, SessionState ss, - ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, out PSModuleInfo module) + internal bool LoadUsingModulePath( + IEnumerable modulePath, + string name, + SessionState ss, + ImportModuleOptions options, + ManifestProcessingFlags manifestProcessingFlags, + out PSModuleInfo module) { - return LoadUsingModulePath(null, found, modulePath, name, ss, options, manifestProcessingFlags, out module); + return LoadUsingModulePath(parentModule: null, modulePath, name, ss, options, manifestProcessingFlags, out module); } - internal bool LoadUsingModulePath(PSModuleInfo parentModule, bool found, IEnumerable modulePath, string name, SessionState ss, - ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, out PSModuleInfo module) + internal bool LoadUsingModulePath( + PSModuleInfo parentModule, + IEnumerable modulePath, + string name, + SessionState ss, + ImportModuleOptions options, + ManifestProcessingFlags manifestProcessingFlags, + out PSModuleInfo module) { string extension = Path.GetExtension(name); string fileBaseName; @@ -325,11 +353,18 @@ internal bool LoadUsingModulePath(PSModuleInfo parentModule, bool found, IEnumer extension = null; } else + { fileBaseName = name.Substring(0, name.Length - extension.Length); + } // Now search using the module path... + bool found = false; foreach (string path in modulePath) { + if (options.SkipSystem32ModulesAndSuppressError && ModuleUtils.IsOnSystem32ModulePath(path)) + { + continue; + } #if UNIX foreach (string folder in Directory.EnumerateDirectories(path)) { @@ -342,7 +377,7 @@ internal bool LoadUsingModulePath(PSModuleInfo parentModule, bool found, IEnumer module = LoadUsingMultiVersionModuleBase(qualifiedPath, manifestProcessingFlags, options, out found); if (!found) { - if (name.IndexOfAny(Utils.Separators.Directory) == -1) + if (name.AsSpan().IndexOfAny('\\', '/') == -1) { qualifiedPath = Path.Combine(qualifiedPath, fileBaseName); } @@ -643,9 +678,19 @@ private bool ValidateManifestHash( return result; } - private PSModuleInfo LoadModuleNamedInManifest(PSModuleInfo parentModule, ModuleSpecification moduleSpecification, string moduleBase, bool searchModulePath, - string prefix, SessionState ss, ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, bool loadTypes, - bool loadFormats, object privateData, out bool found, string shortModuleName, PSLanguageMode? manifestLanguageMode) + private PSModuleInfo LoadModuleNamedInManifest( + PSModuleInfo parentModule, + ModuleSpecification moduleSpecification, + string moduleBase, + bool searchModulePath, + string prefix, + SessionState ss, + ImportModuleOptions options, + ManifestProcessingFlags manifestProcessingFlags, + object privateData, + out bool found, + string shortModuleName, + PSLanguageMode? manifestLanguageMode) { PSModuleInfo module = null; PSModuleInfo tempModuleInfoFromVerification = null; @@ -659,11 +704,16 @@ private PSModuleInfo LoadModuleNamedInManifest(PSModuleInfo parentModule, Module var importingModule = manifestProcessingFlags.HasFlag(ManifestProcessingFlags.LoadElements); string extension = Path.GetExtension(moduleSpecification.Name); + // First check for fully-qualified paths - either absolute or relative string rootedPath = ResolveRootedFilePath(moduleSpecification.Name, this.Context); if (string.IsNullOrEmpty(rootedPath)) { - rootedPath = FixupFileName(moduleBase, moduleSpecification.Name, extension, importingModule); + // Use the name of the parent module if it's specified, otherwise, use the current module name. + // - If the current module is a nested module, then the parent module will be specified. + // - If the current module is a root module, then the parent module will not be specified. + string moduleName = parentModule?.Name ?? ModuleIntrinsics.GetModuleName(moduleSpecification.Name); + rootedPath = FixFileName(moduleName, moduleBase, moduleSpecification.Name, extension: null, canLoadAssembly: importingModule); } else { @@ -823,7 +873,7 @@ private PSModuleInfo LoadModuleNamedInManifest(PSModuleInfo parentModule, Module } // Otherwise try the module path - found = LoadUsingModulePath(parentModule, found, modulePath, + found = LoadUsingModulePath(parentModule, modulePath, moduleSpecification.Name, ss, options, manifestProcessingFlags, out module); } @@ -836,11 +886,21 @@ private PSModuleInfo LoadModuleNamedInManifest(PSModuleInfo parentModule, Module // Constrained Language session. if (module.LanguageMode != manifestLanguageMode) { - var languageModeError = PSTraceSource.NewInvalidOperationException( - Modules.MismatchedLanguageModes, - module.Name, manifestLanguageMode, module.LanguageMode); - languageModeError.SetErrorId("Modules_MismatchedLanguageModes"); - throw languageModeError; + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + var languageModeError = PSTraceSource.NewInvalidOperationException( + Modules.MismatchedLanguageModes, + module.Name, manifestLanguageMode, module.LanguageMode); + languageModeError.SetErrorId("Modules_MismatchedLanguageModes"); + throw languageModeError; + } + + SystemPolicy.LogWDACAuditMessage( + context: Context, + title: Modules.WDACMismatchedLanguageModesTitle, + message: Modules.WDACMismatchedLanguageModesMessage, + fqid: "ModulesMismatchedLanguageModes", + dropIntoDebugger: true); } } @@ -878,7 +938,6 @@ private PSModuleInfo LoadModuleNamedInManifest(PSModuleInfo parentModule, Module // At this point, we are already exhaust all possible ways to load the nested module. The last option is to load it as a binary module/snapin. module = LoadBinaryModule( parentModule, - trySnapInName: true, moduleSpecification.Name, fileName: null, assemblyToLoad: null, @@ -887,8 +946,6 @@ private PSModuleInfo LoadModuleNamedInManifest(PSModuleInfo parentModule, Module options, manifestProcessingFlags, prefix, - loadTypes, - loadFormats, out found, shortModuleName, disableFormatUpdates: false); @@ -963,7 +1020,7 @@ private IEnumerable GetModuleForRootedPaths(List modulePat { bool containsWildCards = false; - string modulePath = mp.TrimEnd(Utils.Separators.Backslash); + string modulePath = mp.TrimEnd('\\'); // If the given path contains wildcards, we won't throw error if no match module path is found. if (WildcardPattern.ContainsWildcardCharacters(modulePath)) @@ -986,9 +1043,8 @@ private IEnumerable GetModuleForRootedPaths(List modulePat PSModuleInfo module = CreateModuleInfoForGetModule(resolvedModulePath, refresh); if (module != null) { - if (!modules.Contains(resolvedModulePath)) + if (modules.Add(resolvedModulePath)) { - modules.Add(resolvedModulePath); yield return module; } } @@ -1017,9 +1073,8 @@ private IEnumerable GetModuleForRootedPaths(List modulePat foundModule = true; // We need to list all versions of the module. string subModulePath = Path.GetDirectoryName(file); - if (!modules.Contains(subModulePath)) + if (modules.Add(subModulePath)) { - modules.Add(subModulePath); yield return module; } } @@ -1056,35 +1111,27 @@ private IEnumerable GetModuleForNames(List names, bool all { IEnumerable allModules = null; HashSet modulePathSet = new HashSet(StringComparer.OrdinalIgnoreCase); - bool cleanupModuleAnalysisAppDomain = Context.TakeResponsibilityForModuleAnalysisAppDomain(); - try + foreach (string path in ModuleIntrinsics.GetModulePath(false, Context)) { - foreach (string path in ModuleIntrinsics.GetModulePath(false, Context)) - { - string uniquePath = path.TrimEnd(Utils.Separators.Directory); + string uniquePath = path.TrimEnd(Utils.Separators.Directory); - // Ignore repeated module path. - if (!modulePathSet.Add(uniquePath)) { continue; } + // Ignore repeated module path. + if (!modulePathSet.Add(uniquePath)) + { + continue; + } - try - { - IEnumerable modulesFound = GetModulesFromOneModulePath( - names, uniquePath, all, refresh).OrderBy(m => m.Name); - allModules = allModules == null ? modulesFound : allModules.Concat(modulesFound); - } - catch (Exception e) when (e is IOException || e is UnauthorizedAccessException) - { - // ignore directories that can't be accessed - continue; - } + try + { + IEnumerable modulesFound = GetModulesFromOneModulePath( + names, uniquePath, all, refresh).OrderBy(static m => m.Name); + allModules = allModules == null ? modulesFound : allModules.Concat(modulesFound); } - } - finally - { - if (cleanupModuleAnalysisAppDomain) + catch (Exception e) when (e is IOException || e is UnauthorizedAccessException) { - Context.ReleaseResponsibilityForModuleAnalysisAppDomain(); + // ignore directories that can't be accessed + continue; } } @@ -1146,7 +1193,7 @@ internal static Version GetMaximumVersion(string stringVersion) { stringVersion = stringVersion.Substring(0, stringVersion.Length - 1); stringVersion += maxRange; - int starNum = stringVersion.Count(x => x == '.'); + int starNum = stringVersion.Count(static x => x == '.'); for (int i = 0; i < (3 - starNum); i++) { stringVersion = stringVersion + '.' + maxRange; @@ -1494,6 +1541,7 @@ internal PSModuleInfo LoadModuleManifest( Dbg.Assert(moduleManifestPath != null, "moduleManifestPath for module (.psd1) can't be null"); string moduleBase = Path.GetDirectoryName(moduleManifestPath); + string moduleName = ModuleIntrinsics.GetModuleName(moduleManifestPath); if ((manifestProcessingFlags & (ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | @@ -1594,24 +1642,32 @@ internal PSModuleInfo LoadModuleManifest( invalidOperation.SetErrorId("Modules_WildCardNotAllowedInModuleToProcessAndInNestedModules"); throw invalidOperation; } + // See if this module is already loaded. Since the manifest entry may not // have an extension and the module table is indexed by full names, we // may have search through all the extensions. PSModuleInfo loadedModule = null; - string rootedPath = this.FixupFileName(moduleBase, actualRootModule, extension: null, importingModule); - string mtpExtension = Path.GetExtension(rootedPath); - if (!string.IsNullOrEmpty(mtpExtension) && ModuleIntrinsics.IsPowerShellModuleExtension(mtpExtension)) + string rootedPath = null; + + // For a root module, we use its own module name instead of the manifest module name when calling 'FixFileName'. + // This is because when actually loading the root module later, it won't have access to the parent manifest module, + // and we will use its own name to query for already loaded assemblies from 'Context.AssemblyCache'. + string rootModuleName = ModuleIntrinsics.GetModuleName(actualRootModule); + string extension = Path.GetExtension(actualRootModule); + if (!string.IsNullOrEmpty(extension) && ModuleIntrinsics.IsPowerShellModuleExtension(extension)) { + rootedPath = FixFileName(rootModuleName, moduleBase, actualRootModule, extension: null, canLoadAssembly: importingModule); TryGetFromModuleTable(rootedPath, out loadedModule); } else { foreach (string extensionToTry in ModuleIntrinsics.PSModuleExtensions) { - rootedPath = this.FixupFileName(moduleBase, actualRootModule, extensionToTry, importingModule); - TryGetFromModuleTable(rootedPath, out loadedModule); - if (loadedModule != null) + rootedPath = FixFileName(rootModuleName, moduleBase, actualRootModule, extensionToTry, canLoadAssembly: importingModule); + if (TryGetFromModuleTable(rootedPath, out loadedModule)) + { break; + } } } @@ -1860,9 +1916,12 @@ internal PSModuleInfo LoadModuleManifest( else if ((requiredProcessorArchitecture != ProcessorArchitecture.None) && (requiredProcessorArchitecture != ProcessorArchitecture.MSIL)) { - ProcessorArchitecture currentArchitecture = typeof(object).Assembly.GetName().ProcessorArchitecture; + Architecture currentArchitecture = RuntimeInformation.ProcessArchitecture; - if (currentArchitecture != requiredProcessorArchitecture) + if ((requiredProcessorArchitecture == ProcessorArchitecture.X86 && currentArchitecture != Architecture.X86) || + (requiredProcessorArchitecture == ProcessorArchitecture.Amd64 && currentArchitecture != Architecture.X64) || + (requiredProcessorArchitecture == ProcessorArchitecture.Arm && (currentArchitecture != Architecture.Arm && currentArchitecture != Architecture.Arm64)) || + requiredProcessorArchitecture == ProcessorArchitecture.IA64) { containedErrors = true; if (writingErrors) @@ -2017,7 +2076,6 @@ internal PSModuleInfo LoadModuleManifest( { bool nameMissingOrEmpty = false; var invalidNames = new List(); - string moduleName = ModuleIntrinsics.GetModuleName(moduleManifestPath); expFeatureList = new List(features.Length); foreach (Hashtable feature in features) @@ -2130,61 +2188,72 @@ internal PSModuleInfo LoadModuleManifest( // Indicates the ISS.Bind() should be called... bool doBind = false; - // Set up to load any required assemblies that have been specified... - List tmpAssemblyList; - List assemblyList = new List(); - List fixedUpAssemblyPathList = new List(); - - if ( - !GetListOfStringsFromData(data, moduleManifestPath, "RequiredAssemblies", manifestProcessingFlags, - out tmpAssemblyList)) + if (!GetListOfStringsFromData( + data, + moduleManifestPath, + "RequiredAssemblies", + manifestProcessingFlags, + out List assemblyList)) { containedErrors = true; - if (bailOnFirstError) return null; + if (bailOnFirstError) + { + return null; + } } - else + else if (assemblyList != null && importingModule) { - if (tmpAssemblyList != null && tmpAssemblyList.Count > 0) + foreach (string assembly in assemblyList) { - foreach (string assembly in tmpAssemblyList) + if (WildcardPattern.ContainsWildcardCharacters(assembly)) { - assemblyList.Add(assembly); + PSInvalidOperationException invalidOperation = PSTraceSource.NewInvalidOperationException( + Modules.WildCardNotAllowedInRequiredAssemblies, + moduleManifestPath); + invalidOperation.SetErrorId("Modules_WildCardNotAllowedInRequiredAssemblies"); + throw invalidOperation; } - } - - if ((assemblyList != null) && importingModule) - { - foreach (string assembly in assemblyList) + else { - if (WildcardPattern.ContainsWildcardCharacters(assembly)) + string fileName = null; + string ext = Path.GetExtension(assembly); + + // Note that we don't need to load the required assemblies eagerly because they will be loaded before + // processing type and format data. So, when calling 'FixupFileName', we only attempt to resolve the + // path, and avoid triggering the loading of the assembly. + if (ModuleIntrinsics.ProcessableAssemblyExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase)) { - PSInvalidOperationException invalidOperation = PSTraceSource.NewInvalidOperationException( - Modules.WildCardNotAllowedInRequiredAssemblies, - moduleManifestPath); - invalidOperation.SetErrorId("Modules_WildCardNotAllowedInRequiredAssemblies"); - throw invalidOperation; + fileName = FixFileNameWithoutLoadingAssembly(moduleBase, assembly, extension: null); } else { - string fileName = FixupFileName(moduleBase, assembly, StringLiterals.PowerShellNgenAssemblyExtension, importingModule, out bool pathIsResolved); - if (!pathIsResolved) + bool isPathResolved = false; + foreach (string extToTry in ModuleIntrinsics.ProcessableAssemblyExtensions) + { + fileName = FixFileNameWithoutLoadingAssembly(moduleBase, assembly, extToTry, out isPathResolved); + if (isPathResolved) + { + break; + } + } + + if (!isPathResolved) { - fileName = FixupFileName(moduleBase, assembly, StringLiterals.PowerShellILAssemblyExtension, importingModule); + // We didn't resolve the assembly path, so remove the '.exe' extension that was added in the + // last iteration of the above loop. + int index = fileName.LastIndexOf('.'); + fileName = fileName.Substring(0, index); } + } - string loadMessage = StringUtil.Format(Modules.LoadingFile, "Assembly", fileName); - WriteVerbose(loadMessage); - iss.Assemblies.Add(new SessionStateAssemblyEntry(assembly, fileName)); - fixedUpAssemblyPathList.Add(fileName); + WriteVerbose(StringUtil.Format(Modules.LoadingFile, "Assembly", fileName)); - fileName = FixupFileName(moduleBase, assembly, StringLiterals.PowerShellILExecutableExtension, importingModule); - loadMessage = StringUtil.Format(Modules.LoadingFile, "Executable", fileName); - WriteVerbose(loadMessage); - iss.Assemblies.Add(new SessionStateAssemblyEntry(assembly, fileName)); - fixedUpAssemblyPathList.Add(fileName); + // Set a fake PSModuleInfo object to indicate the module it comes from. + var assemblyEntry = new SessionStateAssemblyEntry(assembly, fileName); + assemblyEntry.SetModule(new PSModuleInfo(moduleName, path: null, context: null, sessionState: null)); - doBind = true; - } + iss.Assemblies.Add(assemblyEntry); + doBind = true; } } } @@ -2221,8 +2290,7 @@ internal PSModuleInfo LoadModuleManifest( continue; } - string resolvedEntryFileName = ResolveRootedFilePath(entry.FileName, Context) ?? - entry.FileName; + string resolvedEntryFileName = ResolveRootedFilePath(entry.FileName, Context) ?? entry.FileName; if (resolvedEntryFileName.Equals(resolvedFileName, StringComparison.OrdinalIgnoreCase)) { isAlreadyLoaded = true; @@ -2345,7 +2413,7 @@ internal PSModuleInfo LoadModuleManifest( key: "FileList", manifestProcessingFlags, moduleBase, - extension: string.Empty, + extension: null, // Don't check file existence - don't want to change current behavior without feature team discussion. verifyFilesExist: false, out List fileList)) @@ -2384,17 +2452,13 @@ internal PSModuleInfo LoadModuleManifest( { if (importingModule) { - IList moduleProxies = ImportModulesUsingWinCompat(new string[] { moduleManifestPath }, null, options); + IList moduleProxies = ImportModulesUsingWinCompat( + moduleNames: new string[] { moduleManifestPath }, + moduleFullyQualifiedNames: null, + importModuleOptions: options); - // we are loading by a single ManifestPath so expect max of 1 - if (moduleProxies.Count > 0) - { - return moduleProxies[0]; - } - else - { - return null; - } + // We are loading by a single ManifestPath so expect max of 1 + return moduleProxies.Count > 0 ? moduleProxies[0] : null; } } else @@ -2489,38 +2553,27 @@ internal PSModuleInfo LoadModuleManifest( // If there is a session state, set up to import/export commands and variables if (ss != null) { - ss.Internal.SetVariable(SpecialVariables.PSScriptRootVarPath, Path.GetDirectoryName(moduleManifestPath), - true, CommandOrigin.Internal); - ss.Internal.SetVariable(SpecialVariables.PSCommandPathVarPath, moduleManifestPath, true, + ss.Internal.SetVariable( + SpecialVariables.PSScriptRootVarPath, + moduleBase, + asValue: true, CommandOrigin.Internal); + + ss.Internal.SetVariable( + SpecialVariables.PSCommandPathVarPath, + moduleManifestPath, + asValue: true, + CommandOrigin.Internal); + ss.Internal.Module = manifestInfo; // without ModuleToProcess a manifest will export everything by default // (otherwise we want to honour exports from ModuleToProcess) - if (exportedFunctions == null) - { - exportedFunctions = MatchAll; - } - - if (exportedCmdlets == null) - { - exportedCmdlets = MatchAll; - } - - if (exportedVariables == null) - { - exportedVariables = MatchAll; - } - - if (exportedAliases == null) - { - exportedAliases = MatchAll; - } - - if (exportedDscResources == null) - { - exportedDscResources = MatchAll; - } + exportedAliases ??= MatchAll; + exportedCmdlets ??= MatchAll; + exportedDscResources ??= MatchAll; + exportedFunctions ??= MatchAll; + exportedVariables ??= MatchAll; } manifestInfo.Description = description; @@ -2946,8 +2999,6 @@ internal PSModuleInfo LoadModuleManifest( ss: null, options: nestedModuleOptions, manifestProcessingFlags: manifestProcessingFlags, - loadTypes: true, - loadFormats: true, privateData: privateData, found: out found, shortModuleName: null, @@ -3049,8 +3100,6 @@ internal PSModuleInfo LoadModuleManifest( ss: ss, options: options, manifestProcessingFlags: manifestProcessingFlags, - loadTypes: (exportedTypeFiles == null || exportedTypeFiles.Count == 0), // If types files already loaded, don't load snapin files - loadFormats: (exportedFormatFiles == null || exportedFormatFiles.Count == 0), // if format files already loaded, don't load snapin files privateData: privateData, found: out found, shortModuleName: null, @@ -3155,16 +3204,10 @@ internal PSModuleInfo LoadModuleManifest( } } - if (newManifestInfo.RootModule == null) - { - newManifestInfo.RootModule = manifestInfo.RootModule; - } + newManifestInfo.RootModule ??= manifestInfo.RootModule; // If may be the case that a script has already set the PrivateData field in the module // info object, in which case we won't overwrite it. - if (newManifestInfo.PrivateData == null) - { - newManifestInfo.PrivateData = manifestInfo.PrivateData; - } + newManifestInfo.PrivateData ??= manifestInfo.PrivateData; // Assign the PowerShellGet related properties from the module manifest foreach (var tag in manifestInfo.Tags) @@ -3289,10 +3332,7 @@ internal PSModuleInfo LoadModuleManifest( } } - if (newManifestInfo.RootModuleForManifest == null) - { - newManifestInfo.RootModuleForManifest = manifestInfo.RootModuleForManifest; - } + newManifestInfo.RootModuleForManifest ??= manifestInfo.RootModuleForManifest; if (newManifestInfo.DeclaredCmdletExports == null || newManifestInfo.DeclaredCmdletExports.Count == 0) { @@ -3384,13 +3424,25 @@ internal PSModuleInfo LoadModuleManifest( if ((ss != null) && (!ss.Internal.UseExportList)) { // For cross language boundaries, implicitly import all functions only if - // this manifest *does* exort functions explicitly. + // this manifest *does* export functions explicitly. List fnMatchPattern = ( (manifestScriptInfo.DefiningLanguageMode == PSLanguageMode.FullLanguage) && (Context.LanguageMode != PSLanguageMode.FullLanguage) && (exportedFunctions == null) ) ? null : MatchAll; + // If the system is in WDAC policy AUDIT mode, then an export functions restriction should be reported but not applied. + if (fnMatchPattern == null && SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Audit) + { + SystemPolicy.LogWDACAuditMessage( + context: Context, + title: Modules.WDACImplicitFunctionExportLogTitle, + message: StringUtil.Format(Modules.WDACImplicitFunctionExportLogMessage, manifestScriptInfo.ModuleName), + fqid: "ModuleImplicitFunctionExportNotAllowed", + dropIntoDebugger: true); + fnMatchPattern = MatchAll; + } + ModuleIntrinsics.ExportModuleMembers(cmdlet: this, sessionState: ss.Internal, functionPatterns: fnMatchPattern, @@ -3715,7 +3767,7 @@ internal static object IsModuleLoaded(ExecutionContext context, ModuleSpecificat // If the RequiredModule is one of the Engine modules, then they could have been loaded as snapins (using InitialSessionState.CreateDefault()) if (result == null && InitialSessionState.IsEngineModule(requiredModule.Name)) { - result = ModuleCmdletBase.GetEngineSnapIn(context, requiredModule.Name); + result = context.CurrentRunspace.InitialSessionState.GetPSSnapIn(requiredModule.Name); if (result != null) { loaded = true; @@ -4375,8 +4427,6 @@ private bool GetListOfFilesFromData( out List list) { list = null; - - bool importingModule = manifestProcessingFlags.HasFlag(ManifestProcessingFlags.LoadElements); if (!GetListOfStringsFromData(data, moduleManifestPath, key, manifestProcessingFlags, out List listOfStrings)) { return false; @@ -4398,7 +4448,7 @@ private bool GetListOfFilesFromData( { try { - string fixedFileName = FixupFileName(moduleBase, s, extension, importingModule, skipLoading: true); + string fixedFileName = FixFileNameWithoutLoadingAssembly(moduleBase, s, extension); var dir = Path.GetDirectoryName(fixedFileName); if (string.Equals(psHome, dir, StringComparison.OrdinalIgnoreCase) || @@ -4509,7 +4559,7 @@ internal static ModuleLoggingGroupPolicyStatus GetModuleLoggingInformation(out I /// /// Checks to see if the module manifest contains the specified key. /// If it does and it can be converted to the expected type, then it returns and sets to the value. - /// If the key is missing it returns and sets to default(). + /// If the key is missing it returns and sets to default(). /// If the key is invalid then it returns . /// /// The hashtable to look for the key in. @@ -4553,39 +4603,58 @@ internal bool GetScalarFromData( } } + private string FixFileNameWithoutLoadingAssembly(string moduleBase, string fileName, string extension) + { + return FixFileName(moduleName: null, moduleBase, fileName, extension, canLoadAssembly: false, pathIsResolved: out _); + } + + private string FixFileNameWithoutLoadingAssembly(string moduleBase, string fileName, string extension, out bool pathIsResolved) + { + return FixFileName(moduleName: null, moduleBase, fileName, extension, canLoadAssembly: false, out pathIsResolved); + } + /// /// A utility routine to fix up a file name so it's rooted and has an extension. /// - internal string FixupFileName(string moduleBase, string name, string extension, bool isImportingModule, bool skipLoading = false) + private string FixFileName(string moduleName, string moduleBase, string fileName, string extension, bool canLoadAssembly) { - return FixupFileName(moduleBase, name, extension, isImportingModule, pathIsResolved: out _, skipLoading); + return FixFileName(moduleName, moduleBase, fileName, extension, canLoadAssembly, pathIsResolved: out _); } /// /// A utility routine to fix up a file name so it's rooted and has an extension. /// /// - /// When fixing up an assembly file, this method loads the resovled assembly if it's in the process of actually loading a module. + /// When fixing up an assembly file, this method loads the resolved assembly if it's in the process of actually loading a module. /// Read the comments in the method for the detailed information. /// + /// Name of the module that we are processing, used for caching purpose when we need to load an assembly. /// The base path to use if the file is not rooted. - /// The file name to resolve. - /// The extension to use in case the given name has no extension. - /// Indicate if we are loading a module. + /// The file name to resolve. + /// The extension to use for the look up. + /// Indicate if we can load assembly for the resolution. /// Indicate if the returned path is fully resolved. - /// Indicate if the resolved module should be loaded. /// - /// The resolved file path. Or, the combined path of and when the file path cannot be resolved. + /// The resolved file path. Or, the combined path of and when the file path cannot be resolved. /// - internal string FixupFileName(string moduleBase, string name, string extension, bool isImportingModule, out bool pathIsResolved, bool skipLoading = false) + private string FixFileName(string moduleName, string moduleBase, string fileName, string extension, bool canLoadAssembly, out bool pathIsResolved) { pathIsResolved = false; - string originalName = name; - string originalExt = Path.GetExtension(name); - if (string.IsNullOrEmpty(originalExt)) + string originalName = fileName; + string originalExt = Path.GetExtension(fileName); + + if (string.IsNullOrEmpty(extension)) { - name += extension; + // When 'extension' is not explicitly specified, we honor the original extension. + extension = originalExt; + } + else if (!extension.Equals(originalExt, StringComparison.OrdinalIgnoreCase)) + { + // When 'extension' is explicitly specified, append it if the original extension is different. + // Note: the original extension could actually be part of the file name. For example, the name + // is `Microsoft.PowerShell.Command.Utility`, in which case the extension is `.Utility`. + fileName += extension; } // Try to get the resolved fully qualified path to the file. @@ -4597,24 +4666,24 @@ internal string FixupFileName(string moduleBase, string name, string extension, // Check for combinedPath in this case will get us the normalized rooted path 'C:\Windows\System32\WindowsPowerShell\v1.0\WSMan.format.ps1xml'. // The 'Microsoft.WSMan.Management' module in PowerShell was updated to not use the relative path for 'FormatsToProcess' entry, // but it's safer to keep the original behavior to avoid unexpected breaking changes. - string combinedPath = Path.Combine(moduleBase, name); - string resolvedPath = IsRooted(name) - ? ResolveRootedFilePath(name, Context) ?? ResolveRootedFilePath(combinedPath, Context) + string combinedPath = Path.Combine(moduleBase, fileName); + string resolvedPath = IsRooted(fileName) + ? ResolveRootedFilePath(fileName, Context) ?? ResolveRootedFilePath(combinedPath, Context) : ResolveRootedFilePath(combinedPath, Context); // Return the path if successfully resolved. - if (resolvedPath != null) + if (resolvedPath is not null) { - if (isImportingModule && resolvedPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) && !skipLoading) + if (canLoadAssembly && resolvedPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) { // If we are fixing up an assembly file path and we are actually loading the module, then we load the resolved assembly file here. - // This is because we process type/format ps1xml files before 'RootModule' and 'NestedModules' entries during the module loading. - // A types.ps1xml file could refer to a type defined in the assembly that is specified in the 'RootModule' or 'NestedModule', and - // in that case, processing the types.ps1xml file would fail because it happens before processing the 'RootModule', which loads - // the assembly. We cannot move the processing of types.ps1xml file after processing 'RootModule' either, because the 'RootModule' - // might refer to members defined in the types.ps1xml file. In order to make it work for this paradox, we have to load the resolved - // assembly when we are actually loading the module. However, when it's module analysis, there is no need to load the assembly. - ExecutionContext.LoadAssembly(name: null, filename: resolvedPath, error: out _); + // This is because we process type/format ps1xml files before 'RootModule' during the module loading. A types.ps1xml file could + // refer to a type defined in the assembly that is specified in the 'RootModule', and in that case, processing the types.ps1xml file + // would fail because it happens before processing the 'RootModule', which loads the assembly. + // We cannot move the processing of types.ps1xml file after processing 'RootModule' either, because the 'RootModule' might refer to + // members defined in the types.ps1xml file. In order to make it work for this paradox, we have to load the resolved assembly when + // we are actually loading the module. However, when it's module analysis, there is no need to load the assembly. + Context.AddAssembly(source: moduleName, assemblyName: null, filePath: resolvedPath, error: out _); } pathIsResolved = true; @@ -4627,12 +4696,12 @@ internal string FixupFileName(string moduleBase, string name, string extension, // For dlls, we cannot get the path from the provider. // We need to load the assembly and then get the path. // If the module is already loaded, this is not expensive since the assembly is already loaded in the AppDomain - if (!string.IsNullOrEmpty(extension) && + if (canLoadAssembly && !string.IsNullOrEmpty(extension) && (extension.Equals(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase) || - extension.Equals(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase))) + extension.Equals(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase))) { - Assembly assembly = ExecutionContext.LoadAssembly(name: originalName, filename: null, error: out _); - if (assembly != null) + Assembly assembly = Context.AddAssembly(source: moduleName, assemblyName: originalName, filePath: null, error: out _); + if (assembly is not null) { pathIsResolved = true; result = assembly.Location; @@ -4887,12 +4956,12 @@ internal static void SyncCurrentLocationHandler(object sender, LocationChangedEv using var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); ps.AddCommand(new CmdletInfo("Invoke-Command", typeof(InvokeCommandCommand))); ps.AddParameter("Session", compatSession); - ps.AddParameter("ScriptBlock", ScriptBlock.Create(string.Format("Set-Location -Path '{0}'", args.NewPath.Path))); + ps.AddParameter("ScriptBlock", ScriptBlock.Create(string.Create(CultureInfo.InvariantCulture, $"Set-Location -Path '{args.NewPath.Path}'"))); ps.Invoke(); } } - internal static System.EventHandler SyncCurrentLocationDelegate; + internal static EventHandler SyncCurrentLocationDelegate; internal virtual IList ImportModulesUsingWinCompat(IEnumerable moduleNames, IEnumerable moduleFullyQualifiedNames, ImportModuleOptions importModuleOptions) { throw new System.NotImplementedException(); } @@ -4946,8 +5015,6 @@ internal void RemoveModule(PSModuleInfo module) /// Module name specified in the cmdlet. internal void RemoveModule(PSModuleInfo module, string moduleNameInRemoveModuleCmdlet) { - bool isTopLevelModule = false; - // if the module path is empty string, means it is a dynamically generated assembly. // We have set the module path to be module name as key to make it unique, we need update here as well in case the module can be removed. if (module.Path == string.Empty) @@ -4955,7 +5022,7 @@ internal void RemoveModule(PSModuleInfo module, string moduleNameInRemoveModuleC module.Path = module.Name; } - bool shouldModuleBeRemoved = ShouldModuleBeRemoved(module, moduleNameInRemoveModuleCmdlet, out isTopLevelModule); + bool shouldModuleBeRemoved = ShouldModuleBeRemoved(module, moduleNameInRemoveModuleCmdlet, out bool isTopLevelModule); if (shouldModuleBeRemoved) { @@ -4963,17 +5030,14 @@ internal void RemoveModule(PSModuleInfo module, string moduleNameInRemoveModuleC if (Context.Modules.ModuleTable.ContainsKey(module.Path)) { // We should try to run OnRemove as the very first thing - if (module.OnRemove != null) - { - module.OnRemove.InvokeUsingCmdlet( - contextCmdlet: this, - useLocalScope: true, - errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, - dollarUnder: AutomationNull.Value, - input: AutomationNull.Value, - scriptThis: AutomationNull.Value, - args: new object[] { module }); - } + module.OnRemove?.InvokeUsingCmdlet( + contextCmdlet: this, + useLocalScope: true, + errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, + dollarUnder: AutomationNull.Value, + input: AutomationNull.Value, + scriptThis: AutomationNull.Value, + args: new object[] { module }); if (module.ImplementingAssembly != null && !module.ImplementingAssembly.IsDynamic) { @@ -5187,19 +5251,13 @@ internal void RemoveModule(PSModuleInfo module, string moduleNameInRemoveModuleC // And the appdomain level module path cache. PSModuleInfo.RemoveFromAppDomainLevelCache(module.Name); - // Update implicit module loaded property - if (Context.Modules.IsImplicitRemotingModuleLoaded) + // And remove the module assembly entries that may have been added from the assembly cache. + Context.RemoveFromAssemblyCache(source: module.Name); + if (module.ModuleType == ModuleType.Binary && !string.IsNullOrEmpty(module.RootModule)) { - Context.Modules.IsImplicitRemotingModuleLoaded = false; - foreach (var modInfo in Context.Modules.ModuleTable.Values) - { - var privateData = modInfo.PrivateData as Hashtable; - if ((privateData != null) && privateData.ContainsKey("ImplicitRemoting")) - { - Context.Modules.IsImplicitRemotingModuleLoaded = true; - break; - } - } + // We also need to clean up the cache entries that are possibly referenced by the root module in this case. + string rootModuleName = ModuleIntrinsics.GetModuleName(module.RootModule); + Context.RemoveFromAssemblyCache(source: rootModuleName); } } } @@ -5333,9 +5391,8 @@ internal PSModuleInfo LoadUsingExtensions(PSModuleInfo parentModule, string moduleName, string fileBaseName, string extension, string moduleBase, string prefix, SessionState ss, ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, out bool found) { - bool throwAwayModuleFileFound = false; return LoadUsingExtensions(parentModule, moduleName, fileBaseName, extension, moduleBase, prefix, ss, - options, manifestProcessingFlags, out found, out throwAwayModuleFileFound); + options, manifestProcessingFlags, out found, out _); } /// @@ -5436,7 +5493,6 @@ internal PSModuleInfo LoadUsingExtensions(PSModuleInfo parentModule, } else if (File.Exists(fileName)) { - moduleFileFound = true; // Win8: 325243 - Added the version check so that we do not unload modules with the same name but different version if (BaseForce && DoesAlreadyLoadedModuleSatisfyConstraints(module)) { @@ -5592,12 +5648,23 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str PSModuleInfo module = null; // Block ps1 files from being imported in constrained language. - if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage && ext.Equals(StringLiterals.PowerShellScriptFileExtension, StringComparison.OrdinalIgnoreCase)) + if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage && + ext.Equals(StringLiterals.PowerShellScriptFileExtension, StringComparison.OrdinalIgnoreCase)) { - InvalidOperationException invalidOp = new InvalidOperationException(Modules.ImportPSFileNotAllowedInConstrainedLanguage); - ErrorRecord er = new ErrorRecord(invalidOp, "Modules_ImportPSFileNotAllowedInConstrainedLanguage", - ErrorCategory.PermissionDenied, null); - ThrowTerminatingError(er); + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + InvalidOperationException invalidOp = new InvalidOperationException(Modules.ImportPSFileNotAllowedInConstrainedLanguage); + ErrorRecord er = new ErrorRecord(invalidOp, "Modules_ImportPSFileNotAllowedInConstrainedLanguage", + ErrorCategory.PermissionDenied, null); + ThrowTerminatingError(er); + } + + SystemPolicy.LogWDACAuditMessage( + context: Context, + title: Modules.WDACScriptFileImportLogTitle, + message: StringUtil.Format(Modules.WDACScriptFileImportLogMessage, fileName), + fqid: "ModuleImportScriptFilesNotAllowed", + dropIntoDebugger: true); } // If MinimumVersion/RequiredVersion/MaximumVersion has been specified, then only try to process manifest modules... @@ -5681,19 +5748,31 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str // If the script didn't call Export-ModuleMember explicitly, then // implicitly export functions and cmdlets. + var systemLockdownPolicy = SystemPolicy.GetSystemLockdownPolicy(); if (!module.SessionState.Internal.UseExportList) { // For cross language boundaries don't implicitly export all functions, unless they are allowed nested modules. - // Implict function export is allowed when any of the following is true: + // Implicit function export is allowed when any of the following is true: // - Nested modules are allowed by module manifest // - The import context language mode is FullLanguage - // - This script module not running as trusted (FullLanguage) + // - This script module is not running as trusted (FullLanguage) module.ModuleAutoExportsAllFunctions = options.AllowNestedModuleFunctionsToExport || Context.LanguageMode == PSLanguageMode.FullLanguage || psm1ScriptInfo.DefiningLanguageMode != PSLanguageMode.FullLanguage; - List fnMatchPattern = module.ModuleAutoExportsAllFunctions ? MatchAll : null; + // If the system is in WDAC policy AUDIT mode, then an export functions restriction should be reported but not applied. + if (fnMatchPattern == null && systemLockdownPolicy == SystemEnforcementMode.Audit) + { + SystemPolicy.LogWDACAuditMessage( + context: Context, + title: Modules.WDACImplicitFunctionExportLogTitle, + message: StringUtil.Format(Modules.WDACImplicitFunctionExportLogMessage, module.Name), + fqid: "ModuleImplicitFunctionExportNotAllowed", + dropIntoDebugger: true); + fnMatchPattern = MatchAll; + } + ModuleIntrinsics.ExportModuleMembers( cmdlet: this, sessionState: module.SessionState.Internal, @@ -5703,8 +5782,8 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str variablePatterns: null, doNotExportCmdlets: null); } - else if ((SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) && - (module.LanguageMode == PSLanguageMode.FullLanguage) && + else if ((systemLockdownPolicy == SystemEnforcementMode.Enforce || systemLockdownPolicy == SystemEnforcementMode.Audit) && + module.LanguageMode == PSLanguageMode.FullLanguage && module.SessionState.Internal.FunctionsExportedWithWildcard && !module.SessionState.Internal.ManifestWithExplicitFunctionExport) { @@ -5712,10 +5791,10 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str // exported functions only come from this module and not from any imported nested modules. // Unless there is a parent manifest that explicitly filters all exported functions (no wildcards). // This prevents unintended public exposure of imported functions running in FullLanguage. - ModuleIntrinsics.RemoveNestedModuleFunctions(module); + RemoveNestedModuleFunctions(Context, module, systemLockdownPolicy); } - CheckForDisallowedDotSourcing(module.SessionState, psm1ScriptInfo, options); + CheckForDisallowedDotSourcing(module, psm1ScriptInfo, options); // Add it to the all module tables ImportModuleMembers(module, prefix, options); @@ -5869,7 +5948,7 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str if (module != null) { - CheckForDisallowedDotSourcing(module.SessionState, psd1ScriptInfo, options); + CheckForDisallowedDotSourcing(module, psd1ScriptInfo, options); if (importingModule) { @@ -5892,7 +5971,7 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str ext.Equals(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase)) { module = LoadBinaryModule( - trySnapInName: false, + parentModule, ModuleIntrinsics.GetModuleName(fileName), fileName, assemblyToLoad: null, @@ -5901,8 +5980,6 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str options, manifestProcessingFlags, prefix, - loadTypes: true, - loadFormats: true, out found); if (found && module != null) @@ -6036,25 +6113,28 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str } private void CheckForDisallowedDotSourcing( - SessionState ss, + PSModuleInfo moduleInfo, ExternalScriptInfo scriptInfo, ImportModuleOptions options) { - if (ss == null || ss.Internal == null) - { return; } + if (moduleInfo.SessionState == null || moduleInfo.SessionState.Internal == null) + { + return; + } // A manifest with explicit function export is detected through a shared session state or the nested module options, because nested // module processing does not use a shared session state. - var manifestWithExplicitFunctionExport = ss.Internal.ManifestWithExplicitFunctionExport || options.AllowNestedModuleFunctionsToExport; + var manifestWithExplicitFunctionExport = moduleInfo.SessionState.Internal.ManifestWithExplicitFunctionExport || options.AllowNestedModuleFunctionsToExport; // If system is in lock down mode, we disallow trusted modules that use the dotsource operator while simultaneously using // wild cards for exporting module functions, unless there is an overriding manifest that explicitly exports functions // without wild cards. // This is because dotsourcing brings functions into module scope and it is too easy to inadvertently or maliciously // expose harmful private functions that run in trusted (FullLanguage) mode. - if (!manifestWithExplicitFunctionExport && ss.Internal.FunctionsExportedWithWildcard && - (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) && - (scriptInfo.DefiningLanguageMode == PSLanguageMode.FullLanguage)) + var systemLockdownPolicy = SystemPolicy.GetSystemLockdownPolicy(); + if (!manifestWithExplicitFunctionExport && moduleInfo.SessionState.Internal.FunctionsExportedWithWildcard && + (systemLockdownPolicy == SystemEnforcementMode.Enforce || systemLockdownPolicy == SystemEnforcementMode.Audit) && + scriptInfo.DefiningLanguageMode == PSLanguageMode.FullLanguage) { var dotSourceOperator = scriptInfo.GetScriptBlockAst().FindAll(ast => { @@ -6065,14 +6145,49 @@ private void CheckForDisallowedDotSourcing( if (dotSourceOperator != null) { - var errorRecord = new ErrorRecord( - new PSSecurityException(Modules.CannotUseDotSourceWithWildCardFunctionExport), - "Modules_SystemLockDown_CannotUseDotSourceWithWildCardFunctionExport", - ErrorCategory.SecurityError, null); - ThrowTerminatingError(errorRecord); + if (systemLockdownPolicy != SystemEnforcementMode.Audit) + { + var errorRecord = new ErrorRecord( + new PSSecurityException(Modules.CannotUseDotSourceWithWildCardFunctionExport), + "Modules_SystemLockDown_CannotUseDotSourceWithWildCardFunctionExport", + ErrorCategory.SecurityError, null); + ThrowTerminatingError(errorRecord); + } + + SystemPolicy.LogWDACAuditMessage( + context: Context, + title: Modules.WDACModuleDotSourceLogTitle, + message: StringUtil.Format(Modules.WDACModuleDotSourceLogMessage, moduleInfo.Name), + fqid: "ModuleImportDotSourceNotAllowed", + dropIntoDebugger: true); } } } + + private static void RemoveNestedModuleFunctions( + ExecutionContext context, + PSModuleInfo module, + SystemEnforcementMode systemLockdownPolicy) + { + var input = module.SessionState?.Internal?.ExportedFunctions; + if (input == null || input.Count == 0) + { + return; + } + + if (systemLockdownPolicy != SystemEnforcementMode.Audit) + { + input.RemoveAll(fnInfo => !module.Name.Equals(fnInfo.ModuleName, StringComparison.OrdinalIgnoreCase)); + return; + } + + SystemPolicy.LogWDACAuditMessage( + context: context, + title: Modules.WDACModuleFnExportWithNestedModulesLogTitle, + message: StringUtil.Format(Modules.WDACModuleFnExportWithNestedModulesLogMessage, module.Name), + fqid: "ModuleExportWithWildcardCharactersNotAllowed", + dropIntoDebugger: true); + } private static bool ShouldProcessScriptModule(PSModuleInfo parentModule, ref bool found) { @@ -6117,7 +6232,6 @@ private static void ClearAnalysisCaches() private static readonly Dictionary> s_binaryAnalysisCache = new Dictionary>(); -#if CORECLR /// /// Analyze the module assembly to find out all cmdlets and aliases defined in that assembly. /// @@ -6157,155 +6271,6 @@ private static BinaryAnalysisResult GetCmdletsFromBinaryModuleImplementation(str return resultToReturn; } -#else - /// - /// Analyze the module assembly to find out all cmdlets and aliases defined in that assembly. - /// - private BinaryAnalysisResult GetCmdletsFromBinaryModuleImplementation(string path, ManifestProcessingFlags manifestProcessingFlags, out Version assemblyVersion) - { - Tuple tuple = null; - - lock (s_lockObject) - { - s_binaryAnalysisCache.TryGetValue(path, out tuple); - } - - if (tuple != null) - { - assemblyVersion = tuple.Item2; - return tuple.Item1; - } - - assemblyVersion = new Version("0.0.0.0"); - - bool cleanupModuleAnalysisAppDomain = false; - AppDomain tempDomain = Context.AppDomainForModuleAnalysis; - if (tempDomain == null) - { - cleanupModuleAnalysisAppDomain = Context.TakeResponsibilityForModuleAnalysisAppDomain(); - tempDomain = Context.AppDomainForModuleAnalysis = AppDomain.CreateDomain("ReflectionDomain"); - } - - try - { - // create temp appdomain if one is not passed in - tempDomain.SetData("PathToProcess", path); - tempDomain.SetData("IsModuleLoad", 0 != (manifestProcessingFlags & ManifestProcessingFlags.LoadElements)); - - // reset DetectedCmdlets and AssemblyVersion from previous invocation - tempDomain.SetData("DetectedCmdlets", null); - tempDomain.SetData("DetectedAliases", null); - tempDomain.SetData("AssemblyVersion", assemblyVersion); - - tempDomain.DoCallBack(AnalyzeSnapinDomainHelper); - List detectedCmdlets = (List)tempDomain.GetData("DetectedCmdlets"); - List> detectedAliases = (List>)tempDomain.GetData("DetectedAliases"); - assemblyVersion = (Version)tempDomain.GetData("AssemblyVersion"); - - if ((detectedCmdlets.Count == 0) && (System.IO.Path.IsPathRooted(path))) - { - // If we couldn't load it from a file, try loading from the GAC - string assemblyname = Path.GetFileName(path); - BinaryAnalysisResult gacResult = GetCmdletsFromBinaryModuleImplementation(assemblyname, manifestProcessingFlags, out assemblyVersion); - detectedCmdlets = gacResult.DetectedCmdlets; - detectedAliases = gacResult.DetectedAliases; - } - - BinaryAnalysisResult result = new BinaryAnalysisResult(); - result.DetectedCmdlets = detectedCmdlets; - result.DetectedAliases = detectedAliases; - - lock (s_lockObject) - { - s_binaryAnalysisCache[path] = Tuple.Create(result, assemblyVersion); - } - - return result; - } - finally - { - if (cleanupModuleAnalysisAppDomain) - { - Context.ReleaseResponsibilityForModuleAnalysisAppDomain(); - } - } - } - - private static void AnalyzeSnapinDomainHelper() - { - string path = (string)AppDomain.CurrentDomain.GetData("PathToProcess"); - bool isModuleLoad = (bool)AppDomain.CurrentDomain.GetData("IsModuleLoad"); - Dictionary cmdlets = null; - Dictionary> aliases = null; - Dictionary providers = null; - string throwAwayHelpFile = null; - Version assemblyVersion = new Version("0.0.0.0"); - - try - { - Assembly assembly = null; - - try - { - // If this is a fully-qualified search, load it from the file - if (Path.IsPathRooted(path)) - { - assembly = InitialSessionState.LoadAssemblyFromFile(path); - } - else - { - // Otherwise, load it from the GAC - Exception ignored = null; - assembly = ExecutionContext.LoadAssembly(path, null, out ignored); - } - - if (assembly != null) - { - assemblyVersion = GetAssemblyVersionNumber(assembly); - } - } - // Catch-all OK, analyzing user code. - catch (Exception) - { - } - - if (assembly != null) - { - PSSnapInHelpers.AnalyzePSSnapInAssembly(assembly, assembly.Location, null, null, isModuleLoad, out cmdlets, out aliases, out providers, out throwAwayHelpFile); - } - } - // Catch-all OK, analyzing user code. - catch (Exception) - { - } - - List detectedCmdlets = new List(); - List> detectedAliases = new List>(); - - if (cmdlets != null) - { - foreach (SessionStateCmdletEntry cmdlet in cmdlets.Values) - { - detectedCmdlets.Add(cmdlet.Name); - } - } - - if (aliases != null) - { - foreach (List aliasList in aliases.Values) - { - foreach (SessionStateAliasEntry alias in aliasList) - { - detectedAliases.Add(new Tuple(alias.Name, alias.Definition)); - } - } - } - - AppDomain.CurrentDomain.SetData("DetectedCmdlets", detectedCmdlets); - AppDomain.CurrentDomain.SetData("DetectedAliases", detectedAliases); - AppDomain.CurrentDomain.SetData("AssemblyVersion", assemblyVersion); - } -#endif // Analyzes a script module implementation for its exports. private static readonly Dictionary s_scriptAnalysisCache = new Dictionary(); @@ -6435,7 +6400,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))) { @@ -6524,7 +6489,7 @@ private PSModuleInfo AnalyzeScriptFile(string filename, bool force, ExecutionCon /// /// Load a binary module. A binary module is an assembly that should contain cmdlets. /// - /// If true, then the registered snapins will also be searched when loading. + /// The parent module for which this module is a nested module. /// The name of the snapin or assembly to load. /// The path to the assembly to load. /// The assembly to load so no lookup need be done. @@ -6536,13 +6501,11 @@ private PSModuleInfo AnalyzeScriptFile(string filename, bool force, ExecutionCon /// /// The set of options that are used while importing a module. /// The manifest processing flags to use when processing the module. - /// Load the types files mentioned in the snapin registration. - /// Load the formst files mentioned in the snapin registration. /// Command name prefix. /// Sets this to true if an assembly was found. /// THe module info object that was created... internal PSModuleInfo LoadBinaryModule( - bool trySnapInName, + PSModuleInfo parentModule, string moduleName, string fileName, Assembly assemblyToLoad, @@ -6551,13 +6514,10 @@ internal PSModuleInfo LoadBinaryModule( ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, string prefix, - bool loadTypes, - bool loadFormats, out bool found) { return LoadBinaryModule( - parentModule: null, - trySnapInName, + parentModule, moduleName, fileName, assemblyToLoad, @@ -6566,8 +6526,6 @@ internal PSModuleInfo LoadBinaryModule( options, manifestProcessingFlags, prefix, - loadTypes, - loadFormats, out found, shortModuleName: null, disableFormatUpdates: false); @@ -6577,7 +6535,6 @@ internal PSModuleInfo LoadBinaryModule( /// Load a binary module. A binary module is an assembly that should contain cmdlets. /// /// The parent module for which this module is a nested module. - /// If true, then the registered snapins will also be searched when loading. /// The name of the snapin or assembly to load. /// The path to the assembly to load. /// The assembly to load so no lookup need be done. @@ -6590,15 +6547,12 @@ internal PSModuleInfo LoadBinaryModule( /// The set of options that are used while importing a module. /// The manifest processing flags to use when processing the module. /// Command name prefix. - /// Load the types files mentioned in the snapin registration. - /// Load the formst files mentioned in the snapin registration. /// Sets this to true if an assembly was found. /// Short name for module. /// /// THe module info object that was created... internal PSModuleInfo LoadBinaryModule( PSModuleInfo parentModule, - bool trySnapInName, string moduleName, string fileName, Assembly assemblyToLoad, @@ -6607,45 +6561,36 @@ internal PSModuleInfo LoadBinaryModule( ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, string prefix, - bool loadTypes, - bool loadFormats, out bool found, string shortModuleName, bool disableFormatUpdates) { - PSModuleInfo module = null; - if (string.IsNullOrEmpty(moduleName) && string.IsNullOrEmpty(fileName) && assemblyToLoad == null) + { throw PSTraceSource.NewArgumentNullException("moduleName,fileName,assemblyToLoad"); + } + + bool isParentEngineModule = parentModule != null && InitialSessionState.IsEngineModule(parentModule.Name); // Load the dll and process any cmdlets it might contain... InitialSessionState iss = InitialSessionState.Create(); List detectedCmdlets = null; List> detectedAliases = null; Assembly assembly = null; - Exception error = null; - bool importSuccessful = false; string modulePath = string.Empty; Version assemblyVersion = new Version(0, 0, 0, 0); - var importingModule = (manifestProcessingFlags & ManifestProcessingFlags.LoadElements) != 0; + bool importingModule = (manifestProcessingFlags & ManifestProcessingFlags.LoadElements) != 0; // See if we're loading a straight assembly... if (assemblyToLoad != null) { // Figure out what to use for a module path... - if (!string.IsNullOrEmpty(fileName)) - { - modulePath = fileName; - } - else - { - modulePath = assemblyToLoad.Location; - } + modulePath = string.IsNullOrEmpty(fileName) ? assemblyToLoad.Location : fileName; // And what to use for a module name... if (string.IsNullOrEmpty(moduleName)) { - moduleName = "dynamic_code_module_" + assemblyToLoad.GetName(); + moduleName = "dynamic_code_module_" + assemblyToLoad.FullName; } if (importingModule) @@ -6653,191 +6598,73 @@ internal PSModuleInfo LoadBinaryModule( // Passing module as a parameter here so that the providers can have the module property populated. // For engine providers, the module should point to top-level module name // For FileSystem, the module is Microsoft.PowerShell.Core and not System.Management.Automation - if (parentModule != null && InitialSessionState.IsEngineModule(parentModule.Name)) - { - iss.ImportCmdletsFromAssembly(assemblyToLoad, parentModule); - } - else - { - iss.ImportCmdletsFromAssembly(assemblyToLoad, null); - } + iss.ImportCmdletsFromAssembly(assemblyToLoad, isParentEngineModule ? parentModule : null); } assemblyVersion = GetAssemblyVersionNumber(assemblyToLoad); assembly = assemblyToLoad; - // If this is an in-memory only assembly, add it directly to the assembly cache if - // it isn't already there. - if (string.IsNullOrEmpty(assembly.Location)) - { - if (!Context.AssemblyCache.ContainsKey(assembly.FullName)) - { - Context.AssemblyCache.Add(assembly.FullName, assembly); - } - } + + // Use the parent module name for caching if there is one. + string source = parentModule?.Name ?? moduleName; + // Add it to the assembly cache if it isn't already there. + Context.AddToAssemblyCache(source, assembly); } - else + else if (importingModule) { - // Avoid trying to import a PowerShell assembly as Snapin as it results in PSArgumentException - if ((moduleName != null) && Utils.IsPowerShellAssembly(moduleName)) - { - trySnapInName = false; - } + // Use the parent module name for caching if there is one. + string source = parentModule?.Name ?? moduleName; + assembly = Context.AddAssembly(source, moduleName, fileName, out Exception error); - if (trySnapInName && PSSnapInInfo.IsPSSnapinIdValid(moduleName)) + if (assembly == null) { - PSSnapInInfo snapin = null; - -#if !CORECLR - // Avoid trying to load SnapIns with Import-Module - PSSnapInException warning; - try + if (error != null) { - if (importingModule) - { - snapin = iss.ImportPSSnapIn(moduleName, out warning); - } + throw error; } - catch (PSArgumentException) - { - // BUGBUG - brucepay - probably want to have a verbose message here... - } -#endif - if (snapin != null) - { - importSuccessful = true; - if (string.IsNullOrEmpty(fileName)) - modulePath = snapin.AbsoluteModulePath; - else - modulePath = fileName; - assemblyVersion = snapin.Version; - // If we're not supposed to load the types files from the snapin - // clear the iss member - if (!loadTypes) - { - iss.Types.Reset(); - } - // If we're not supposed to load the format files from the snapin, - // clear the iss member - if (!loadFormats) - { - iss.Formats.Reset(); - } - - foreach (var a in ClrFacade.GetAssemblies()) - { - if (a.GetName().FullName.Equals(snapin.AssemblyName, StringComparison.Ordinal)) - { - assembly = a; - break; - } - } - } + found = false; + return null; } - if (!importSuccessful) - { - if (importingModule) - { - assembly = Context.AddAssembly(moduleName, fileName, out error); - - if (assembly == null) - { - if (error != null) - throw error; - - found = false; - return null; - } + assemblyVersion = GetAssemblyVersionNumber(assembly); + modulePath = string.IsNullOrEmpty(fileName) ? assembly.Location : fileName; - assemblyVersion = GetAssemblyVersionNumber(assembly); - - if (string.IsNullOrEmpty(fileName)) - modulePath = assembly.Location; - else - modulePath = fileName; - - // Passing module as a parameter here so that the providers can have the module property populated. - // For engine providers, the module should point to top-level module name - // For FileSystem, the module is Microsoft.PowerShell.Core and not System.Management.Automation - if (parentModule != null && InitialSessionState.IsEngineModule(parentModule.Name)) - { - iss.ImportCmdletsFromAssembly(assembly, parentModule); - } - else - { - iss.ImportCmdletsFromAssembly(assembly, null); - } - } - else - { - string binaryPath = fileName; - modulePath = fileName; - if (binaryPath == null) - { - binaryPath = System.IO.Path.Combine(moduleBase, moduleName); - } + // Passing module as a parameter here so that the providers can have the module property populated. + // For engine providers, the module should point to top-level module name + // For FileSystem, the module is Microsoft.PowerShell.Core and not System.Management.Automation + iss.ImportCmdletsFromAssembly(assembly, isParentEngineModule ? parentModule : null); + } + else + { + string binaryPath = fileName; + modulePath = fileName; + binaryPath ??= System.IO.Path.Combine(moduleBase, moduleName); - BinaryAnalysisResult analysisResult = GetCmdletsFromBinaryModuleImplementation(binaryPath, manifestProcessingFlags, out assemblyVersion); - detectedCmdlets = analysisResult.DetectedCmdlets; - detectedAliases = analysisResult.DetectedAliases; - } - } + BinaryAnalysisResult analysisResult = GetCmdletsFromBinaryModuleImplementation(binaryPath, manifestProcessingFlags, out assemblyVersion); + detectedCmdlets = analysisResult.DetectedCmdlets; + detectedAliases = analysisResult.DetectedAliases; } found = true; - if (string.IsNullOrEmpty(shortModuleName)) - module = new PSModuleInfo(moduleName, modulePath, Context, ss); - else - module = new PSModuleInfo(shortModuleName, modulePath, Context, ss); + string nameToUse = string.IsNullOrEmpty(shortModuleName) ? moduleName : shortModuleName; + PSModuleInfo module = new PSModuleInfo(nameToUse, modulePath, Context, ss); module.SetModuleType(ModuleType.Binary); module.SetModuleBase(moduleBase); module.SetVersion(assemblyVersion); - module.ImplementingAssembly = assemblyToLoad ?? assembly; + module.ImplementingAssembly = assembly; if (importingModule) { SetModuleLoggingInformation(module); } - // Add the types table entries - List typesFileNames = new List(); - foreach (SessionStateTypeEntry sste in iss.Types) - { - typesFileNames.Add(sste.FileName); - } - - if (typesFileNames.Count > 0) - { - module.SetExportedTypeFiles(new ReadOnlyCollection(typesFileNames)); - } - - // Add the format file entries - List formatsFileNames = new List(); - foreach (SessionStateFormatEntry ssfe in iss.Formats) - { - formatsFileNames.Add(ssfe.FileName); - } - - if (formatsFileNames.Count > 0) - { - module.SetExportedFormatFiles(new ReadOnlyCollection(formatsFileNames)); - } - // Add the module info the providers... foreach (SessionStateProviderEntry sspe in iss.Providers) { // For engine providers, the module should point to top-level module name // For FileSystem, the module is Microsoft.PowerShell.Core and not System.Management.Automation - if (parentModule != null && InitialSessionState.IsEngineModule(parentModule.Name)) - { - sspe.SetModule(parentModule); - } - else - { - sspe.SetModule(module); - } + sspe.SetModule(isParentEngineModule ? parentModule : module); } // Add all of the exported cmdlets to the module object... @@ -6951,16 +6778,7 @@ internal PSModuleInfo LoadBinaryModule( iss.Bind(Context, updateOnly: true, module, options.NoClobber, options.Local, setLocation: false); // Scan all of the types in the assembly to register JobSourceAdapters. - IEnumerable allTypes = Array.Empty(); - if (assembly != null) - { - allTypes = assembly.ExportedTypes; - } - else if (assemblyToLoad != null) - { - allTypes = assemblyToLoad.ExportedTypes; - } - + IEnumerable allTypes = assembly?.ExportedTypes ?? Array.Empty(); foreach (Type type in allTypes) { // If it derives from JobSourceAdapter and it's not already registered, register it... @@ -7150,17 +6968,7 @@ internal static void AddModuleToModuleTables(ExecutionContext context, SessionSt targetSessionState.ModuleTableKeys.Add(moduleTableKey); } - if (targetSessionState.Module != null) - { - targetSessionState.Module.AddNestedModule(module); - } - - var privateDataHashTable = module.PrivateData as Hashtable; - if (!context.Modules.IsImplicitRemotingModuleLoaded && - privateDataHashTable != null && privateDataHashTable.ContainsKey("ImplicitRemoting")) - { - context.Modules.IsImplicitRemotingModuleLoaded = true; - } + targetSessionState.Module?.AddNestedModule(module); } /// @@ -7462,6 +7270,9 @@ private static void ImportFunctions(FunctionInfo func, SessionStateInternal targ CommandOrigin.Internal, 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 + // sets it on the other. SetCommandVisibility(isImportModulePrivate, functionInfo); functionInfo.Module = sourceModule; @@ -7611,29 +7422,6 @@ private static void ValidateCommandName(ModuleCmdletBase cmdlet, } } - /// - /// Search a PSSnapin with the specified name. - /// - internal static PSSnapInInfo GetEngineSnapIn(ExecutionContext context, string name) - { - HashSet snapinSet = new HashSet(); - List cmdlets = context.SessionState.InvokeCommand.GetCmdlets(); - foreach (CmdletInfo cmdlet in cmdlets) - { - PSSnapInInfo snapin = cmdlet.PSSnapIn; - if (snapin != null && !snapinSet.Contains(snapin)) - snapinSet.Add(snapin); - } - - foreach (PSSnapInInfo snapin in snapinSet) - { - if (string.Equals(snapin.Name, name, StringComparison.OrdinalIgnoreCase)) - return snapin; - } - - return null; - } - /// /// Returns the context cached ModuleTable module for import only if found and has safe language boundaries while /// exporting all functions by default. @@ -7646,7 +7434,7 @@ internal static PSSnapInInfo GetEngineSnapIn(ExecutionContext context, string na /// /// Note that module loading order is important with this check when the system is *locked down with DeviceGuard*. /// If a submodule that does not explicitly export any functions is imported from the command line, its useless - /// because no functions are exported (default fn export is explictly disallowed on locked down systems). + /// because no functions are exported (default fn export is explicitly disallowed on locked down systems). /// But if a parentmodule that imports the submodule is then imported, it will get the useless version of the /// module from the ModuleTable and the parent module will not work. /// $mSub = import-module SubModule # No functions exported, useless @@ -7654,7 +7442,7 @@ internal static PSSnapInInfo GetEngineSnapIn(ExecutionContext context, string na /// $mParent.DoSomething # This will likely be broken because SubModule functions are not accessible /// But this is not a realistic scenario because SubModule is useless with DeviceGuard lock down and must explicitly /// export its functions to become useful, at which point this check is no longer in effect and there is no issue. - /// $mSub = import-module SubModule # Explictly exports functions, useful + /// $mSub = import-module SubModule # Explicitly exports functions, useful /// $mParent = import-module ParentModule # This internally imports SubModule /// $mParent.DoSomething # This works because SubModule functions are exported and accessible. /// diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs index 9d5af609710..b687e502763 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs @@ -54,15 +54,6 @@ internal ModuleIntrinsics(ExecutionContext context) private const int MaxModuleNestingDepth = 10; - /// - /// Gets and sets boolean that indicates when an implicit remoting module is loaded. - /// - internal bool IsImplicitRemotingModuleLoaded - { - get; - set; - } - internal void IncrementModuleNestingDepth(PSCmdlet cmdlet, string path) { if (++ModuleNestingDepth > MaxModuleNestingDepth) @@ -150,10 +141,7 @@ private PSModuleInfo CreateModuleImplementation(string name, string path, object // script scope for the ss. // Allocate the session state instance for this module. - if (ss == null) - { - ss = new SessionState(_context, true, true); - } + ss ??= new SessionState(_context, true, true); // Now set up the module's session state to be the current session state SessionStateInternal oldSessionState = _context.EngineSessionState; @@ -279,7 +267,7 @@ internal List GetModules(string[] patterns, bool all) internal List GetExactMatchModules(string moduleName, bool all, bool exactMatch) { - if (moduleName == null) { moduleName = string.Empty; } + moduleName ??= string.Empty; return GetModuleCore(new string[] { moduleName }, all, exactMatch); } @@ -296,10 +284,7 @@ private List GetModuleCore(string[] patterns, bool all, bool exact } else { - if (patterns == null) - { - patterns = new string[] { "*" }; - } + patterns ??= new string[] { "*" }; foreach (string pattern in patterns) { @@ -358,7 +343,7 @@ private List GetModuleCore(string[] patterns, bool all, bool exact } } - return modulesMatched.OrderBy(m => m.Name).ToList(); + return modulesMatched.OrderBy(static m => m.Name).ToList(); } internal List GetModules(ModuleSpecification[] fullyQualifiedName, bool all) @@ -417,7 +402,7 @@ internal List GetModules(ModuleSpecification[] fullyQualifiedName, } } - return modulesMatched.OrderBy(m => m.Name).ToList(); + return modulesMatched.OrderBy(static m => m.Name).ToList(); } /// @@ -730,7 +715,7 @@ internal static bool MatchesModulePath(string modulePath, string requiredPath) string moduleDirPath = Path.GetDirectoryName(modulePath); // The module itself may be in a versioned directory (case 3) - if (Version.TryParse(Path.GetFileName(moduleDirPath), out Version unused)) + if (Version.TryParse(Path.GetFileName(moduleDirPath), out _)) { moduleDirPath = Path.GetDirectoryName(moduleDirPath); } @@ -874,7 +859,10 @@ internal static ExperimentalFeature[] GetExperimentalFeature(string manifestPath foreach (Hashtable feature in features) { string featureName = feature["Name"] as string; - if (string.IsNullOrEmpty(featureName)) { continue; } + if (string.IsNullOrEmpty(featureName)) + { + continue; + } if (ExperimentalFeature.IsModuleFeatureName(featureName, moduleName)) { @@ -894,25 +882,35 @@ internal static ExperimentalFeature[] GetExperimentalFeature(string manifestPath } // The extensions of all of the files that can be processed with Import-Module, put the ni.dll in front of .dll to have higher priority to be loaded. - internal static readonly string[] PSModuleProcessableExtensions = new string[] { - StringLiterals.PowerShellDataFileExtension, - StringLiterals.PowerShellScriptFileExtension, - StringLiterals.PowerShellModuleFileExtension, - StringLiterals.PowerShellCmdletizationFileExtension, - StringLiterals.PowerShellNgenAssemblyExtension, - StringLiterals.PowerShellILAssemblyExtension, - StringLiterals.PowerShellILExecutableExtension, - }; + internal static readonly string[] PSModuleProcessableExtensions = new string[] + { + StringLiterals.PowerShellDataFileExtension, + StringLiterals.PowerShellScriptFileExtension, + StringLiterals.PowerShellModuleFileExtension, + StringLiterals.PowerShellCmdletizationFileExtension, + StringLiterals.PowerShellNgenAssemblyExtension, + StringLiterals.PowerShellILAssemblyExtension, + StringLiterals.PowerShellILExecutableExtension, + }; // A list of the extensions to check for implicit module loading and discovery, put the ni.dll in front of .dll to have higher priority to be loaded. - internal static readonly string[] PSModuleExtensions = new string[] { - StringLiterals.PowerShellDataFileExtension, - StringLiterals.PowerShellModuleFileExtension, - StringLiterals.PowerShellCmdletizationFileExtension, - StringLiterals.PowerShellNgenAssemblyExtension, - StringLiterals.PowerShellILAssemblyExtension, - StringLiterals.PowerShellILExecutableExtension, - }; + internal static readonly string[] PSModuleExtensions = new string[] + { + StringLiterals.PowerShellDataFileExtension, + StringLiterals.PowerShellModuleFileExtension, + StringLiterals.PowerShellCmdletizationFileExtension, + StringLiterals.PowerShellNgenAssemblyExtension, + StringLiterals.PowerShellILAssemblyExtension, + StringLiterals.PowerShellILExecutableExtension, + }; + + // A list of the extensions to check for required assemblies. + internal static readonly string[] ProcessableAssemblyExtensions = new string[] + { + StringLiterals.PowerShellNgenAssemblyExtension, + StringLiterals.PowerShellILAssemblyExtension, + StringLiterals.PowerShellILExecutableExtension + }; /// /// Returns true if the extension is one of the module extensions... @@ -924,7 +922,9 @@ internal static bool IsPowerShellModuleExtension(string extension) foreach (string ext in PSModuleProcessableExtensions) { if (extension.Equals(ext, StringComparison.OrdinalIgnoreCase)) + { return true; + } } return false; @@ -967,7 +967,8 @@ internal static string GetPersonalModulePath() #if UNIX return Platform.SelectProductNameForDirectory(Platform.XDG_Type.USER_MODULES); #else - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), Utils.ModuleDirectory); + string myDocumentsPath = InternalTestHooks.SetMyDocumentsSpecialFolderToBlank ? string.Empty : Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); + return string.IsNullOrEmpty(myDocumentsPath) ? null : Path.Combine(myDocumentsPath, Utils.ModuleDirectory); #endif } @@ -985,20 +986,17 @@ internal static string GetPSHomeModulePath() try { string psHome = Utils.DefaultPowerShellAppBase; - if (!string.IsNullOrEmpty(psHome)) - { - // Win8: 584267 Powershell Modules are listed twice in x86, and cannot be removed - // This happens because ModuleTable uses Path as the key and CBS installer - // expands the path to include "SysWOW64" (for - // HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\PowerShell\3\PowerShellEngine ApplicationBase). - // Because of this, the module that is getting loaded during startup (through LocalRunspace) - // is using "SysWow64" in the key. Later, when Import-Module is called, it loads the - // module using ""System32" in the key. #if !UNIX - psHome = psHome.ToLowerInvariant().Replace("\\syswow64\\", "\\system32\\"); + // Win8: 584267 Powershell Modules are listed twice in x86, and cannot be removed. + // This happens because 'ModuleTable' uses Path as the key and x86 WinPS has "SysWOW64" in its $PSHOME. + // Because of this, the module that is getting loaded during startup (through LocalRunspace) is using + // "SysWow64" in the key. Later, when 'Import-Module' is called, it loads the module using ""System32" + // in the key. + // For the cross-platform PowerShell, a user can choose to install it under "C:\Windows\SysWOW64", and + // thus it may have the same problem as described above. So we keep this line of code. + psHome = psHome.ToLowerInvariant().Replace(@"\syswow64\", @"\system32\"); #endif - Interlocked.CompareExchange(ref s_psHomeModulePath, Path.Combine(psHome, "Modules"), null); - } + Interlocked.CompareExchange(ref s_psHomeModulePath, Path.Combine(psHome, "Modules"), null); } catch (System.Security.SecurityException) { @@ -1097,7 +1095,7 @@ private static int PathContainsSubstring(string pathToScan, string pathToLookFor 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(Utils.Separators.PathSeparator, StringSplitOptions.None); // we want to process empty entries + 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) { @@ -1136,24 +1134,31 @@ private static string AddToPath(string basePath, string pathToAdd, int insertPos if (!string.IsNullOrEmpty(pathToAdd)) // we don't want to append empty paths { - foreach (string subPathToAdd in pathToAdd.Split(Utils.Separators.PathSeparator, StringSplitOptions.RemoveEmptyEntries)) // in case pathToAdd is a 'combined path' (semicolon-separated) + foreach (string subPathToAdd in pathToAdd.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries)) // in case pathToAdd is a 'combined path' (semicolon-separated) { 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) // append subPathToAdd to the end + 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); + if (result.Length > 0) + { + endsWithPathSeparator = (result[result.Length - 1] == Path.PathSeparator); + } 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 seperator already + // 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 ( location) and by 'user-specific location' (SpecialFolder.MyDocuments or EVT.User)) @@ -1167,6 +1172,42 @@ private static string AddToPath(string basePath, string pathToAdd, int insertPos return result.ToString(); } + /// + /// The available module path scopes. + /// + public enum PSModulePathScope + { + /// The users module path. + User, + + /// The Builtin module path. This is where PowerShell is installed (PSHOME). + Builtin, + + /// The machine module path. This is the shared location for all users of the system. + Machine + } + + /// + /// Retrieve the current PSModulePath for the specified scope. + /// + /// The scope of module path to retrieve. This can be User, Builtin, or Machine. + /// The string representing the requested module path type. + public static string GetPSModulePath(PSModulePathScope scope) + { + if (scope == PSModulePathScope.User) + { + return GetPersonalModulePath(); + } + else if (scope == PSModulePathScope.Builtin) + { + return GetPSHomeModulePath(); + } + else + { + return GetSharedModulePath(); + } + } + /// /// Checks the various PSModulePath environment string and returns PSModulePath string as appropriate. /// @@ -1189,7 +1230,15 @@ public static string GetModulePath(string currentProcessModulePath, string hklmM currentProcessModulePath = hkcuUserModulePath; // = EVT.User } - currentProcessModulePath += Path.PathSeparator; + if (string.IsNullOrEmpty(currentProcessModulePath)) + { + currentProcessModulePath ??= string.Empty; + } + else + { + currentProcessModulePath += Path.PathSeparator; + } + if (string.IsNullOrEmpty(hklmMachineModulePath)) // EVT.Machine does Not exist { currentProcessModulePath += CombineSystemModulePaths(); // += (SharedModulePath + $PSHome\Modules) @@ -1210,16 +1259,32 @@ public static string GetModulePath(string currentProcessModulePath, string hklmM // personalModulePath // sharedModulePath // systemModulePath - currentProcessModulePath = AddToPath(currentProcessModulePath, personalModulePathToUse, 0); - int insertIndex = PathContainsSubstring(currentProcessModulePath, personalModulePathToUse) + personalModulePathToUse.Length + 1; - currentProcessModulePath = AddToPath(currentProcessModulePath, sharedModulePath, insertIndex); - insertIndex = PathContainsSubstring(currentProcessModulePath, sharedModulePath) + sharedModulePath.Length + 1; - currentProcessModulePath = AddToPath(currentProcessModulePath, systemModulePathToUse, insertIndex); + + int insertIndex = 0; + + currentProcessModulePath = UpdatePath(currentProcessModulePath, personalModulePathToUse, ref insertIndex); + currentProcessModulePath = UpdatePath(currentProcessModulePath, sharedModulePath, ref insertIndex); + currentProcessModulePath = UpdatePath(currentProcessModulePath, systemModulePathToUse, ref insertIndex); } 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; + } + /// /// 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 @@ -1233,7 +1298,7 @@ internal static string GetModulePath() #if !UNIX /// - /// Returns a PSModulePath suiteable for Windows PowerShell by removing PowerShell's specific + /// Returns a PSModulePath suitable for Windows PowerShell by removing PowerShell's specific /// paths from current PSModulePath. /// /// @@ -1258,24 +1323,23 @@ internal static string GetWindowsPowerShellModulePath() }; var modulePathList = new List(); - foreach (var path in currentModulePath.Split(';')) + foreach (var path in currentModulePath.Split(';', StringSplitOptions.TrimEntries)) { - var trimmedPath = path.Trim(); - if (!excludeModulePaths.Contains(trimmedPath)) + if (!excludeModulePaths.Contains(path)) { // make sure this module path is Not part of other PS Core installation - var possiblePwshDir = Path.GetDirectoryName(trimmedPath); + var possiblePwshDir = Path.GetDirectoryName(path); if (string.IsNullOrEmpty(possiblePwshDir)) { // i.e. module dir is in the drive root - modulePathList.Add(trimmedPath); + modulePathList.Add(path); } else { if (!File.Exists(Path.Combine(possiblePwshDir, "pwsh.dll"))) { - modulePathList.Add(trimmedPath); + modulePathList.Add(path); } } } @@ -1336,7 +1400,7 @@ internal static IEnumerable GetModulePath(bool includeSystemModulePath, if (!string.IsNullOrWhiteSpace(modulePathString)) { - foreach (string envPath in modulePathString.Split(Utils.Separators.PathSeparator, StringSplitOptions.RemoveEmptyEntries)) + foreach (string envPath in modulePathString.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries)) { var processedPath = ProcessOneModulePath(context, envPath, processedPathSet); if (processedPath != null) @@ -1422,32 +1486,10 @@ private static string ProcessOneModulePath(ExecutionContext context, string envP return null; } - /// - /// Removes all functions not belonging to the parent module. - /// - /// Parent module. - internal static void RemoveNestedModuleFunctions(PSModuleInfo module) - { - var input = module.SessionState?.Internal?.ExportedFunctions; - if ((input == null) || (input.Count == 0)) - { return; } - - List output = new List(input.Count); - foreach (var fnInfo in input) - { - if (module.Name.Equals(fnInfo.ModuleName, StringComparison.OrdinalIgnoreCase)) - { - output.Add(fnInfo); - } - } - - input.Clear(); - input.AddRange(output); - } - +#nullable enable private static void SortAndRemoveDuplicates(List input, Func keyGetter) { - Dbg.Assert(input != null, "Caller should verify that input != null"); + Dbg.Assert(input is not null, "Caller should verify that input != null"); input.Sort( (T x, T y) => @@ -1458,24 +1500,19 @@ private static void SortAndRemoveDuplicates(List input, Func ke } ); - bool firstItem = true; - string previousKey = null; - List output = new List(input.Count); - foreach (T item in input) + string? previousKey = null; + input.RemoveAll(ShouldRemove); + + bool ShouldRemove(T item) { string currentKey = keyGetter(item); - if ((firstItem) || !currentKey.Equals(previousKey, StringComparison.OrdinalIgnoreCase)) - { - output.Add(item); - } - + bool match = previousKey is not null + && currentKey.Equals(previousKey, StringComparison.OrdinalIgnoreCase); previousKey = currentKey; - firstItem = false; + return match; } - - input.Clear(); - input.AddRange(output); } +#nullable restore /// /// Mark stuff to be exported from the current environment using the various patterns. @@ -1527,7 +1564,7 @@ internal static void ExportModuleMembers( } } - SortAndRemoveDuplicates(sessionState.ExportedFunctions, (FunctionInfo ci) => ci.Name); + SortAndRemoveDuplicates(sessionState.ExportedFunctions, static (FunctionInfo ci) => ci.Name); } if (cmdletPatterns != null) @@ -1582,7 +1619,7 @@ internal static void ExportModuleMembers( } } - SortAndRemoveDuplicates(sessionState.Module.CompiledExports, (CmdletInfo ci) => ci.Name); + SortAndRemoveDuplicates(sessionState.Module.CompiledExports, static (CmdletInfo ci) => ci.Name); } if (variablePatterns != null) @@ -1605,7 +1642,7 @@ internal static void ExportModuleMembers( } } - SortAndRemoveDuplicates(sessionState.ExportedVariables, (PSVariable v) => v.Name); + SortAndRemoveDuplicates(sessionState.ExportedVariables, static (PSVariable v) => v.Name); } if (aliasPatterns != null) @@ -1645,7 +1682,7 @@ internal static void ExportModuleMembers( } } - SortAndRemoveDuplicates(sessionState.ExportedAliases, (AliasInfo ci) => ci.Name); + SortAndRemoveDuplicates(sessionState.ExportedAliases, static (AliasInfo ci) => ci.Name); } } @@ -1711,10 +1748,11 @@ internal enum ModuleMatchFailure /// Module version was greater than the maximum version. MaximumVersion, - /// The module specifcation passed in was null. + /// The module specification passed in was null. NullModuleSpecification, } +#nullable enable /// /// Used by Modules/Snapins to provide a hook to the engine for startup initialization /// w.r.t compiled assembly loading. diff --git a/src/System.Management.Automation/engine/Modules/ModuleSpecification.cs b/src/System.Management.Automation/engine/Modules/ModuleSpecification.cs index 315cf3aaf64..3c06ee856f3 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleSpecification.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleSpecification.cs @@ -44,12 +44,10 @@ public ModuleSpecification() /// The module name. public ModuleSpecification(string moduleName) { - if (string.IsNullOrEmpty(moduleName)) - { - throw new ArgumentNullException(nameof(moduleName)); - } + ArgumentException.ThrowIfNullOrEmpty(moduleName); this.Name = moduleName; + // Alias name of miniumVersion this.Version = null; this.RequiredVersion = null; @@ -67,10 +65,7 @@ public ModuleSpecification(string moduleName) /// The module specification as a hashtable. public ModuleSpecification(Hashtable moduleSpecification) { - if (moduleSpecification == null) - { - throw new ArgumentNullException(nameof(moduleSpecification)); - } + ArgumentNullException.ThrowIfNull(moduleSpecification); var exception = ModuleSpecificationInitHelper(this, moduleSpecification); if (exception != null) @@ -130,7 +125,7 @@ internal static Exception ModuleSpecificationInitHelper(ModuleSpecification modu } } // catch all exceptions here, we are going to report them via return value. - // Example of catched exception: one of conversions to Version failed. + // Example of caught exception: one of conversions to Version failed. catch (Exception e) { return e; @@ -170,13 +165,53 @@ internal static Exception ModuleSpecificationInitHelper(ModuleSpecification modu return null; } - internal ModuleSpecification(PSModuleInfo moduleInfo) + internal string GetRequiredModuleNotFoundVersionMessage() { - if (moduleInfo == null) + if (RequiredVersion is not null) { - throw new ArgumentNullException(nameof(moduleInfo)); + return StringUtil.Format( + Modules.RequiredModuleNotFoundRequiredVersion, + Name, + RequiredVersion); } + bool hasVersion = Version is not null; + bool hasMaximumVersion = MaximumVersion is not null; + + if (hasVersion && hasMaximumVersion) + { + return StringUtil.Format( + Modules.RequiredModuleNotFoundModuleAndMaximumVersion, + Name, + Version, + MaximumVersion); + } + + if (hasVersion) + { + return StringUtil.Format( + Modules.RequiredModuleNotFoundModuleVersion, + Name, + Version); + } + + if (hasMaximumVersion) + { + return StringUtil.Format( + Modules.RequiredModuleNotFoundMaximumVersion, + Name, + MaximumVersion); + } + + return StringUtil.Format( + Modules.RequiredModuleNotFoundWithoutVersion, + Name); + } + + internal ModuleSpecification(PSModuleInfo moduleInfo) + { + ArgumentNullException.ThrowIfNull(moduleInfo); + this.Name = moduleInfo.Name; this.Version = moduleInfo.Version; this.Guid = moduleInfo.Guid; diff --git a/src/System.Management.Automation/engine/Modules/ModuleUtils.cs b/src/System.Management.Automation/engine/Modules/ModuleUtils.cs index b2ee85a1b03..41bf4ac3521 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleUtils.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleUtils.cs @@ -13,22 +13,39 @@ namespace System.Management.Automation.Internal { internal static class ModuleUtils { + // These are documented members FILE_ATTRIBUTE, they just have not yet been + // added to System.IO.FileAttributes yet. + private const int FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS = 0x400000; + + private const int FILE_ATTRIBUTE_RECALL_ON_OPEN = 0x40000; + // Default option for local file system enumeration: // - Ignore files/directories when access is denied; // - Search top directory only. private static readonly System.IO.EnumerationOptions s_defaultEnumerationOptions = - new System.IO.EnumerationOptions() { AttributesToSkip = FileAttributes.Hidden }; + new System.IO.EnumerationOptions() { AttributesToSkip = FileAttributesToSkip }; + + private static readonly FileAttributes FileAttributesToSkip; // Default option for UNC path enumeration. Same as above plus a large buffer size. // For network shares, a large buffer may result in better performance as more results can be batched over the wire. // The buffer size 16K is recommended in the comment of the 'BufferSize' property: // "A "large" buffer, for example, would be 16K. Typical is 4K." private static readonly System.IO.EnumerationOptions s_uncPathEnumerationOptions = - new System.IO.EnumerationOptions() { AttributesToSkip = FileAttributes.Hidden, BufferSize = 16384 }; + new System.IO.EnumerationOptions() { AttributesToSkip = FileAttributesToSkip, BufferSize = 16384 }; private static readonly string EnCulturePath = Path.DirectorySeparatorChar + "en"; private static readonly string EnUsCulturePath = Path.DirectorySeparatorChar + "en-us"; + static ModuleUtils() + { + FileAttributesToSkip = FileAttributes.Hidden + // Skip OneDrive files/directories that are not fully on disk. + | FileAttributes.Offline + | (FileAttributes)FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS + | (FileAttributes)FILE_ATTRIBUTE_RECALL_ON_OPEN; + } + /// /// Check if a directory is likely a localized resources folder. /// @@ -81,8 +98,7 @@ internal static IEnumerable GetAllAvailableModuleFiles(string topDirecto string directoryToCheck = directoriesToCheck.Dequeue(); try { - string[] subDirectories = Directory.GetDirectories(directoryToCheck, "*", options); - foreach (string toAdd in subDirectories) + foreach (string toAdd in Directory.EnumerateDirectories(directoryToCheck, "*", options)) { if (firstSubDirs || !IsPossibleResourceDirectory(toAdd)) { @@ -94,8 +110,7 @@ internal static IEnumerable GetAllAvailableModuleFiles(string topDirecto catch (UnauthorizedAccessException) { } firstSubDirs = false; - string[] files = Directory.GetFiles(directoryToCheck, "*", options); - foreach (string moduleFile in files) + foreach (string moduleFile in Directory.EnumerateFiles(directoryToCheck, "*", options)) { foreach (string ext in ModuleIntrinsics.PSModuleExtensions) { @@ -123,7 +138,7 @@ internal static bool IsPSEditionCompatible( #if UNIX return true; #else - if (!ModuleUtils.IsOnSystem32ModulePath(moduleManifestPath)) + if (!IsOnSystem32ModulePath(moduleManifestPath)) { return true; } @@ -278,6 +293,11 @@ internal static IEnumerable GetDefaultAvailableModuleFiles(string topDir manifestPath += StringLiterals.PowerShellDataFileExtension; if (File.Exists(manifestPath)) { + if (HasSkippedFileAttribute(manifestPath)) + { + continue; + } + isModuleDirectory = true; yield return manifestPath; } @@ -290,6 +310,11 @@ internal static IEnumerable GetDefaultAvailableModuleFiles(string topDir string moduleFile = Path.Combine(directoryToCheck, proposedModuleName) + ext; if (File.Exists(moduleFile)) { + if (HasSkippedFileAttribute(moduleFile)) + { + continue; + } + isModuleDirectory = true; yield return moduleFile; @@ -332,14 +357,33 @@ internal static List GetModuleVersionSubfolders(string moduleBase) if (!string.IsNullOrWhiteSpace(moduleBase) && Directory.Exists(moduleBase)) { var options = Utils.PathIsUnc(moduleBase) ? s_uncPathEnumerationOptions : s_defaultEnumerationOptions; - string[] subdirectories = Directory.GetDirectories(moduleBase, "*", options); + IEnumerable subdirectories = Directory.EnumerateDirectories(moduleBase, "*", options); ProcessPossibleVersionSubdirectories(subdirectories, versionFolders); } return versionFolders; } - private static void ProcessPossibleVersionSubdirectories(string[] subdirectories, List versionFolders) + private static bool HasSkippedFileAttribute(string path) + { + try + { + FileAttributes attributes = File.GetAttributes(path); + if ((attributes & FileAttributesToSkip) is not 0) + { + return true; + } + } + catch + { + // Ignore failures so that we keep the current behavior of failing + // later in the search. + } + + return false; + } + + private static void ProcessPossibleVersionSubdirectories(IEnumerable subdirectories, List versionFolders) { foreach (string subdir in subdirectories) { @@ -352,7 +396,7 @@ private static void ProcessPossibleVersionSubdirectories(string[] subdirectories if (versionFolders.Count > 1) { - versionFolders.Sort((x, y) => y.CompareTo(x)); + versionFolders.Sort(static (x, y) => y.CompareTo(x)); } } @@ -387,15 +431,15 @@ internal static bool IsOnSystem32ModulePath(string path) /// Command pattern. /// Execution context. /// Command origin. + /// Fuzzy matcher to use. /// If true, rediscovers imported modules. /// Specific module version to be required. /// IEnumerable tuple containing the CommandInfo and the match score. - internal static IEnumerable GetFuzzyMatchingCommands(string pattern, ExecutionContext context, CommandOrigin commandOrigin, bool rediscoverImportedModules = false, bool moduleVersionRequired = false) + internal static IEnumerable GetFuzzyMatchingCommands(string pattern, ExecutionContext context, CommandOrigin commandOrigin, FuzzyMatcher fuzzyMatcher, bool rediscoverImportedModules = false, bool moduleVersionRequired = false) { - foreach (CommandInfo command in GetMatchingCommands(pattern, context, commandOrigin, rediscoverImportedModules, moduleVersionRequired, useFuzzyMatching: true)) + foreach (CommandInfo command in GetMatchingCommands(pattern, context, commandOrigin, rediscoverImportedModules, moduleVersionRequired, fuzzyMatcher: fuzzyMatcher)) { - int score = FuzzyMatcher.GetDamerauLevenshteinDistance(command.Name, pattern); - if (score <= FuzzyMatcher.MinimumDistance) + if (fuzzyMatcher.IsFuzzyMatch(command.Name, pattern, out int score)) { yield return new CommandScore(command, score); } @@ -410,10 +454,10 @@ internal static IEnumerable GetFuzzyMatchingCommands(string patter /// Command origin. /// If true, rediscovers imported modules. /// Specific module version to be required. - /// Use fuzzy matching. + /// Fuzzy matcher for fuzzy searching. /// Use abbreviation expansion for matching. /// Returns matching CommandInfo IEnumerable. - internal static IEnumerable GetMatchingCommands(string pattern, ExecutionContext context, CommandOrigin commandOrigin, bool rediscoverImportedModules = false, bool moduleVersionRequired = false, bool useFuzzyMatching = false, bool useAbbreviationExpansion = false) + internal static IEnumerable GetMatchingCommands(string pattern, ExecutionContext context, CommandOrigin commandOrigin, bool rediscoverImportedModules = false, bool moduleVersionRequired = false, FuzzyMatcher fuzzyMatcher = null, bool useAbbreviationExpansion = false) { // Otherwise, if it had wildcards, just return the "AvailableCommand" // type of command info. @@ -437,7 +481,7 @@ internal static IEnumerable GetMatchingCommands(string pattern, Exe // 1. We continue to the next module path if we don't want to re-discover those imported modules // 2. If we want to re-discover the imported modules, but one or more commands from the module were made private, // then we don't do re-discovery - if (!rediscoverImportedModules || modules.Exists(module => module.ModuleHasPrivateMembers)) + if (!rediscoverImportedModules || modules.Exists(static module => module.ModuleHasPrivateMembers)) { continue; } @@ -451,7 +495,7 @@ internal static IEnumerable GetMatchingCommands(string pattern, Exe foreach (KeyValuePair entry in psModule.ExportedCommands) { if (commandPattern.IsMatch(entry.Value.Name) || - (useFuzzyMatching && FuzzyMatcher.IsFuzzyMatch(entry.Value.Name, pattern)) || + (fuzzyMatcher is not null && fuzzyMatcher.IsFuzzyMatch(entry.Value.Name, pattern)) || (useAbbreviationExpansion && string.Equals(pattern, AbbreviateName(entry.Value.Name), StringComparison.OrdinalIgnoreCase))) { CommandInfo current = null; @@ -511,7 +555,7 @@ internal static IEnumerable GetMatchingCommands(string pattern, Exe CommandTypes commandTypes = pair.Value; if (commandPattern.IsMatch(commandName) || - (useFuzzyMatching && FuzzyMatcher.IsFuzzyMatch(commandName, pattern)) || + (fuzzyMatcher is not null && fuzzyMatcher.IsFuzzyMatch(commandName, pattern)) || (useAbbreviationExpansion && string.Equals(pattern, AbbreviateName(commandName), StringComparison.OrdinalIgnoreCase))) { bool shouldExportCommand = true; diff --git a/src/System.Management.Automation/engine/Modules/NewModuleCommand.cs b/src/System.Management.Automation/engine/Modules/NewModuleCommand.cs index d47681166a0..ceb5dd3c2e3 100644 --- a/src/System.Management.Automation/engine/Modules/NewModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/NewModuleCommand.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; +using System.Management.Automation.Security; // // Now define the set of commands for manipulating modules. @@ -168,15 +169,24 @@ protected override void EndProcessing() { // Check ScriptBlock language mode. If it is different than the context language mode // then throw error since private trusted script functions may be exposed. - if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage && - _scriptBlock.LanguageMode == PSLanguageMode.FullLanguage) + if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage && _scriptBlock.LanguageMode == PSLanguageMode.FullLanguage) { - this.ThrowTerminatingError( - new ErrorRecord( - new PSSecurityException(Modules.CannotCreateModuleWithScriptBlock), - "Modules_CannotCreateModuleWithFullLanguageScriptBlock", - ErrorCategory.SecurityError, - null)); + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + this.ThrowTerminatingError( + new ErrorRecord( + new PSSecurityException(Modules.CannotCreateModuleWithScriptBlock), + "Modules_CannotCreateModuleWithFullLanguageScriptBlock", + ErrorCategory.SecurityError, + targetObject: null)); + } + + SystemPolicy.LogWDACAuditMessage( + context: Context, + title: Modules.WDACNewModuleCommandLogTitle, + message: Modules.WDACNewModuleCommandLogMessage, + fqid: "NewModuleCmdletWitFullLanguageScriptblockNotAllowed", + dropIntoDebugger: true); } string gs = System.Guid.NewGuid().ToString(); diff --git a/src/System.Management.Automation/engine/Modules/NewModuleManifestCommand.cs b/src/System.Management.Automation/engine/Modules/NewModuleManifestCommand.cs index 30036e035e2..9346e7caa97 100644 --- a/src/System.Management.Automation/engine/Modules/NewModuleManifestCommand.cs +++ b/src/System.Management.Automation/engine/Modules/NewModuleManifestCommand.cs @@ -160,7 +160,7 @@ public string Description [Parameter] public ProcessorArchitecture ProcessorArchitecture { - get { return _processorArchitecture.HasValue ? _processorArchitecture.Value : ProcessorArchitecture.None; } + get { return _processorArchitecture ?? ProcessorArchitecture.None; } set { _processorArchitecture = value; } } @@ -886,14 +886,12 @@ private List TryResolveFilePath(string filePath) /// private string ManifestFragment(string key, string resourceString, string value, StreamWriter streamWriter) { - return string.Format(CultureInfo.InvariantCulture, "{0}# {1}{2}{0}{3:19} = {4}{2}{2}", - _indent, resourceString, streamWriter.NewLine, key, value); + return string.Format(CultureInfo.InvariantCulture, "{0}# {1}{2}{0}{3:19} = {4}{2}{2}", _indent, resourceString, streamWriter.NewLine, key, value); } private string ManifestFragmentForNonSpecifiedManifestMember(string key, string resourceString, string value, StreamWriter streamWriter) { - return string.Format(CultureInfo.InvariantCulture, "{0}# {1}{2}{0}# {3:19} = {4}{2}{2}", - _indent, resourceString, streamWriter.NewLine, key, value); + return string.Format(CultureInfo.InvariantCulture, "{0}# {1}{2}{0}# {3:19} = {4}{2}{2}", _indent, resourceString, streamWriter.NewLine, key, value); } private static string ManifestComment(string insert, StreamWriter streamWriter) @@ -948,12 +946,9 @@ protected override void EndProcessing() // wildcards for exported commands that weren't specified on the command line. if (_rootModule != null || _nestedModules != null || _requiredModules != null) { - if (_exportedFunctions == null) - _exportedFunctions = new string[] { "*" }; - if (_exportedAliases == null) - _exportedAliases = new string[] { "*" }; - if (_exportedCmdlets == null) - _exportedCmdlets = new string[] { "*" }; + _exportedAliases ??= new string[] { "*" }; + _exportedCmdlets ??= new string[] { "*" }; + _exportedFunctions ??= new string[] { "*" }; } ValidateUriParameterValue(ProjectUri, "ProjectUri"); @@ -966,7 +961,7 @@ protected override void EndProcessing() if (CompatiblePSEditions != null && (CompatiblePSEditions.Distinct(StringComparer.OrdinalIgnoreCase).Count() != CompatiblePSEditions.Length)) { - string message = StringUtil.Format(Modules.DuplicateEntriesInCompatiblePSEditions, string.Join(",", CompatiblePSEditions)); + string message = StringUtil.Format(Modules.DuplicateEntriesInCompatiblePSEditions, string.Join(',', CompatiblePSEditions)); var ioe = new InvalidOperationException(message); var er = new ErrorRecord(ioe, "Modules_DuplicateEntriesInCompatiblePSEditions", ErrorCategory.InvalidArgument, CompatiblePSEditions); ThrowTerminatingError(er); @@ -1030,8 +1025,7 @@ protected override void EndProcessing() result.Append(streamWriter.NewLine); result.Append(streamWriter.NewLine); - if (_rootModule == null) - _rootModule = string.Empty; + _rootModule ??= string.Empty; BuildModuleManifest(result, nameof(RootModule), Modules.RootModule, !string.IsNullOrEmpty(_rootModule), () => QuoteName(_rootModule), streamWriter); diff --git a/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs b/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs index f8625697800..4315f8bcb2b 100644 --- a/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs +++ b/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs @@ -453,7 +453,7 @@ internal void SetVersion(Version version) public ModuleType ModuleType { get; private set; } = ModuleType.Script; /// - /// This this module as being a compiled module... + /// This module as being a compiled module... /// internal void SetModuleType(ModuleType moduleType) { ModuleType = moduleType; } @@ -545,7 +545,10 @@ public Dictionary ExportedFunctions // If the module is not binary, it may also have functions... if (DeclaredFunctionExports != null) { - if (DeclaredFunctionExports.Count == 0) { return exports; } + if (DeclaredFunctionExports.Count == 0) + { + return exports; + } foreach (string fn in DeclaredFunctionExports) { @@ -661,16 +664,16 @@ internal void CreateExportedTypeDefinitions(ScriptBlockAst moduleContentScriptBl else { this._exportedTypeDefinitionsNoNested = new ReadOnlyDictionary( - moduleContentScriptBlockAsts.FindAll(a => (a is TypeDefinitionAst), false) + moduleContentScriptBlockAsts.FindAll(static a => (a is TypeDefinitionAst), false) .OfType() - .ToDictionary(a => a.Name, StringComparer.OrdinalIgnoreCase)); + .ToDictionary(static a => a.Name, StringComparer.OrdinalIgnoreCase)); } } internal void AddDetectedTypeExports(List typeDefinitions) { this._exportedTypeDefinitionsNoNested = new ReadOnlyDictionary( - typeDefinitions.ToDictionary(a => a.Name, StringComparer.OrdinalIgnoreCase)); + typeDefinitions.ToDictionary(static a => a.Name, StringComparer.OrdinalIgnoreCase)); } /// @@ -707,7 +710,10 @@ public Dictionary ExportedCmdlets if (DeclaredCmdletExports != null) { - if (DeclaredCmdletExports.Count == 0) { return exports; } + if (DeclaredCmdletExports.Count == 0) + { + return exports; + } foreach (string fn in DeclaredCmdletExports) { @@ -1302,10 +1308,7 @@ public object Invoke(ScriptBlock sb, params object[] args) /// public PSVariable GetVariableFromCallersModule(string variableName) { - if (string.IsNullOrEmpty(variableName)) - { - throw new ArgumentNullException(nameof(variableName)); - } + ArgumentException.ThrowIfNullOrEmpty(variableName); var context = LocalPipeline.GetExecutionContextFromTLS(); SessionState callersSessionState = null; @@ -1437,8 +1440,8 @@ internal void SetExportedTypeFiles(ReadOnlyCollection files) /// /// Implements deep copy of a PSModuleInfo instance. - /// A new PSModuleInfo instance. /// + /// A new PSModuleInfo instance. public PSModuleInfo Clone() { PSModuleInfo clone = (PSModuleInfo)this.MemberwiseClone(); diff --git a/src/System.Management.Automation/engine/Modules/RemoteDiscoveryHelper.cs b/src/System.Management.Automation/engine/Modules/RemoteDiscoveryHelper.cs index c52a752c640..18e12541528 100644 --- a/src/System.Management.Automation/engine/Modules/RemoteDiscoveryHelper.cs +++ b/src/System.Management.Automation/engine/Modules/RemoteDiscoveryHelper.cs @@ -43,9 +43,9 @@ private static Collection RehydrateHashtableKeys(PSObject pso, string pr List list = hashtable .Keys .Cast() - .Where(k => k != null) - .Select(k => k.ToString()) - .Where(s => s != null) + .Where(static k => k != null) + .Select(static k => k.ToString()) + .Where(static s => s != null) .ToList(); return new Collection(list); } @@ -683,13 +683,13 @@ internal void FetchAllModuleFiles(CimSession cimSession, string cimNamespace, Ci "Dependent", operationOptions); - IEnumerable associatedFiles = associatedInstances.Select(i => new CimModuleImplementationFile(i)); + IEnumerable associatedFiles = associatedInstances.Select(static i => new CimModuleImplementationFile(i)); _moduleFiles = associatedFiles.ToList(); } private List _moduleFiles; - private class CimModuleManifestFile : CimModuleFile + private sealed class CimModuleManifestFile : CimModuleFile { internal CimModuleManifestFile(string fileName, byte[] rawFileData) { @@ -705,7 +705,7 @@ internal CimModuleManifestFile(string fileName, byte[] rawFileData) internal override byte[] RawFileDataCore { get; } } - private class CimModuleImplementationFile : CimModuleFile + private sealed class CimModuleImplementationFile : CimModuleFile { private readonly CimInstance _baseObject; @@ -795,7 +795,7 @@ private static IEnumerable GetCimModules( options); // TODO/FIXME: ETW for method results IEnumerable cimModules = syncResults - .Select(cimInstance => new CimModule(cimInstance)) + .Select(static cimInstance => new CimModule(cimInstance)) .Where(cimModule => wildcardPattern.IsMatch(cimModule.ModuleName)); if (!onlyManifests) diff --git a/src/System.Management.Automation/engine/Modules/RemoveModuleCommand.cs b/src/System.Management.Automation/engine/Modules/RemoveModuleCommand.cs index 182e535604c..52fa4879f41 100644 --- a/src/System.Management.Automation/engine/Modules/RemoveModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/RemoveModuleCommand.cs @@ -291,7 +291,7 @@ private bool ModuleProvidesCurrentSessionDrive(PSModuleInfo module) return false; } - private void GetAllNestedModules(PSModuleInfo module, ref List nestedModulesWithNoCircularReference) + private static void GetAllNestedModules(PSModuleInfo module, ref List nestedModulesWithNoCircularReference) { List nestedModules = new List(); if (module.NestedModules != null && module.NestedModules.Count > 0) @@ -362,7 +362,7 @@ protected override void EndProcessing() hasWildcards = false; } - if (FullyQualifiedName != null && (FullyQualifiedName.Any(moduleSpec => !InitialSessionState.IsEngineModule(moduleSpec.Name)))) + if (FullyQualifiedName != null && (FullyQualifiedName.Any(static moduleSpec => !InitialSessionState.IsEngineModule(moduleSpec.Name)))) { isEngineModule = false; } diff --git a/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs b/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs index f2005400c9d..7207a7292c0 100644 --- a/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs +++ b/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs @@ -14,7 +14,6 @@ namespace System.Management.Automation /// /// Class describing a PowerShell module... /// - [Serializable] internal class ScriptAnalysis { internal static ScriptAnalysis Analyze(string path, ExecutionContext context) @@ -94,10 +93,9 @@ internal static string ReadScript(string path) { using (FileStream readerStream = new FileStream(path, FileMode.Open, FileAccess.Read)) { - Encoding defaultEncoding = ClrFacade.GetDefaultEncoding(); Microsoft.Win32.SafeHandles.SafeFileHandle safeFileHandle = readerStream.SafeFileHandle; - using (StreamReader scriptReader = new StreamReader(readerStream, defaultEncoding)) + using (StreamReader scriptReader = new StreamReader(readerStream, Encoding.Default)) { return scriptReader.ReadToEnd(); } @@ -279,10 +277,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; @@ -433,9 +443,12 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) return AstVisitAction.SkipChildren; } - private void ProcessCmdletArguments(object value, Action onEachArgument) + private static void ProcessCmdletArguments(object value, Action onEachArgument) { - if (value == null) return; + if (value == null) + { + return; + } var commandName = value as string; if (commandName != null) @@ -557,7 +570,7 @@ private static Hashtable DoPsuedoParameterBinding(CommandAst commandAst, string private static readonly Dictionary s_parameterBindingInfoTable; - private class ParameterBindingInfo + private sealed class ParameterBindingInfo { internal ParameterInfo[] parameterInfo; } @@ -571,7 +584,6 @@ private struct ParameterInfo // Class to keep track of modules we need to import, and commands that should // be filtered out of them. - [Serializable] internal class RequiredModuleInfo { internal string Name { get; set; } diff --git a/src/System.Management.Automation/engine/Modules/SwitchProcessCommand.cs b/src/System.Management.Automation/engine/Modules/SwitchProcessCommand.cs new file mode 100644 index 00000000000..84d14939356 --- /dev/null +++ b/src/System.Management.Automation/engine/Modules/SwitchProcessCommand.cs @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Management.Automation; +using System.Runtime.InteropServices; + +using Dbg = System.Management.Automation.Diagnostics; + +#if UNIX + +namespace Microsoft.PowerShell.Commands +{ + /// + /// Implements a cmdlet that allows use of execv API. + /// + [Cmdlet(VerbsCommon.Switch, "Process", HelpUri = "https://go.microsoft.com/fwlink/?linkid=2181448")] + public sealed class SwitchProcessCommand : PSCmdlet + { + /// + /// Get or set the command and arguments to replace the current pwsh process. + /// + [Parameter(Position = 0, Mandatory = false, ValueFromRemainingArguments = true)] + public string[] WithCommand { get; set; } = Array.Empty(); + + /// + /// Execute the command and arguments + /// + protected override void EndProcessing() + { + if (WithCommand.Length == 0) + { + return; + } + + // execv requires command to be full path so resolve command to first match + var command = this.SessionState.InvokeCommand.GetCommand(WithCommand[0], CommandTypes.Application); + if (command is null) + { + ThrowTerminatingError( + new ErrorRecord( + new CommandNotFoundException( + string.Format( + System.Globalization.CultureInfo.InvariantCulture, + CommandBaseStrings.NativeCommandNotFound, + WithCommand[0] + ) + ), + "CommandNotFound", + ErrorCategory.InvalidArgument, + WithCommand[0] + ) + ); + } + + var execArgs = new string?[WithCommand.Length + 1]; + + // execv convention is the first arg is the program name + execArgs[0] = command.Name; + + for (int i = 1; i < WithCommand.Length; i++) + { + execArgs[i] = WithCommand[i]; + } + + // need null terminator at end + execArgs[execArgs.Length - 1] = null; + + var env = Environment.GetEnvironmentVariables(); + var envBlock = new string?[env.Count + 1]; + int j = 0; + foreach (DictionaryEntry entry in env) + { + envBlock[j++] = entry.Key + "=" + entry.Value; + } + + envBlock[envBlock.Length - 1] = null; + + // setup termios for a child process as .NET modifies termios dynamically for use with ReadKey() + ConfigureTerminalForChildProcess(true); + int exitCode = Exec(command.Source, execArgs, envBlock); + if (exitCode < 0) + { + ConfigureTerminalForChildProcess(false); + ThrowTerminatingError( + new ErrorRecord( + new Exception( + string.Format( + System.Globalization.CultureInfo.InvariantCulture, + CommandBaseStrings.ExecFailed, + Marshal.GetLastPInvokeError(), + string.Join(' ', WithCommand) + ) + ), + "ExecutionFailed", + ErrorCategory.InvalidOperation, + WithCommand + ) + ); + } + } + + /// + /// The `execv` POSIX syscall we use to exec /bin/sh. + /// + /// The path to the executable to exec. + /// + /// The arguments to send through to the executable. + /// Array must have its final element be null. + /// + /// + /// The environment variables to send through to the executable in the form of "key=value". + /// Array must have its final element be null. + /// + /// An exit code if exec failed, but if successful the calling process will be overwritten. + /// + [DllImport("libc", + EntryPoint = "execve", + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + SetLastError = true)] + private static extern int Exec(string path, string?[] args, string?[] env); + + // leverage .NET runtime's native library which abstracts the need to handle different OS and architectures for termios api + [DllImport("libSystem.Native", EntryPoint = "SystemNative_ConfigureTerminalForChildProcess")] + private static extern void ConfigureTerminalForChildProcess([MarshalAs(UnmanagedType.Bool)] bool childUsesTerminal); + } +} + +#endif diff --git a/src/System.Management.Automation/engine/Modules/TestModuleManifestCommand.cs b/src/System.Management.Automation/engine/Modules/TestModuleManifestCommand.cs index 265287a010f..650588c32e4 100644 --- a/src/System.Management.Automation/engine/Modules/TestModuleManifestCommand.cs +++ b/src/System.Management.Automation/engine/Modules/TestModuleManifestCommand.cs @@ -294,7 +294,7 @@ protected override void ProcessRecord() // All module extensions except ".psd1" are valid RootModule extensions private static readonly IReadOnlyList s_validRootModuleExtensions = ModuleIntrinsics.PSModuleExtensions - .Where(ext => !string.Equals(ext, StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase)) + .Where(static ext => !string.Equals(ext, StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase)) .ToArray(); /// @@ -374,7 +374,9 @@ private bool IsValidFilePath(string path, PSModuleInfo module, bool verifyPathSc ThrowTerminatingError(er); } - path = pathInfos[0].Path; + // `Path` returns the PSProviderPath which is fully qualified to the provider and the filesystem APIs + // don't understand this. Instead `ProviderPath` returns the path that the FileSystemProvider understands. + path = pathInfos[0].ProviderPath; // First, we validate if the path does exist. if (!File.Exists(path) && !Directory.Exists(path)) diff --git a/src/System.Management.Automation/engine/MshCmdlet.cs b/src/System.Management.Automation/engine/MshCmdlet.cs index a2a7023b2d0..40095ad1472 100644 --- a/src/System.Management.Automation/engine/MshCmdlet.cs +++ b/src/System.Management.Automation/engine/MshCmdlet.cs @@ -14,6 +14,7 @@ namespace System.Management.Automation { #region Auxiliary + /// /// An interface that a /// or @@ -31,6 +32,7 @@ namespace System.Management.Automation /// /// /// +#nullable enable public interface IDynamicParameters { /// @@ -62,8 +64,10 @@ public interface IDynamicParameters /// may not be set at the time this method is called, /// even if the parameters are mandatory. /// - object GetDynamicParameters(); + object? GetDynamicParameters(); } +#nullable restore + /// /// Type used to define a parameter on a cmdlet script of function that /// can only be used as a switch. @@ -112,7 +116,7 @@ public bool ToBool() /// Construct a SwitchParameter instance with a particular value. /// /// - /// If true, it indicates that the switch is present, flase otherwise. + /// If true, it indicates that the switch is present, false otherwise. /// public SwitchParameter(bool isPresent) { @@ -279,8 +283,7 @@ public bool HasErrors /// public string ExpandString(string source) { - if (_cmdlet != null) - _cmdlet.ThrowIfStopping(); + _cmdlet?.ThrowIfStopping(); return _context.Engine.Expand(source); } @@ -359,7 +362,7 @@ public CommandInfo GetCommand(string commandName, CommandTypes type, object[] ar public System.EventHandler PostCommandLookupAction { get; set; } /// - /// Gets or sets the action that is invoked everytime the runspace location (cwd) is changed. + /// Gets or sets the action that is invoked every time the runspace location (cwd) is changed. /// public System.EventHandler LocationChangedAction { get; set; } @@ -670,40 +673,46 @@ internal IEnumerable GetCommands(string name, CommandTypes commandT } /// - /// Executes a piece of text as a script synchronously. + /// Executes a piece of text as a script synchronously in the caller's session state. + /// The given text will be executed in a child scope rather than dot-sourced. /// /// The script text to evaluate. - /// A collection of MshCobjects generated by the script. + /// A collection of PSObjects generated by the script. Never null, but may be empty. /// Thrown if there was a parsing error in the script. /// Represents a script-level exception. /// public Collection InvokeScript(string script) { - return InvokeScript(script, true, PipelineResultTypes.None, null); + return InvokeScript(script, useNewScope: true, PipelineResultTypes.None, input: null); } /// - /// Executes a piece of text as a script synchronously. + /// Executes a piece of text as a script synchronously in the caller's session state. + /// The given text will be executed in a child scope rather than dot-sourced. /// /// The script text to evaluate. - /// The arguments to the script. - /// A collection of MshCobjects generated by the script. + /// The arguments to the script, available as $args. + /// A collection of PSObjects generated by the script. Never null, but may be empty. /// Thrown if there was a parsing error in the script. /// Represents a script-level exception. /// public Collection InvokeScript(string script, params object[] args) { - return InvokeScript(script, true, PipelineResultTypes.None, null, args); + return InvokeScript(script, useNewScope: true, PipelineResultTypes.None, input: null, args); } /// + /// Executes a given scriptblock synchronously in the given session state. + /// The scriptblock will be executed in the calling scope (dot-sourced) rather than in a new child scope. /// - /// - /// - /// - /// + /// The session state in which to execute the scriptblock. + /// The scriptblock to execute. + /// The arguments to the scriptblock, available as $args. + /// A collection of the PSObjects emitted by the executing scriptblock. Never null, but may be empty. public Collection InvokeScript( - SessionState sessionState, ScriptBlock scriptBlock, params object[] args) + SessionState sessionState, + ScriptBlock scriptBlock, + params object[] args) { if (scriptBlock == null) { @@ -735,13 +744,18 @@ public Collection InvokeScript( /// /// Invoke a scriptblock in the current runspace, controlling if it gets a new scope. /// - /// If true, a new scope will be created. + /// If true, executes the scriptblock in a new child scope, otherwise the scriptblock is dot-sourced into the calling scope. /// The scriptblock to execute. - /// Optionall input to the command. + /// Optional input to the command. /// Arguments to pass to the scriptblock. - /// The result of the evaluation. + /// + /// A collection of the PSObjects generated by executing the script. Never null, but may be empty. + /// public Collection InvokeScript( - bool useLocalScope, ScriptBlock scriptBlock, IList input, params object[] args) + bool useLocalScope, + ScriptBlock scriptBlock, + IList input, + params object[] args) { if (scriptBlock == null) { @@ -767,24 +781,27 @@ public Collection InvokeScript( /// /// The script to evaluate. /// If true, evaluate the script in its own scope. - /// If false, the script will be evaluated in the current scope i.e. it will be "dotted" + /// If false, the script will be evaluated in the current scope i.e. it will be dot-sourced. /// If set to Output, all output will be streamed /// to the output pipe of the calling cmdlet. If set to None, the result will be returned /// to the caller as a collection of PSObjects. No other flags are supported at this time and /// will result in an exception if used. /// The list of objects to use as input to the script. - /// The array of arguments to the command. - /// A collection of MshCobjects generated by the script. This will be - /// empty if output was redirected. + /// The array of arguments to the command, available as $args. + /// A collection of PSObjects generated by the script. This will be + /// empty if output was redirected. Never null. /// Thrown if there was a parsing error in the script. /// Represents a script-level exception. /// Thrown if any redirect other than output is attempted. /// - public Collection InvokeScript(string script, bool useNewScope, - PipelineResultTypes writeToPipeline, IList input, params object[] args) + public Collection InvokeScript( + string script, + bool useNewScope, + PipelineResultTypes writeToPipeline, + IList input, + params object[] args) { - if (script == null) - throw new ArgumentNullException(nameof(script)); + ArgumentNullException.ThrowIfNull(script); // Compile the script text into an executable script block. ScriptBlock sb = ScriptBlock.Create(_context, script); @@ -792,11 +809,14 @@ public Collection InvokeScript(string script, bool useNewScope, return InvokeScript(sb, useNewScope, writeToPipeline, input, args); } - private Collection InvokeScript(ScriptBlock sb, bool useNewScope, - PipelineResultTypes writeToPipeline, IList input, params object[] args) + private Collection InvokeScript( + ScriptBlock sb, + bool useNewScope, + PipelineResultTypes writeToPipeline, + IList input, + params object[] args) { - if (_cmdlet != null) - _cmdlet.ThrowIfStopping(); + _cmdlet?.ThrowIfStopping(); Cmdlet cmdletToUse = null; ScriptBlock.ErrorHandlingBehavior errorHandlingBehavior = ScriptBlock.ErrorHandlingBehavior.WriteToExternalErrorPipe; @@ -887,8 +907,7 @@ private Collection InvokeScript(ScriptBlock sb, bool useNewScope, /// public ScriptBlock NewScriptBlock(string scriptText) { - if (_commandRuntime != null) - _commandRuntime.ThrowIfStopping(); + _commandRuntime?.ThrowIfStopping(); ScriptBlock result = ScriptBlock.Create(_context, scriptText); return result; diff --git a/src/System.Management.Automation/engine/MshCommandRuntime.cs b/src/System.Management.Automation/engine/MshCommandRuntime.cs index e43e49631aa..883c800deab 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); + /// /// Display debug information. /// @@ -566,6 +572,9 @@ public void WriteVerbose(string text) WriteVerbose(new VerboseRecord(text)); } + internal bool IsWriteVerboseEnabled() + => WriteHelper_ShouldWrite(VerbosePreference, lastVerboseContinueStatus); + /// /// Display verbose information. /// @@ -660,6 +669,9 @@ public void WriteWarning(string text) WriteWarning(new WarningRecord(text)); } + internal bool IsWriteWarningEnabled() + => WriteHelper_ShouldWrite(WarningPreference, lastWarningContinueStatus); + /// /// Display warning information. /// @@ -733,6 +745,9 @@ public void WriteInformation(InformationRecord informationRecord) WriteInformation(informationRecord, false); } + internal bool IsWriteInformationEnabled() + => WriteHelper_ShouldWrite(InformationPreference, lastInformationContinueStatus); + /// /// Display tagged object information. /// @@ -876,7 +891,7 @@ internal void WriteInformation(InformationRecord record, bool overrideInquire = /// pipeline execution log. /// /// If LogPipelineExecutionDetail is turned on, this information will be written - /// to monad log under log category "Pipeline execution detail" + /// to PowerShell log under log category "Pipeline execution detail" /// /// /// @@ -928,7 +943,8 @@ private bool InitShouldLogPipelineExecutionDetail() /// internal string PipelineVariable { get; set; } - private PSVariable _pipelineVarReference = null; + private PSVariable _pipelineVarReference; + private bool _shouldRemovePipelineVariable; internal void SetupOutVariable() { @@ -941,13 +957,10 @@ internal void SetupOutVariable() // Handle the creation of OutVariable in the case of Out-Default specially, // as it needs to handle much of its OutVariable support itself. - if ( - (!string.IsNullOrEmpty(this.OutVariable)) && - (!(this.OutVariable.StartsWith('+'))) && - string.Equals("Out-Default", _thisCommand.CommandInfo.Name, StringComparison.OrdinalIgnoreCase)) + if (!OutVariable.StartsWith('+') && + string.Equals("Out-Default", _commandInfo.Name, StringComparison.OrdinalIgnoreCase)) { - if (_state == null) - _state = new SessionState(Context.EngineSessionState); + _state ??= new SessionState(Context.EngineSessionState); IList oldValue = null; oldValue = PSObject.Base(_state.PSVariable.GetValue(this.OutVariable)) as IList; @@ -972,23 +985,34 @@ internal void SetupPipelineVariable() // This can't use the common SetupVariable implementation, as this needs to persist for an entire // pipeline. - if (string.IsNullOrEmpty(this.PipelineVariable)) + if (string.IsNullOrEmpty(PipelineVariable)) { return; } EnsureVariableParameterAllowed(); - if (_state == null) - _state = new SessionState(Context.EngineSessionState); + _state ??= new SessionState(Context.EngineSessionState); // Create the pipeline variable - _pipelineVarReference = new PSVariable(this.PipelineVariable); - _state.PSVariable.Set(_pipelineVarReference); + _pipelineVarReference = new PSVariable(PipelineVariable); + object varToUse = _state.Internal.SetVariable( + _pipelineVarReference, + force: false, + CommandOrigin.Internal); - // Get the reference again in case we re-used one from the - // same scope. - _pipelineVarReference = _state.PSVariable.Get(this.PipelineVariable); + if (ReferenceEquals(_pipelineVarReference, varToUse)) + { + // The returned variable is the exact same instance, which means we set a new variable. + // In this case, we will try removing the pipeline variable in the end. + _shouldRemovePipelineVariable = true; + } + else + { + // A variable with the same name already exists in the same scope and it was returned. + // In this case, we update the reference and don't remove the variable in the end. + _pipelineVarReference = (PSVariable)varToUse; + } if (_thisCommand is not PSScriptCmdlet) { @@ -996,6 +1020,15 @@ internal void SetupPipelineVariable() } } + internal void RemovePipelineVariable() + { + if (_shouldRemovePipelineVariable) + { + // Remove pipeline variable when a pipeline is being torn down. + _state.PSVariable.Remove(PipelineVariable); + } + } + /// /// Configures the number of objects to buffer before calling the downstream Cmdlet. /// @@ -1063,8 +1096,8 @@ internal int OutBuffer /// , /// /// - /// - /// namespace Microsoft.Samples.MSH.Cmdlet + /// + /// namespace Microsoft.Samples.Cmdlet /// { /// [Cmdlet(VerbsCommon.Remove,"myobjecttype1")] /// public class RemoveMyObjectType1 : PSCmdlet @@ -1086,7 +1119,7 @@ internal int OutBuffer /// } /// } /// } - /// + /// /// /// /// @@ -1157,8 +1190,8 @@ public bool ShouldProcess(string target) /// , /// /// - /// - /// namespace Microsoft.Samples.MSH.Cmdlet + /// + /// namespace Microsoft.Samples.Cmdlet /// { /// [Cmdlet(VerbsCommon.Remove,"myobjecttype2")] /// public class RemoveMyObjectType2 : PSCmdlet @@ -1180,7 +1213,7 @@ public bool ShouldProcess(string target) /// } /// } /// } - /// + /// /// /// /// @@ -1260,8 +1293,8 @@ public bool ShouldProcess(string target, string action) /// , /// /// - /// - /// namespace Microsoft.Samples.MSH.Cmdlet + /// + /// namespace Microsoft.Samples.Cmdlet /// { /// [Cmdlet(VerbsCommon.Remove,"myobjecttype3")] /// public class RemoveMyObjectType3 : PSCmdlet @@ -1277,8 +1310,8 @@ public bool ShouldProcess(string target, string action) /// public override void ProcessRecord() /// { /// if (ShouldProcess( - /// string.Format("Deleting file {0}",filename), - /// string.Format("Are you sure you want to delete file {0}?", filename), + /// string.Format($"Deleting file {filename}"), + /// string.Format($"Are you sure you want to delete file {filename}?"), /// "Delete file")) /// { /// // delete the object @@ -1286,7 +1319,7 @@ public bool ShouldProcess(string target, string action) /// } /// } /// } - /// + /// /// /// /// @@ -1375,8 +1408,8 @@ public bool ShouldProcess( /// , /// /// - /// - /// namespace Microsoft.Samples.MSH.Cmdlet + /// + /// namespace Microsoft.Samples.Cmdlet /// { /// [Cmdlet(VerbsCommon.Remove,"myobjecttype3")] /// public class RemoveMyObjectType3 : PSCmdlet @@ -1393,8 +1426,8 @@ public bool ShouldProcess( /// { /// ShouldProcessReason shouldProcessReason; /// if (ShouldProcess( - /// string.Format("Deleting file {0}",filename), - /// string.Format("Are you sure you want to delete file {0}?", filename), + /// string.Format($"Deleting file {filename}"), + /// string.Format($"Are you sure you want to delete file {filename}?"), /// "Delete file", /// out shouldProcessReason)) /// { @@ -1403,7 +1436,7 @@ public bool ShouldProcess( /// } /// } /// } - /// + /// /// /// /// @@ -1465,7 +1498,7 @@ private bool CanShouldProcessAutoConfirm() /// /// are returned. /// - /// true iff the action should be performed + /// true if-and-only-if the action should be performed /// /// The pipeline has already been terminated, or was terminated /// during the execution of this method. @@ -1681,8 +1714,8 @@ internal ShouldProcessPossibleOptimization CalculatePossibleShouldProcessOptimiz /// to ShouldProcess for the Cmdlet instance. /// /// - /// - /// namespace Microsoft.Samples.MSH.Cmdlet + /// + /// namespace Microsoft.Samples.Cmdlet /// { /// [Cmdlet(VerbsCommon.Remove,"myobjecttype4")] /// public class RemoveMyObjectType4 : PSCmdlet @@ -1706,14 +1739,14 @@ internal ShouldProcessPossibleOptimization CalculatePossibleShouldProcessOptimiz /// public override void ProcessRecord() /// { /// if (ShouldProcess( - /// string.Format("Deleting file {0}",filename), - /// string.Format("Are you sure you want to delete file {0}", filename), + /// string.Format($"Deleting file {filename}"), + /// string.Format($"Are you sure you want to delete file {filename}"), /// "Delete file")) /// { /// if (IsReadOnly(filename)) /// { /// if (!Force && !ShouldContinue( - /// string.Format("File {0} is read-only. Are you sure you want to delete read-only file {0}?", filename), + /// string.Format($"File {filename} is read-only. Are you sure you want to delete read-only file {filename}?"), /// "Delete file")) /// ) /// { @@ -1725,7 +1758,7 @@ internal ShouldProcessPossibleOptimization CalculatePossibleShouldProcessOptimiz /// } /// } /// } - /// + /// /// /// /// @@ -1765,11 +1798,11 @@ public bool ShouldContinue(string query, string caption) /// the default option selected in the selection menu is 'No'. /// /// - /// true iff user selects YesToAll. If this is already true, + /// true if-and-only-if user selects YesToAll. If this is already true, /// ShouldContinue will bypass the prompt and return true. /// /// - /// true iff user selects NoToAll. If this is already true, + /// true if-and-only-if user selects NoToAll. If this is already true, /// ShouldContinue will bypass the prompt and return false. /// /// @@ -1812,11 +1845,11 @@ public bool ShouldContinue( /// It may be displayed by some hosts, but not all. /// /// - /// true iff user selects YesToAll. If this is already true, + /// true if-and-only-if user selects YesToAll. If this is already true, /// ShouldContinue will bypass the prompt and return true. /// /// - /// true iff user selects NoToAll. If this is already true, + /// true if-and-only-if user selects NoToAll. If this is already true, /// ShouldContinue will bypass the prompt and return false. /// /// @@ -1863,8 +1896,8 @@ public bool ShouldContinue( /// to ShouldProcess for the Cmdlet instance. /// /// - /// - /// namespace Microsoft.Samples.MSH.Cmdlet + /// + /// namespace Microsoft.Samples.Cmdlet /// { /// [Cmdlet(VerbsCommon.Remove,"myobjecttype4")] /// public class RemoveMyObjectType5 : PSCmdlet @@ -1891,14 +1924,14 @@ public bool ShouldContinue( /// public override void ProcessRecord() /// { /// if (ShouldProcess( - /// string.Format("Deleting file {0}",filename), - /// string.Format("Are you sure you want to delete file {0}", filename), + /// string.Format($"Deleting file {filename}"), + /// string.Format($"Are you sure you want to delete file {filename}"), /// "Delete file")) /// { /// if (IsReadOnly(filename)) /// { /// if (!Force && !ShouldContinue( - /// string.Format("File {0} is read-only. Are you sure you want to delete read-only file {0}?", filename), + /// string.Format($"File {filename} is read-only. Are you sure you want to delete read-only file {filename}?"), /// "Delete file"), /// ref yesToAll, /// ref noToAll @@ -1912,7 +1945,7 @@ public bool ShouldContinue( /// } /// } /// } - /// + /// /// /// /// @@ -2055,6 +2088,7 @@ public PSTransactionContext CurrentPSTransaction /// . /// etc. /// + [System.Diagnostics.CodeAnalysis.DoesNotReturn] public void ThrowTerminatingError(ErrorRecord errorRecord) { ThrowIfStopping(); @@ -2317,7 +2351,7 @@ internal IDisposable AllowThisCommandToWrite(bool permittedToWriteToPipeline) return new AllowWrite(_thisCommand, permittedToWriteToPipeline); } - private class AllowWrite : IDisposable + private sealed class AllowWrite : IDisposable { /// /// Begin the scope where WriteObject/WriteError is permitted. @@ -2376,10 +2410,7 @@ public Exception ManageException(Exception e) if (e == null) throw PSTraceSource.NewArgumentNullException(nameof(e)); - if (PipelineProcessor != null) - { - PipelineProcessor.RecordFailure(e, _thisCommand); - } + PipelineProcessor?.RecordFailure(e, _thisCommand); // 1021203-2005/05/09-JonN // HaltCommandException will cause the command @@ -2403,7 +2434,6 @@ public Exception ManageException(Exception e) } // Log a command health event - MshLog.LogCommandHealthEvent( Context, e, @@ -2542,8 +2572,7 @@ internal void SetupVariable(VariableStreamKind streamKind, string variableName, EnsureVariableParameterAllowed(); - if (_state == null) - _state = new SessionState(Context.EngineSessionState); + _state ??= new SessionState(Context.EngineSessionState); if (variableName.StartsWith('+')) { @@ -2803,8 +2832,16 @@ private void DoWriteError(object obj) _WriteErrorSkipAllowCheck(errorRecord, preference); } - // NOTICE-2004/06/08-JonN 959638 - // Use this variant to skip the ThrowIfWriteNotPermitted check + /// + /// Write an error, skipping the ThrowIfWriteNotPermitted check. + /// + /// The error record to write. + /// The configured error action preference. + /// + /// True when this method is called to write from a native command's stderr stream. + /// When errors are written through a native stderr stream, they do not interact with the error preference system, + /// but must still present as errors in PowerShell. + /// /// /// The pipeline has already been terminated, or was terminated /// during the execution of this method. @@ -2818,7 +2855,7 @@ private void DoWriteError(object obj) /// but the command failure will ultimately be /// , /// - internal void _WriteErrorSkipAllowCheck(ErrorRecord errorRecord, ActionPreference? actionPreference = null, bool isNativeError = false) + internal void _WriteErrorSkipAllowCheck(ErrorRecord errorRecord, ActionPreference? actionPreference = null, bool isFromNativeStdError = false) { ThrowIfStopping(); @@ -2838,7 +2875,7 @@ internal void _WriteErrorSkipAllowCheck(ErrorRecord errorRecord, ActionPreferenc this.PipelineProcessor.LogExecutionError(_thisCommand.MyInvocation, errorRecord); } - if (!(ExperimentalFeature.IsEnabled("PSNotApplyErrorActionToStderr") && isNativeError)) + if (!isFromNativeStdError) { this.PipelineProcessor.ExecutionFailed = true; @@ -2904,7 +2941,7 @@ internal void _WriteErrorSkipAllowCheck(ErrorRecord errorRecord, ActionPreferenc // when tracing), so don't add the member again. // We don't add a note property on messages that comes from stderr stream. - if (!isNativeError) + if (!isFromNativeStdError) { errorWrap.WriteStream = WriteStreamType.Error; } @@ -3204,7 +3241,7 @@ internal SwitchParameter UseTransaction private bool _debugFlag = false; /// - /// Debug tell the command system to provide Programmer/Support type messages to understand what is really occuring + /// Debug tell the command system to provide Programmer/Support type messages to understand what is really occurring /// and give the user the opportunity to stop or debug the situation. /// /// @@ -3241,8 +3278,7 @@ internal SwitchParameter WhatIf { if (!IsWhatIfFlagSet && !_isWhatIfPreferenceCached) { - bool defaultUsed = false; - _whatIfFlag = Context.GetBooleanPreference(SpecialVariables.WhatIfPreferenceVarPath, _whatIfFlag, out defaultUsed); + _whatIfFlag = Context.GetBooleanPreference(SpecialVariables.WhatIfPreferenceVarPath, _whatIfFlag, out _); _isWhatIfPreferenceCached = true; } @@ -3309,7 +3345,7 @@ internal ActionPreference ProgressPreference { get { - if (_isProgressPreferenceSet) + if (IsProgressActionSet) return _progressPreference; if (!_isProgressPreferenceCached) @@ -3330,12 +3366,14 @@ internal ActionPreference ProgressPreference } _progressPreference = value; - _isProgressPreferenceSet = true; + IsProgressActionSet = true; } } private ActionPreference _progressPreference = InitialSessionState.DefaultProgressPreference; - private bool _isProgressPreferenceSet = false; + + internal bool IsProgressActionSet { get; private set; } = false; + private bool _isProgressPreferenceCached = false; /// @@ -3735,8 +3773,10 @@ internal void SetVariableListsInPipe() { Diagnostics.Assert(_thisCommand is PSScriptCmdlet, "this is only done for script cmdlets"); - if (_outVarList != null) + if (_outVarList != null && !OutputPipe.IgnoreOutVariableList) { + // A null pipe is used when executing the 'Clean' block of a PSScriptCmdlet. + // In such a case, we don't capture output to the out variable list. this.OutputPipe.AddVariableList(VariableStreamKind.Output, _outVarList); } @@ -3757,26 +3797,13 @@ internal void SetVariableListsInPipe() if (this.PipelineVariable != null) { - // _state can be null if the current script block is dynamicparam, etc. - if (_state != null) - { - // Create the pipeline variable - _state.PSVariable.Set(_pipelineVarReference); - - // Get the reference again in case we re-used one from the - // same scope. - _pipelineVarReference = _state.PSVariable.Get(this.PipelineVariable); - } - this.OutputPipe.SetPipelineVariable(_pipelineVarReference); } } internal void RemoveVariableListsInPipe() { - // Diagnostics.Assert(thisCommand is PSScriptCmdlet, "this is only done for script cmdlets"); - - if (_outVarList != null) + if (_outVarList != null && !OutputPipe.IgnoreOutVariableList) { this.OutputPipe.RemoveVariableList(VariableStreamKind.Output, _outVarList); } @@ -3799,9 +3826,6 @@ internal void RemoveVariableListsInPipe() if (this.PipelineVariable != null) { this.OutputPipe.RemovePipelineVariable(); - // '_state' could be null when a 'DynamicParam' block runs because the 'DynamicParam' block runs in 'DoPrepare', - // before 'PipelineProcessor.SetupParameterVariables' is called, where '_state' is initialized. - _state?.PSVariable.Remove(this.PipelineVariable); } } } diff --git a/src/System.Management.Automation/engine/MshMemberInfo.cs b/src/System.Management.Automation/engine/MshMemberInfo.cs index 73ee243f6e8..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 /// /// Enumerates all possible types of members. /// - [TypeConverterAttribute(typeof(LanguagePrimitives.EnumMultipleTypeConverter))] - [FlagsAttribute()] + [TypeConverter(typeof(LanguagePrimitives.EnumMultipleTypeConverter))] + [Flags] public enum PSMemberTypes { /// @@ -120,8 +120,8 @@ public enum PSMemberTypes /// /// Enumerator for all possible views available on a PSObject. /// - [TypeConverterAttribute(typeof(LanguagePrimitives.EnumMultipleTypeConverter))] - [FlagsAttribute()] + [TypeConverter(typeof(LanguagePrimitives.EnumMultipleTypeConverter))] + [Flags] public enum PSMemberViewTypes { /// @@ -148,7 +148,7 @@ public enum PSMemberViewTypes /// /// Match options. /// - [FlagsAttribute] + [Flags] internal enum MshMemberMatchOptions { /// @@ -1909,9 +1909,18 @@ internal class PSMethodInvocationConstraints internal PSMethodInvocationConstraints( Type methodTargetType, Type[] parameterTypes) + : this(methodTargetType, parameterTypes, genericTypeParameters: null) { - this.MethodTargetType = methodTargetType; - _parameterTypes = parameterTypes; + } + + internal PSMethodInvocationConstraints( + Type methodTargetType, + Type[] parameterTypes, + object[] genericTypeParameters) + { + MethodTargetType = methodTargetType; + ParameterTypes = parameterTypes; + GenericTypeParameters = genericTypeParameters; } /// @@ -1922,9 +1931,12 @@ internal PSMethodInvocationConstraints( /// /// If then there are no constraints /// - public IEnumerable ParameterTypes => _parameterTypes; + public Type[] ParameterTypes { get; } - private readonly Type[] _parameterTypes; + /// + /// Gets the generic type parameters for the method invocation. + /// + public object[] GenericTypeParameters { get; } internal static bool EqualsForCollection(ICollection xs, ICollection ys) { @@ -1946,8 +1958,6 @@ internal static bool EqualsForCollection(ICollection xs, ICollection ys return xs.SequenceEqual(ys); } - // TODO: IEnumerable genericTypeParameters { get; private set; } - public bool Equals(PSMethodInvocationConstraints other) { if (other is null) @@ -1965,7 +1975,12 @@ public bool Equals(PSMethodInvocationConstraints other) return false; } - if (!EqualsForCollection(_parameterTypes, other._parameterTypes)) + if (!EqualsForCollection(ParameterTypes, other.ParameterTypes)) + { + return false; + } + + if (!EqualsForCollection(GenericTypeParameters, other.GenericTypeParameters)) { return false; } @@ -1994,36 +2009,53 @@ public override bool Equals(object obj) } public override int GetHashCode() - { - // algorithm based on https://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode - unchecked - { - int result = 61; - - result = result * 397 + (MethodTargetType != null ? MethodTargetType.GetHashCode() : 0); - result = result * 397 + ParameterTypes.SequenceGetHashCode(); - - return result; - } - } + => HashCode.Combine(MethodTargetType, ParameterTypes.SequenceGetHashCode(), GenericTypeParameters.SequenceGetHashCode()); public override string ToString() { StringBuilder sb = new StringBuilder(); string separator = string.Empty; - if (MethodTargetType != null) + if (MethodTargetType is not null) { sb.Append("this: "); sb.Append(ToStringCodeMethods.Type(MethodTargetType, dropNamespaces: true)); separator = " "; } - if (_parameterTypes != null) + if (GenericTypeParameters is not null) + { + sb.Append(separator); + sb.Append("genericTypeParams: "); + + separator = string.Empty; + foreach (object parameter in GenericTypeParameters) + { + sb.Append(separator); + + switch (parameter) + { + case Type paramType: + sb.Append(ToStringCodeMethods.Type(paramType, dropNamespaces: true)); + break; + case ITypeName paramTypeName: + sb.Append(paramTypeName.ToString()); + break; + default: + throw new ArgumentException("Unexpected value"); + } + + separator = ", "; + } + + separator = " "; + } + + if (ParameterTypes is not null) { sb.Append(separator); sb.Append("args: "); separator = string.Empty; - foreach (var p in _parameterTypes) + foreach (var p in ParameterTypes) { sb.Append(separator); sb.Append(ToStringCodeMethods.Type(p, dropNamespaces: true)); @@ -2244,10 +2276,7 @@ public override object Invoke(params object[] arguments) newArguments[i + 1] = arguments[i]; } - if (_codeReferenceMethodInformation == null) - { - _codeReferenceMethodInformation = DotNetAdapter.GetMethodInformationArray(new[] { CodeReference }); - } + _codeReferenceMethodInformation ??= DotNetAdapter.GetMethodInformationArray(new[] { CodeReference }); Adapter.GetBestMethodAndArguments(CodeReference.Name, _codeReferenceMethodInformation, newArguments, out object[] convertedArguments); @@ -2600,10 +2629,7 @@ internal static PSMethod Create(string name, DotNetAdapter dotNetInstanceAdapter return new PSMethod(name, dotNetInstanceAdapter, baseObject, method, isSpecial, isHidden); } - if (method.PSMethodCtor == null) - { - method.PSMethodCtor = CreatePSMethodConstructor(method.methodInformationStructures); - } + method.PSMethodCtor ??= CreatePSMethodConstructor(method.methodInformationStructures); return method.PSMethodCtor.Invoke(name, dotNetInstanceAdapter, baseObject, method, isSpecial, isHidden); } @@ -2659,7 +2685,7 @@ private static Type GetMethodGroupType(MethodInfo methodInfo) return DelegateHelpers.MakeDelegate(methodTypes); } - catch (TypeLoadException) + catch (Exception) { return typeof(Func); } @@ -3339,8 +3365,7 @@ internal override PSMemberInfoInternalCollection InternalMembers break; default: Diagnostics.Assert(false, - string.Format(CultureInfo.InvariantCulture, - "PSInternalMemberSet cannot process {0}", name)); + string.Create(CultureInfo.InvariantCulture, $"PSInternalMemberSet cannot process {name}")); break; } } @@ -5035,7 +5060,7 @@ internal struct Enumerator : IEnumerator private readonly PSMemberInfoInternalCollection _allMembers; /// - /// Constructs this instance to enumerate over members. + /// Initializes a new instance of the class to enumerate over members. /// /// Members we are enumerating. internal Enumerator(PSMemberInfoIntegratingCollection integratingCollection) @@ -5063,8 +5088,8 @@ internal Enumerator(PSMemberInfoIntegratingCollection integratingCollection) /// Moves to the next element in the enumeration. /// /// - /// false if there are no more elements to enumerate - /// true otherwise + /// If there are no more elements to enumerate, returns false. + /// Returns true otherwise. /// public bool MoveNext() { @@ -5093,7 +5118,7 @@ public bool MoveNext() } /// - /// Current PSMemberInfo in the enumeration. + /// Gets the current PSMemberInfo in the enumeration. /// /// For invalid arguments. T IEnumerator.Current diff --git a/src/System.Management.Automation/engine/MshObject.cs b/src/System.Management.Automation/engine/MshObject.cs index 7ea7ef1bd72..8303058759d 100644 --- a/src/System.Management.Automation/engine/MshObject.cs +++ b/src/System.Management.Automation/engine/MshObject.cs @@ -44,7 +44,6 @@ namespace System.Management.Automation /// but there is no established scenario for doing this, nor has it been tested. /// [TypeDescriptionProvider(typeof(PSObjectTypeDescriptionProvider))] - [Serializable] public class PSObject : IFormattable, IComparable, ISerializable, IDynamicMetaObjectProvider { #region constructors @@ -490,6 +489,7 @@ internal static AdapterSet GetMappedAdapter(object obj, TypeTable typeTable) if (result == null) { +#if !UNIX if (objectType.IsCOMObject) { // All WinRT types are COM types. @@ -526,6 +526,9 @@ internal static AdapterSet GetMappedAdapter(object obj, TypeTable typeTable) { result = PSObject.s_dotNetInstanceAdapterSet; } +#else + result = PSObject.s_dotNetInstanceAdapterSet; +#endif } var existingOrNew = s_adapterMapping.GetOrAdd(objectType, result); @@ -674,13 +677,10 @@ internal PSMemberInfoInternalCollection InstanceMembers { lock (_lockObject) { - if (_instanceMembers == null) - { - _instanceMembers = - s_instanceMembersResurrectionTable.GetValue( - GetKeyForResurrectionTables(this), - _ => new PSMemberInfoInternalCollection()); - } + _instanceMembers ??= + s_instanceMembersResurrectionTable.GetValue( + GetKeyForResurrectionTables(this), + _ => new PSMemberInfoInternalCollection()); } } @@ -721,10 +721,7 @@ private AdapterSet InternalAdapterSet { lock (_lockObject) { - if (_adapterSet == null) - { - _adapterSet = GetMappedAdapter(_immediateBaseObject, GetTypeTable()); - } + _adapterSet ??= GetMappedAdapter(_immediateBaseObject, GetTypeTable()); } } @@ -743,10 +740,7 @@ public PSMemberInfoCollection Members { lock (_lockObject) { - if (_members == null) - { - _members = new PSMemberInfoIntegratingCollection(this, s_memberCollection); - } + _members ??= new PSMemberInfoIntegratingCollection(this, s_memberCollection); } } @@ -765,10 +759,7 @@ public PSMemberInfoCollection Properties { lock (_lockObject) { - if (_properties == null) - { - _properties = new PSMemberInfoIntegratingCollection(this, s_propertyCollection); - } + _properties ??= new PSMemberInfoIntegratingCollection(this, s_propertyCollection); } } @@ -787,10 +778,7 @@ public PSMemberInfoCollection Methods { lock (_lockObject) { - if (_methods == null) - { - _methods = new PSMemberInfoIntegratingCollection(this, s_methodCollection); - } + _methods ??= new PSMemberInfoIntegratingCollection(this, s_methodCollection); } } @@ -2059,7 +2047,7 @@ internal static void CopyDeserializerFields(PSObject source, PSObject target) /// /// Object which is set as core. /// If true, overwrite the type information. - ///This method is to be used only by Serialization code + /// This method is to be used only by Serialization code internal void SetCoreOnDeserialization(object value, bool overrideTypeInfo) { Diagnostics.Assert(this.ImmediateBaseObjectIsEmpty, "BaseObject should be PSCustomObject for deserialized objects"); diff --git a/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs b/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs index 6e941b4c5f1..11955713c7e 100644 --- a/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs +++ b/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs @@ -410,10 +410,7 @@ private void CheckAndAddProperty(PSPropertyInfo propertyInfo, Attribute[] attrib } } - if (propertyAttributes == null) - { - propertyAttributes = new AttributeCollection(); - } + propertyAttributes ??= new AttributeCollection(); typeDescriptor.WriteLine("Adding property \"{0}\".", propertyInfo.Name); diff --git a/src/System.Management.Automation/engine/MshReference.cs b/src/System.Management.Automation/engine/MshReference.cs index a021e09f794..fa8b6f13583 100644 --- a/src/System.Management.Automation/engine/MshReference.cs +++ b/src/System.Management.Automation/engine/MshReference.cs @@ -8,7 +8,7 @@ namespace System.Management.Automation { /// - /// Define type for a reference object in Monad scripting language. + /// Define type for a reference object in PowerShell scripting language. /// /// /// This class is used to describe both kinds of references: diff --git a/src/System.Management.Automation/engine/MshSecurityException.cs b/src/System.Management.Automation/engine/MshSecurityException.cs index 6ba04e4af7c..07c70ec7317 100644 --- a/src/System.Management.Automation/engine/MshSecurityException.cs +++ b/src/System.Management.Automation/engine/MshSecurityException.cs @@ -8,7 +8,6 @@ namespace System.Management.Automation /// /// This is a wrapper for exception class SecurityException. /// - [Serializable] public class PSSecurityException : RuntimeException { #region ctor @@ -34,19 +33,11 @@ public PSSecurityException() /// Serialization information. /// Streaming context. /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSSecurityException(SerializationInfo info, StreamingContext context) - : base(info, context) { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - "UnauthorizedAccess", - ErrorCategory.SecurityError, - null); - _errorRecord.ErrorDetails = new ErrorDetails(SessionStateStrings.CanNotRun); - _message = _errorRecord.ErrorDetails.Message; - // no fields, nothing more to serialize - // no need for a GetObjectData implementation + throw new NotSupportedException(); } /// @@ -93,14 +84,11 @@ public override ErrorRecord ErrorRecord { get { - if (_errorRecord == null) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - "UnauthorizedAccess", - ErrorCategory.SecurityError, - null); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + "UnauthorizedAccess", + ErrorCategory.SecurityError, + null); return _errorRecord; } diff --git a/src/System.Management.Automation/engine/MshSnapinQualifiedName.cs b/src/System.Management.Automation/engine/MshSnapinQualifiedName.cs index a74cae5252d..b4cf7c16eef 100644 --- a/src/System.Management.Automation/engine/MshSnapinQualifiedName.cs +++ b/src/System.Management.Automation/engine/MshSnapinQualifiedName.cs @@ -10,7 +10,7 @@ namespace System.Management.Automation /// /// A class representing a name that is qualified by the PSSnapin name. /// - internal class PSSnapinQualifiedName + internal sealed class PSSnapinQualifiedName { private PSSnapinQualifiedName(string[] splitName) { @@ -68,7 +68,7 @@ private PSSnapinQualifiedName(string[] splitName) { if (name == null) return null; - string[] splitName = name.Split(Utils.Separators.Backslash); + string[] splitName = name.Split('\\'); if (splitName.Length == 0 || splitName.Length > 2) return null; var result = new PSSnapinQualifiedName(splitName); diff --git a/src/System.Management.Automation/engine/NativeCommand.cs b/src/System.Management.Automation/engine/NativeCommand.cs index 822d4cf82d9..80748605a98 100644 --- a/src/System.Management.Automation/engine/NativeCommand.cs +++ b/src/System.Management.Automation/engine/NativeCommand.cs @@ -26,8 +26,7 @@ internal override void DoStopProcessing() { try { - if (_myCommandProcessor != null) - _myCommandProcessor.StopProcessing(); + _myCommandProcessor?.StopProcessing(); } catch (Exception) { diff --git a/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs b/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs index e0c643c3d3b..0124b01332c 100644 --- a/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs +++ b/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Collections; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; @@ -82,7 +83,7 @@ internal void BindParameters(Collection parameters) if (parameter.ParameterNameSpecified) { Diagnostics.Assert(!parameter.ParameterText.Contains(' '), "Parameters cannot have whitespace"); - PossiblyGlobArg(parameter.ParameterText, StringConstantType.BareWord); + PossiblyGlobArg(parameter.ParameterText, parameter, usedQuotes: false); if (parameter.SpaceAfterParameter) { @@ -107,30 +108,22 @@ internal void BindParameters(Collection parameters) // windbg -k com:port=\\devbox\pipe\debug,pipe,resets=0,reconnect // The parser produced an array of strings but marked the parameter so we // can properly reconstruct the correct command line. - StringConstantType stringConstantType = StringConstantType.BareWord; + bool usedQuotes = false; ArrayLiteralAst arrayLiteralAst = null; switch (parameter?.ArgumentAst) { case StringConstantExpressionAst sce: - stringConstantType = sce.StringConstantType; + usedQuotes = sce.StringConstantType != StringConstantType.BareWord; break; case ExpandableStringExpressionAst ese: - stringConstantType = ese.StringConstantType; + usedQuotes = ese.StringConstantType != StringConstantType.BareWord; break; case ArrayLiteralAst ala: arrayLiteralAst = ala; break; } - // Prior to PSNativePSPathResolution experimental feature, a single quote worked the same as a double quote - // so if the feature is not enabled, we treat any quotes as double quotes. When this feature is no longer - // experimental, this code here needs to be removed. - if (!ExperimentalFeature.IsEnabled("PSNativePSPathResolution") && stringConstantType == StringConstantType.SingleQuoted) - { - stringConstantType = StringConstantType.DoubleQuoted; - } - - AppendOneNativeArgument(Context, argValue, arrayLiteralAst, sawVerbatimArgumentMarker, stringConstantType); + AppendOneNativeArgument(Context, parameter, argValue, arrayLiteralAst, sawVerbatimArgumentMarker, usedQuotes); } } } @@ -151,6 +144,69 @@ internal string Arguments private readonly StringBuilder _arguments = new StringBuilder(); + internal string[] ArgumentList + { + get + { + return _argumentList.ToArray(); + } + } + + /// + /// Add an argument to the ArgumentList. + /// We may need to construct the argument out of the parameter text and the argument + /// in the case that we have a parameter that appears as "-switch:value". + /// + /// The parameter associated with the operation. + /// The value used with parameter. + internal void AddToArgumentList(CommandParameterInternal parameter, string argument) + { + if (parameter.ParameterNameSpecified && parameter.ParameterText.EndsWith(":")) + { + if (argument != parameter.ParameterText) + { + // Only combine the text and argument if there was no space after the parameter, + // otherwise, add the parameter and arguments as separate elements. + if (parameter.SpaceAfterParameter) + { + _argumentList.Add(parameter.ParameterText); + _argumentList.Add(argument); + } + else + { + _argumentList.Add(parameter.ParameterText + argument); + } + } + } + else + { + _argumentList.Add(argument); + } + } + + private readonly List _argumentList = new List(); + + /// + /// Gets a value indicating whether to use an ArgumentList or string for arguments when invoking a native executable. + /// + internal NativeArgumentPassingStyle ArgumentPassingStyle + { + get + { + try + { + var preference = LanguagePrimitives.ConvertTo( + Context.GetVariableValue(SpecialVariables.NativeArgumentPassingVarPath, NativeArgumentPassingStyle.Standard)); + return preference; + } + catch + { + // The value is not convertible send back Legacy + return NativeArgumentPassingStyle.Legacy; + } + } + } + #endregion internal members #region private members @@ -161,24 +217,27 @@ internal string Arguments /// each of which will be stringized. /// /// Execution context instance. + /// The parameter associated with the operation. /// The object to append. /// If the argument was an array literal, the Ast, otherwise null. /// True if the argument occurs after --%. - /// Bare, SingleQuoted, or DoubleQuoted. - private void AppendOneNativeArgument(ExecutionContext context, object obj, ArrayLiteralAst argArrayAst, bool sawVerbatimArgumentMarker, StringConstantType stringConstantType) + /// True if the argument was a quoted string (single or double). + private void AppendOneNativeArgument(ExecutionContext context, CommandParameterInternal parameter, object obj, ArrayLiteralAst argArrayAst, bool sawVerbatimArgumentMarker, bool usedQuotes) { IEnumerator list = LanguagePrimitives.GetEnumerator(obj); - Diagnostics.Assert((argArrayAst == null) || obj is object[] && ((object[])obj).Length == argArrayAst.Elements.Count, "array argument and ArrayLiteralAst differ in number of elements"); + Diagnostics.Assert((argArrayAst == null) || (obj is object[] && ((object[])obj).Length == argArrayAst.Elements.Count), "array argument and ArrayLiteralAst differ in number of elements"); int currentElement = -1; string separator = string.Empty; do { string arg; + object currentObj; if (list == null) { arg = PSObject.ToStringParser(context, obj); + currentObj = obj; } else { @@ -187,7 +246,8 @@ private void AppendOneNativeArgument(ExecutionContext context, object obj, Array break; } - arg = PSObject.ToStringParser(context, ParserOps.Current(null, list)); + currentObj = ParserOps.Current(null, list); + arg = PSObject.ToStringParser(context, currentObj); currentElement += 1; if (currentElement != 0) @@ -198,12 +258,16 @@ private void AppendOneNativeArgument(ExecutionContext context, object obj, Array if (!string.IsNullOrEmpty(arg)) { + // Only add the separator to the argument string rather than adding a separator to the ArgumentList. _arguments.Append(separator); if (sawVerbatimArgumentMarker) { arg = Environment.ExpandEnvironmentVariables(arg); _arguments.Append(arg); + + // we need to split the argument on spaces + _argumentList.AddRange(arg.Split(' ', StringSplitOptions.RemoveEmptyEntries)); } else { @@ -223,18 +287,11 @@ private void AppendOneNativeArgument(ExecutionContext context, object obj, Array if (NeedQuotes(arg)) { _arguments.Append('"'); - - if (stringConstantType == StringConstantType.DoubleQuoted) - { - _arguments.Append(ResolvePath(arg, Context)); - } - else - { - _arguments.Append(arg); - } + AddToArgumentList(parameter, arg); // need to escape all trailing backslashes so the native command receives it correctly // according to http://www.daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESDOC + _arguments.Append(arg); for (int i = arg.Length - 1; i >= 0 && arg[i] == '\\'; i--) { _arguments.Append('\\'); @@ -244,179 +301,152 @@ private void AppendOneNativeArgument(ExecutionContext context, object obj, Array } else { - PossiblyGlobArg(arg, stringConstantType); + if (argArrayAst != null && ArgumentPassingStyle != NativeArgumentPassingStyle.Legacy) + { + // We have a literal array, so take the extent, break it on spaces and add them to the argument list. + foreach (string element in argArrayAst.Extent.Text.Split(' ', StringSplitOptions.RemoveEmptyEntries)) + { + PossiblyGlobArg(element, parameter, usedQuotes); + } + + break; + } + else + { + PossiblyGlobArg(arg, parameter, usedQuotes); + } } } } + else if (ArgumentPassingStyle != NativeArgumentPassingStyle.Legacy && currentObj != null) + { + // add empty strings to arglist, but not nulls + AddToArgumentList(parameter, arg); + } } while (list != null); } /// - /// On Windows, just append . + /// On Windows, do tilde expansion, otherwise just append . /// On Unix, do globbing as appropriate, otherwise just append . /// /// The argument that possibly needs expansion. - /// Bare, SingleQuoted, or DoubleQuoted. - private void PossiblyGlobArg(string arg, StringConstantType stringConstantType) + /// The parameter associated with the operation. + /// True if the argument was a quoted string (single or double). + private void PossiblyGlobArg(string arg, CommandParameterInternal parameter, bool usedQuotes) { var argExpanded = false; #if UNIX // On UNIX systems, we expand arguments containing wildcard expressions against // the file system just like bash, etc. - - if (stringConstantType == StringConstantType.BareWord) + if (!usedQuotes && WildcardPattern.ContainsWildcardCharacters(arg)) { - if (WildcardPattern.ContainsWildcardCharacters(arg)) + // See if the current working directory is a filesystem provider location + // We won't do the expansion if it isn't since native commands can only access the file system. + var cwdinfo = Context.EngineSessionState.CurrentLocation; + + // If it's a filesystem location then expand the wildcards + if (cwdinfo.Provider.Name.Equals(FileSystemProvider.ProviderName, StringComparison.OrdinalIgnoreCase)) { - // See if the current working directory is a filesystem provider location - // We won't do the expansion if it isn't since native commands can only access the file system. - var cwdinfo = Context.EngineSessionState.CurrentLocation; + // On UNIX, paths starting with ~ or absolute paths are not normalized + bool normalizePath = arg.Length == 0 || !(arg[0] == '~' || arg[0] == '/'); - // If it's a filesystem location then expand the wildcards - if (cwdinfo.Provider.Name.Equals(FileSystemProvider.ProviderName, StringComparison.OrdinalIgnoreCase)) + // See if there are any matching paths otherwise just add the pattern as the argument + Collection paths = null; + try { - // On UNIX, paths starting with ~ or absolute paths are not normalized - bool normalizePath = arg.Length == 0 || !(arg[0] == '~' || arg[0] == '/'); - - // See if there are any matching paths otherwise just add the pattern as the argument - Collection paths = null; - try - { - paths = Context.EngineSessionState.InvokeProvider.ChildItem.Get(arg, false); - } - catch - { - // Fallthrough will append the pattern unchanged. - } + paths = Context.EngineSessionState.InvokeProvider.ChildItem.Get(arg, false); + } + catch + { + // Fallthrough will append the pattern unchanged. + } - // Expand paths, but only from the file system. - if (paths?.Count > 0 && paths.All(p => p.BaseObject is FileSystemInfo)) + // Expand paths, but only from the file system. + if (paths?.Count > 0 && paths.All(p => p.BaseObject is FileSystemInfo)) + { + var sep = string.Empty; + foreach (var path in paths) { - var sep = string.Empty; - foreach (var path in paths) + _arguments.Append(sep); + sep = " "; + var expandedPath = (path.BaseObject as FileSystemInfo).FullName; + if (normalizePath) { - _arguments.Append(sep); - sep = " "; - var expandedPath = (path.BaseObject as FileSystemInfo).FullName; - if (normalizePath) - { - expandedPath = - Context.SessionState.Path.NormalizeRelativePath(expandedPath, cwdinfo.ProviderPath); - } - // If the path contains spaces, then add quotes around it. - if (NeedQuotes(expandedPath)) - { - _arguments.Append('"'); - _arguments.Append(expandedPath); - _arguments.Append('"'); - } - else - { - _arguments.Append(expandedPath); - } - - argExpanded = true; + expandedPath = + Context.SessionState.Path.NormalizeRelativePath(expandedPath, cwdinfo.ProviderPath); + } + // If the path contains spaces, then add quotes around it. + if (NeedQuotes(expandedPath)) + { + _arguments.Append('"'); + _arguments.Append(expandedPath); + _arguments.Append('"'); + } + else + { + _arguments.Append(expandedPath); } + + AddToArgumentList(parameter, expandedPath); + argExpanded = true; } } } - else + } + else if (!usedQuotes) + { + // Even if there are no wildcards, we still need to possibly + // expand ~ into the filesystem provider home directory path + if (ExpandTilde(arg, parameter)) { - // Even if there are no wildcards, we still need to possibly - // expand ~ into the filesystem provider home directory path - ProviderInfo fileSystemProvider = Context.EngineSessionState.GetSingleProvider(FileSystemProvider.ProviderName); - string home = fileSystemProvider.Home; - if (string.Equals(arg, "~")) - { - _arguments.Append(home); - argExpanded = true; - } - else if (arg.StartsWith("~/", StringComparison.OrdinalIgnoreCase)) - { - var replacementString = home + arg.Substring(1); - _arguments.Append(replacementString); - argExpanded = true; - } + argExpanded = true; } } -#endif // UNIX - - if (stringConstantType != StringConstantType.SingleQuoted) +#else + if (!usedQuotes && ExperimentalFeature.IsEnabled(ExperimentalFeature.PSNativeWindowsTildeExpansion)) { - arg = ResolvePath(arg, Context); + if (ExpandTilde(arg, parameter)) + { + argExpanded = true; + } } +#endif if (!argExpanded) { _arguments.Append(arg); + AddToArgumentList(parameter, arg); } } /// - /// Check if string is prefixed by psdrive, if so, expand it if filesystem path. + /// Replace tilde for unquoted arguments in the form ~ and ~/. For windows, ~\ is also expanded. /// - /// The potential PSPath to resolve. - /// The current ExecutionContext. - /// Resolved PSPath if applicable otherwise the original path - internal static string ResolvePath(string path, ExecutionContext context) + /// The argument that possibly needs expansion. + /// The parameter associated with the operation. + /// True if tilde expansion occurred. + private bool ExpandTilde(string arg, CommandParameterInternal parameter) { - if (ExperimentalFeature.IsEnabled("PSNativePSPathResolution")) + var fileSystemProvider = Context.EngineSessionState.GetSingleProvider(FileSystemProvider.ProviderName); + var home = fileSystemProvider.Home; + if (string.Equals(arg, "~")) { -#if !UNIX - // on Windows, we need to expand ~ to point to user's home path - if (string.Equals(path, "~", StringComparison.Ordinal) || path.StartsWith(TildeDirectorySeparator, StringComparison.Ordinal) || path.StartsWith(TildeAltDirectorySeparator, StringComparison.Ordinal)) - { - try - { - ProviderInfo fileSystemProvider = context.EngineSessionState.GetSingleProvider(FileSystemProvider.ProviderName); - return new StringBuilder(fileSystemProvider.Home) - .Append(path.Substring(1)) - .Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar) - .ToString(); - } - catch - { - return path; - } - } - - // check if the driveName is an actual disk drive on Windows, if so, no expansion - if (path.Length >= 2 && path[1] == ':') - { - foreach (var drive in DriveInfo.GetDrives()) - { - if (drive.Name.StartsWith(new string(path[0], 1), StringComparison.OrdinalIgnoreCase)) - { - return path; - } - } - } -#endif - - if (path.Contains(':')) - { - LocationGlobber globber = new LocationGlobber(context.SessionState); - try - { - ProviderInfo providerInfo; - - // replace the argument with resolved path if it's a filesystem path - string pspath = globber.GetProviderPath(path, out providerInfo); - if (string.Equals(providerInfo.Name, FileSystemProvider.ProviderName, StringComparison.OrdinalIgnoreCase)) - { - path = pspath; - } - } - catch - { - // if it's not a provider path, do nothing - } - } + _arguments.Append(home); + AddToArgumentList(parameter, home); + return true; + } + else if (arg.StartsWith("~/") || (OperatingSystem.IsWindows() && arg.StartsWith(@"~\"))) + { + var replacementString = string.Concat(home, arg.AsSpan(1)); + _arguments.Append(replacementString); + AddToArgumentList(parameter, replacementString); + return true; } - return path; + return false; } /// @@ -446,7 +476,10 @@ internal static bool NeedQuotes(string stringToCheck) private static string GetEnumerableArgSeparator(ArrayLiteralAst arrayLiteralAst, int index) { - if (arrayLiteralAst == null) return " "; + if (arrayLiteralAst == null) + { + return " "; + } // index points to the *next* element, so we're looking for space between // it and the previous element. @@ -458,14 +491,25 @@ private static string GetEnumerableArgSeparator(ArrayLiteralAst arrayLiteralAst, var afterPrev = prev.Extent.EndOffset; var beforeNext = next.Extent.StartOffset - 1; - if (afterPrev == beforeNext) return ","; + if (afterPrev == beforeNext) + { + return ","; + } var arrayText = arrayExtent.Text; afterPrev -= arrayExtent.StartOffset; beforeNext -= arrayExtent.StartOffset; - if (arrayText[afterPrev] == ',') return ", "; - if (arrayText[beforeNext] == ',') return " ,"; + if (arrayText[afterPrev] == ',') + { + return ", "; + } + + if (arrayText[beforeNext] == ',') + { + return " ,"; + } + return " , "; } @@ -473,8 +517,6 @@ private static string GetEnumerableArgSeparator(ArrayLiteralAst arrayLiteralAst, /// The native command to bind to. /// private readonly NativeCommand _nativeCommand; - private static readonly string TildeDirectorySeparator = $"~{Path.DirectorySeparatorChar}"; - private static readonly string TildeAltDirectorySeparator = $"~{Path.AltDirectorySeparatorChar}"; #endregion private members } diff --git a/src/System.Management.Automation/engine/NativeCommandParameterBinderController.cs b/src/System.Management.Automation/engine/NativeCommandParameterBinderController.cs index ecdd4554abe..9a1dab71beb 100644 --- a/src/System.Management.Automation/engine/NativeCommandParameterBinderController.cs +++ b/src/System.Management.Automation/engine/NativeCommandParameterBinderController.cs @@ -38,6 +38,28 @@ internal string Arguments } } + /// + /// Gets the value of the command arguments as an array of strings. + /// + internal string[] ArgumentList + { + get + { + return ((NativeCommandParameterBinder)DefaultParameterBinder).ArgumentList; + } + } + + /// + /// Gets the value indicating what type of native argument binding to use. + /// + internal NativeArgumentPassingStyle ArgumentPassingStyle + { + get + { + return ((NativeCommandParameterBinder)DefaultParameterBinder).ArgumentPassingStyle; + } + } + /// /// Passes the binding directly through to the parameter binder. /// It does no verification against metadata. @@ -49,8 +71,7 @@ internal string Arguments /// Ignored. /// /// - /// True if the parameter was successfully bound. Any error condition - /// produces an exception. + /// True if the parameter was successfully bound. Any error condition produces an exception. /// internal override bool BindParameter( CommandParameterInternal argument, diff --git a/src/System.Management.Automation/engine/NativeCommandProcessor.cs b/src/System.Management.Automation/engine/NativeCommandProcessor.cs index 96ad84a6c46..371e1ff00ff 100644 --- a/src/System.Management.Automation/engine/NativeCommandProcessor.cs +++ b/src/System.Management.Automation/engine/NativeCommandProcessor.cs @@ -3,21 +3,24 @@ #pragma warning disable 1634, 1691 +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; -using System.ComponentModel; +using System.Management.Automation.Internal; +using System.Management.Automation.Runspaces; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; using System.Text; -using System.Collections; using System.Threading; -using System.Management.Automation.Internal; +using System.Threading.Tasks; using System.Xml; -using System.Runtime.InteropServices; +using Microsoft.PowerShell.Telemetry; using Dbg = System.Management.Automation.Diagnostics; -using System.Runtime.Serialization; -using System.Globalization; -using System.Diagnostics.CodeAnalysis; -using System.Collections.Concurrent; -using System.Collections.Generic; namespace System.Management.Automation { @@ -130,11 +133,89 @@ internal ProcessOutputObject(object data, MinishellStream stream) } } + #nullable enable + /// + /// This exception is used by the NativeCommandProcessor to indicate an error + /// when a native command returns a non-zero exit code. + /// + public sealed class NativeCommandExitException : RuntimeException + { + // NOTE: + // When implementing the native error action preference integration, + // reusing ApplicationFailedException was rejected. + // Instead of reusing a type already used in another scenario + // it was decided instead to use a fresh type to avoid conflating the two scenarios: + // * ApplicationFailedException: PowerShell was not able to complete execution of the application. + // * NativeCommandExitException: the application completed execution but returned a non-zero exit code. + + #region Constructors + + /// + /// Initializes a new instance of the class with information on the native + /// command, a specified error message and a specified error ID. + /// + /// The full path of the native command. + /// The exit code returned by the native command. + /// The process ID of the process before it ended. + /// The error message. + /// The PowerShell runtime error ID. + internal NativeCommandExitException(string path, int exitCode, int processId, string message, string errorId) + : base(message) + { + SetErrorId(errorId); + SetErrorCategory(ErrorCategory.NotSpecified); + Path = path; + ExitCode = exitCode; + ProcessId = processId; + } + + #endregion Constructors + + /// + /// Gets the path of the native command. + /// + public string? Path { get; } + + /// + /// Gets the exit code returned by the native command. + /// + public int ExitCode { get; } + + /// + /// Gets the native command's process ID. + /// + public int ProcessId { get; } + + } + #nullable restore + /// /// Provides way to create and execute native commands. /// 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 s_legacyFileExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) + { + ".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 s_legacyCommands = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "cmd", + "cscript", + "find", + "sqlcmd", + "wscript", + }; + #region ctor/native command properties /// @@ -221,16 +302,16 @@ private string Path } } + internal NativeCommandProcessor DownStreamNativeCommand { get; set; } + + internal bool UpstreamIsNativeCommand { get; set; } + + internal BytePipe StdOutDestination { get; set; } + #endregion ctor/native command properties #region parameter binder - /// - /// Variable which is set to true when prepare is called. - /// Parameter Binder should only be created after Prepare method is called. - /// - private bool _isPreparedCalled = false; - /// /// Parameter binder used by this command processor. /// @@ -247,8 +328,6 @@ private string Path /// internal ParameterBinderController NewParameterBinderController(InternalCommand command) { - Dbg.Assert(_isPreparedCalled, "parameter binder should not be created before prepared is called"); - if (_isMiniShell) { _nativeParameterBinderController = @@ -287,8 +366,6 @@ internal NativeCommandParameterBinderController NativeParameterBinderController /// internal override void Prepare(IDictionary psDefaultParameterValues) { - _isPreparedCalled = true; - // Check if the application is minishell _isMiniShell = IsMiniShell(); @@ -306,7 +383,7 @@ internal override void Prepare(IDictionary psDefaultParameterValues) catch (Exception) { // Do cleanup in case of exception - CleanUp(); + CleanUp(killBackgroundProcess: true); throw; } } @@ -318,9 +395,14 @@ internal override void ProcessRecord() { try { - while (Read()) + // If upstream is a native command it'll be writing directly to our stdin stream + // so we can skip reading here. + if (!UpstreamIsNativeCommand) { - _inputWriter.Add(Command.CurrentPipelineObject); + while (Read()) + { + _inputWriter.Add(Command.CurrentPipelineObject); + } } ConsumeAvailableNativeProcessOutput(blocking: false); @@ -328,7 +410,7 @@ internal override void ProcessRecord() catch (Exception) { // Do cleanup in case of exception - CleanUp(); + CleanUp(killBackgroundProcess: true); throw; } } @@ -357,7 +439,7 @@ internal override void ProcessRecord() /// /// Indicate if we have called 'NotifyBeginApplication()' on the host, so that - /// we can call the counterpart 'NotifyEndApplication' as approriate. + /// we can call the counterpart 'NotifyEndApplication' as appropriate. /// private bool _hasNotifiedBeginApplication; @@ -378,6 +460,59 @@ internal override void ProcessRecord() /// private readonly object _sync = new object(); + private SemaphoreSlim _processInitialized; + + internal async Task WaitForProcessInitializationAsync(CancellationToken cancellationToken) + { + SemaphoreSlim processInitialized = _processInitialized; + if (processInitialized is null) + { + lock (_sync) + { + processInitialized = _processInitialized ??= new SemaphoreSlim(0, 1); + } + } + + try + { + await processInitialized.WaitAsync(cancellationToken); + } + finally + { + processInitialized.Release(); + } + } + + /// + /// Creates a pipe representing the streaming of unprocessed bytes. + /// + /// + /// The stream that the pipe should represent. + /// for stdout, for stdin. + /// + /// A new byte pipe representing the specified stream. + internal BytePipe CreateBytePipe(bool stdout) => new NativeCommandProcessorBytePipe(this, stdout); + + /// + /// Gets the specified base for the underlying + /// . + /// + /// + /// The stream that should be retrieved. for + /// stdout, for stdin. + /// + /// The specified . + internal Stream GetStream(bool stdout) + { + Debug.Assert( + _nativeProcess is not null, + "Caller should verify that initialization has completed before attempting to get the underlying stream."); + + return stdout + ? _nativeProcess.StandardOutput.BaseStream + : _nativeProcess.StandardInput.BaseStream; + } + /// /// Executes the native command once all of the input has been gathered. /// @@ -409,6 +544,11 @@ private void InitNativeProcess() // Get the start info for the process. ProcessStartInfo startInfo = GetProcessStartInfo(redirectOutput, redirectError, redirectInput, soloCommand); + // Send Telemetry indicating what argument passing mode we are in. + ApplicationInsightsTelemetry.SendExperimentalUseData( + "PSWindowsNativeCommandArgPassing", + NativeParameterBinderController.ArgumentPassingStyle.ToString()); + #if !UNIX string commandPath = this.Path.ToLowerInvariant(); if (commandPath.EndsWith("powershell.exe") || commandPath.EndsWith("powershell_ise.exe")) @@ -430,15 +570,14 @@ private void InitNativeProcess() Exception exceptionToRethrow = null; try { - // If this process is being run standalone, tell the host, which might want - // to save off the window title or other such state as might be tweaked by - // the native process + // Before start the executable, tell the host, which might want to save off the + // window title or other such state as might be tweaked by the native process. + Command.Context.EngineHostInterface.NotifyBeginApplication(); + _hasNotifiedBeginApplication = true; + if (_runStandAlone) { - this.Command.Context.EngineHostInterface.NotifyBeginApplication(); - _hasNotifiedBeginApplication = true; - - // Also, store the Raw UI coordinates so that we can scrape the screen after + // Store the Raw UI coordinates so that we can scrape the screen after // if we are transcribing. if (_isTranscribing && (s_supportScreenScrape == true)) { @@ -467,13 +606,20 @@ private void InitNativeProcess() } catch (Win32Exception) { - // On Unix platforms, nothing can be further done, so just throw +#if UNIX + // On Unix platforms, nothing can be further done, so just throw. + throw; +#else // On headless Windows SKUs, there is no shell to fall back to, so just throw - if (!Platform.IsWindowsDesktop) { throw; } + if (!Platform.IsWindowsDesktop) + { + throw; + } // on Windows desktops, see if there is a file association for this command. If so then we'll use that. - string executable = FindExecutable(startInfo.FileName); + string executable = Interop.Windows.FindExecutable(startInfo.FileName); bool notDone = true; + // check to see what mode we should be in for argument passing if (!string.IsNullOrEmpty(executable)) { isWindowsApplication = IsWindowsApplication(executable); @@ -485,7 +631,16 @@ private void InitNativeProcess() string oldArguments = startInfo.Arguments; string oldFileName = startInfo.FileName; - startInfo.Arguments = "\"" + startInfo.FileName + "\" " + startInfo.Arguments; + // Check to see whether this executable should be using Legacy mode argument parsing + bool useSpecialArgumentPassing = UseSpecialArgumentPassing(oldFileName); + if (useSpecialArgumentPassing) + { + startInfo.Arguments = "\"" + oldFileName + "\" " + startInfo.Arguments; + } + else + { + startInfo.ArgumentList.Insert(0, oldFileName); + } startInfo.FileName = executable; try { @@ -495,7 +650,14 @@ private void InitNativeProcess() catch (Win32Exception) { // Restore the old filename and arguments to try shell execute last... - startInfo.Arguments = oldArguments; + if (useSpecialArgumentPassing) + { + startInfo.Arguments = oldArguments; + } + else + { + startInfo.ArgumentList.RemoveAt(0); + } startInfo.FileName = oldFileName; } } @@ -517,6 +679,13 @@ private void InitNativeProcess() throw; } } +#endif + } + + if (UpstreamIsNativeCommand) + { + _processInitialized ??= new SemaphoreSlim(0, 1); + _processInitialized.Release(); } } @@ -550,7 +719,7 @@ private void InitNativeProcess() lock (_sync) { - if (!_stopped) + if (!_stopped && !UpstreamIsNativeCommand) { _inputWriter.Start(_nativeProcess, inputFormat); } @@ -599,6 +768,8 @@ private void InitNativeProcess() } } + private AsyncByteStreamTransfer _stdOutByteTransfer; + private void InitOutputQueue() { // if output is redirected, start reading output of process in queue. @@ -608,9 +779,32 @@ private void InitOutputQueue() { if (!_stopped) { + if (CommandRuntime.ErrorMergeTo is MshCommandRuntime.MergeDataStream.Output) + { + StdOutDestination = null; + if (DownStreamNativeCommand is not null) + { + DownStreamNativeCommand.UpstreamIsNativeCommand = false; + DownStreamNativeCommand = null; + } + } + _nativeProcessOutputQueue = new BlockingCollection(); // we don't assign the handler to anything, because it's used only for objects marshaling - new ProcessOutputHandler(_nativeProcess, _nativeProcessOutputQueue); + BytePipe stdOutDestination = StdOutDestination ?? DownStreamNativeCommand?.CreateBytePipe(stdout: false); + + BytePipe stdOutSource = null; + if (stdOutDestination is not null) + { + stdOutSource = CreateBytePipe(stdout: true); + } + + _ = new ProcessOutputHandler( + _nativeProcess, + _nativeProcessOutputQueue, + stdOutDestination, + stdOutSource, + out _stdOutByteTransfer); } } } @@ -641,12 +835,9 @@ private ProcessOutputObject DequeueProcessOutput(bool blocking) return null; } - else - { - ProcessOutputObject record = null; - _nativeProcessOutputQueue.TryTake(out record); - return record; - } + + _nativeProcessOutputQueue.TryTake(out ProcessOutputObject record); + return record; } /// @@ -654,21 +845,38 @@ private ProcessOutputObject DequeueProcessOutput(bool blocking) /// private void ConsumeAvailableNativeProcessOutput(bool blocking) { - if (!_isRunningInBackground) + if (_isRunningInBackground) { - if (_nativeProcess.StartInfo.RedirectStandardOutput || _nativeProcess.StartInfo.RedirectStandardError) + return; + } + + bool stdOutRedirected = _nativeProcess.StartInfo.RedirectStandardOutput; + bool stdErrRedirected = _nativeProcess.StartInfo.RedirectStandardError; + if (stdOutRedirected && _stdOutByteTransfer is not null) + { + if (blocking) { - ProcessOutputObject record; - while ((record = DequeueProcessOutput(blocking)) != null) - { - if (this.Command.Context.CurrentPipelineStopping) - { - this.StopProcessing(); - return; - } + _stdOutByteTransfer.EOF.GetAwaiter().GetResult(); + } - ProcessOutputRecord(record); + if (!stdErrRedirected) + { + return; + } + } + + if (stdOutRedirected || stdErrRedirected) + { + ProcessOutputObject record; + while ((record = DequeueProcessOutput(blocking)) != null) + { + if (this.Command.Context.CurrentPipelineStopping) + { + this.StopProcessing(); + return; } + + ProcessOutputRecord(record); } } } @@ -681,7 +889,10 @@ internal override void Complete() if (!_isRunningInBackground) { // Wait for input writer to finish. - _inputWriter.Done(); + if (!UpstreamIsNativeCommand || _nativeProcess.StartInfo.RedirectStandardError) + { + _inputWriter.Done(); + } // read all the available output in the blocking way ConsumeAvailableNativeProcessOutput(blocking: true); @@ -725,8 +936,57 @@ internal override void Complete() } this.Command.Context.SetVariable(SpecialVariables.LastExitCodeVarPath, _nativeProcess.ExitCode); - if (_nativeProcess.ExitCode != 0) - this.commandRuntime.PipelineProcessor.ExecutionFailed = true; + if (_nativeProcess.ExitCode == 0) + { + return; + } + + this.commandRuntime.PipelineProcessor.ExecutionFailed = true; + + // We send telemetry information only if the feature is enabled. + // This shouldn't be done once, because it's a run-time check we should send telemetry every time. + // Report on the following conditions: + // - The variable is not present + // - The value is not set (variable is null) + // - The value is set to true or false + bool useDefaultSetting; + bool nativeErrorActionPreferenceSetting = Command.Context.GetBooleanPreference( + SpecialVariables.PSNativeCommandUseErrorActionPreferenceVarPath, + defaultPref: false, + out useDefaultSetting); + + // The variable is unset + if (useDefaultSetting) + { + ApplicationInsightsTelemetry.SendExperimentalUseData("PSNativeCommandErrorActionPreference", "unset"); + return; + } + + // Send the value that was set. + ApplicationInsightsTelemetry.SendExperimentalUseData("PSNativeCommandErrorActionPreference", nativeErrorActionPreferenceSetting.ToString()); + + // if it was explicitly set to false, return + if (!nativeErrorActionPreferenceSetting) + { + return; + } + + const string errorId = nameof(CommandBaseStrings.ProgramExitedWithNonZeroCode); + + string errorMsg = StringUtil.Format( + CommandBaseStrings.ProgramExitedWithNonZeroCode, + NativeCommandName, + _nativeProcess.ExitCode); + + var exception = new NativeCommandExitException( + Path, + _nativeProcess.ExitCode, + _nativeProcess.Id, + errorMsg, + errorId); + + var errorRecord = new ErrorRecord(exception, errorId, ErrorCategory.NotSpecified, targetObject: Path); + this.commandRuntime._WriteErrorSkipAllowCheck(errorRecord); } } catch (Win32Exception e) @@ -745,7 +1005,7 @@ internal override void Complete() finally { // Do some cleanup - CleanUp(); + CleanUp(killBackgroundProcess: false); } // An exception was thrown while attempting to run the program @@ -936,27 +1196,19 @@ private static void KillChildProcesses(int parentId, ProcessWithParentId[] curre /// /// /// - [ArchitectureSensitive] private static bool IsWindowsApplication(string fileName) { #if UNIX return false; #else - if (!Platform.IsWindowsDesktop) { return false; } - - // SHGetFileInfo() does not understand reparse points and returns 0 ("non exe or error") - // so we are trying to get a real path before. - // It is a workaround for Microsoft Store applications. - string realPath = Microsoft.PowerShell.Commands.InternalSymbolicLinkLinkCodeMethods.WinInternalGetTarget(fileName); - if (realPath is not null) + if (!Platform.IsWindowsDesktop) { - fileName = realPath; + return false; } - SHFILEINFO shinfo = new SHFILEINFO(); - IntPtr type = SHGetFileInfo(fileName, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_EXETYPE); + int type = Interop.Windows.SHGetFileInfo(fileName); - switch ((int)type) + switch (type) { case 0x0: // 0x0 = not an exe @@ -987,7 +1239,11 @@ internal void StopProcessing() { lock (_sync) { - if (_stopped) return; + if (_stopped) + { + return; + } + _stopped = true; } @@ -996,8 +1252,12 @@ internal void StopProcessing() if (!_runStandAlone) { // Stop input writer - _inputWriter.Stop(); + if (!UpstreamIsNativeCommand) + { + _inputWriter.Stop(); + } + _stdOutByteTransfer?.Dispose(); KillProcess(_nativeProcess); } } @@ -1008,21 +1268,34 @@ internal void StopProcessing() /// /// Aggressively clean everything up... /// - private void CleanUp() + /// If set, also terminate background process. + private void CleanUp(bool killBackgroundProcess) { // We need to call 'NotifyEndApplication' as appropriate during cleanup if (_hasNotifiedBeginApplication) { - this.Command.Context.EngineHostInterface.NotifyEndApplication(); + Command.Context.EngineHostInterface.NotifyEndApplication(); } try { - // Dispose the process if it's already created - if (_nativeProcess != null) + // on Unix, we need to kill the process (if not running in background) to ensure it terminates, + // as Dispose() merely closes the redirected streams and the process does not exit. + // However, on Windows, a winexe like notepad should continue running so we don't want to kill it. +#if UNIX + if (killBackgroundProcess || !_isRunningInBackground) { - _nativeProcess.Dispose(); + try + { + _nativeProcess?.Kill(); + } + catch + { + // Ignore all exceptions since it is cleanup. + } } +#endif + _nativeProcess?.Dispose(); } catch (Exception) { @@ -1038,7 +1311,7 @@ private void ProcessOutputRecord(ProcessOutputObject outputValue) ErrorRecord record = outputValue.Data as ErrorRecord; Dbg.Assert(record != null, "ProcessReader should ensure that data is ErrorRecord"); record.SetInvocationInfo(this.Command.MyInvocation); - this.commandRuntime._WriteErrorSkipAllowCheck(record, isNativeError: true); + this.commandRuntime._WriteErrorSkipAllowCheck(record, isFromNativeStdError: true); } else if (outputValue.Stream == MinishellStream.Output) { @@ -1096,56 +1369,81 @@ private void ProcessOutputRecord(ProcessOutputObject outputValue) } /// - /// Gets the start info for process. + /// Get whether we should treat this executable with special handling and use the legacy passing style. /// - /// - /// - /// - /// - /// - private ProcessStartInfo GetProcessStartInfo(bool redirectOutput, bool redirectError, bool redirectInput, bool soloCommand) + /// + private bool UseSpecialArgumentPassing(string filePath) => + NativeParameterBinderController.ArgumentPassingStyle switch + { + NativeArgumentPassingStyle.Legacy => true, + NativeArgumentPassingStyle.Windows => ShouldUseLegacyPassingStyle(filePath), + _ => false + }; + + /// + /// Gets the ProcessStartInfo for process. + /// + /// A boolean that indicates that, when true, output from the process is redirected to a stream, and otherwise is sent to stdout. + /// A boolean that indicates that, when true, error output from the process is redirected to a stream, and otherwise is sent to stderr. + /// A boolean that indicates that, when true, input to the process is taken from a stream, and otherwise is taken from stdin. + /// A boolean that indicates, when true, that the command to be executed is not part of a pipeline, and otherwise indicates that it is. + /// A ProcessStartInfo object which is the base of the native invocation. + private ProcessStartInfo GetProcessStartInfo( + bool redirectOutput, + bool redirectError, + bool redirectInput, + bool soloCommand) { - ProcessStartInfo startInfo = new ProcessStartInfo(); - startInfo.FileName = this.Path; + var startInfo = new ProcessStartInfo + { + FileName = this.Path + }; - if (IsExecutable(this.Path)) + if (!IsExecutable(this.Path)) { - startInfo.UseShellExecute = false; - if (redirectInput) + if (Platform.IsNanoServer || Platform.IsIoT) { - startInfo.RedirectStandardInput = true; + // Shell doesn't exist on headless SKUs, so documents cannot be associated with an application. + // Therefore, we cannot run document in this case. + throw InterpreterError.NewInterpreterException( + this.Path, + typeof(RuntimeException), + this.Command.InvocationExtent, + "CantActivateDocumentInPowerShellCore", + ParserStrings.CantActivateDocumentInPowerShellCore, + this.Path); } - if (redirectOutput) + // We only want to ShellExecute something that is standalone... + if (!soloCommand) { - startInfo.RedirectStandardOutput = true; - startInfo.StandardOutputEncoding = Console.OutputEncoding; + throw InterpreterError.NewInterpreterException( + this.Path, + typeof(RuntimeException), + this.Command.InvocationExtent, + "CantActivateDocumentInPipeline", + ParserStrings.CantActivateDocumentInPipeline, + this.Path); } - if (redirectError) - { - startInfo.RedirectStandardError = true; - startInfo.StandardErrorEncoding = Console.OutputEncoding; - } + startInfo.UseShellExecute = true; } else { - if (Platform.IsNanoServer || Platform.IsIoT) + startInfo.UseShellExecute = false; + startInfo.RedirectStandardInput = redirectInput; + + if (redirectOutput) { - // Shell doesn't exist on headless SKUs, so documents cannot be associated with an application. - // Therefore, we cannot run document in this case. - throw InterpreterError.NewInterpreterException(this.Path, typeof(RuntimeException), - this.Command.InvocationExtent, "CantActivateDocumentInPowerShellCore", ParserStrings.CantActivateDocumentInPowerShellCore, this.Path); + startInfo.RedirectStandardOutput = true; + startInfo.StandardOutputEncoding = Console.OutputEncoding; } - // We only want to ShellExecute something that is standalone... - if (!soloCommand) + if (redirectError) { - throw InterpreterError.NewInterpreterException(this.Path, typeof(RuntimeException), - this.Command.InvocationExtent, "CantActivateDocumentInPipeline", ParserStrings.CantActivateDocumentInPipeline, this.Path); + startInfo.RedirectStandardError = true; + startInfo.StandardErrorEncoding = Console.OutputEncoding; } - - startInfo.UseShellExecute = true; } // For minishell value of -outoutFormat parameter depends on value of redirectOutput. @@ -1153,22 +1451,71 @@ private ProcessStartInfo GetProcessStartInfo(bool redirectOutput, bool redirectE if (_isMiniShell) { MinishellParameterBinderController mpc = (MinishellParameterBinderController)NativeParameterBinderController; - mpc.BindParameters(arguments, redirectOutput, this.Command.Context.EngineHostInterface.Name); + mpc.BindParameters(arguments, startInfo.RedirectStandardOutput, this.Command.Context.EngineHostInterface.Name); startInfo.CreateNoWindow = mpc.NonInteractive; } - startInfo.Arguments = NativeParameterBinderController.Arguments; - ExecutionContext context = this.Command.Context; + // We provide the user a way to select the new behavior via a new preference variable + using (ParameterBinderBase.bindingTracer.TraceScope("BIND NAMED native application line args [{0}]", this.Path)) + { + // We need to check if we're using legacy argument passing or it's a special case. + if (UseSpecialArgumentPassing(startInfo.FileName)) + { + using (ParameterBinderBase.bindingTracer.TraceScope("BIND argument [{0}]", NativeParameterBinderController.Arguments)) + { + startInfo.Arguments = NativeParameterBinderController.Arguments; + } + } + else + { + // Use new API for running native application + int position = 0; + foreach (string nativeArgument in NativeParameterBinderController.ArgumentList) + { + if (nativeArgument != null) + { + using (ParameterBinderBase.bindingTracer.TraceScope("BIND cmd line arg [{0}] to position [{1}]", nativeArgument, position++)) + { + startInfo.ArgumentList.Add(nativeArgument); + } + } + } + } + } + // Start command in the current filesystem directory string rawPath = context.EngineSessionState.GetNamespaceCurrentLocation( context.ProviderNames.FileSystem).ProviderPath; - startInfo.WorkingDirectory = WildcardPattern.Unescape(rawPath); + + // Only set this if the PowerShell's current working directory still exists. + if (Directory.Exists(rawPath)) + { + startInfo.WorkingDirectory = WildcardPattern.Unescape(rawPath); + } + return startInfo; } + /// + /// 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. + /// + /// The file to use when checking how to pass arguments. + /// A boolean indicating what passing style should be used. + private static bool ShouldUseLegacyPassingStyle(string filePath) + { + if (string.IsNullOrEmpty(filePath)) + { + return false; + } + + return s_legacyFileExtensions.Contains(IO.Path.GetExtension(filePath)) + || s_legacyCommands.Contains(IO.Path.GetFileNameWithoutExtension(filePath)); + } + private static bool IsDownstreamOutDefault(Pipe downstreamPipe) { Diagnostics.Assert(downstreamPipe != null, "Caller makes sure the passed-in parameter is not null."); @@ -1224,15 +1571,19 @@ private void CalculateIORedirection(bool isWindowsApplication, out bool redirect // $powershell.AddScript('ipconfig.exe') // $powershell.AddCommand('Out-Default') // $powershell.Invoke()) - // we should not count it as a redirection. - if (IsDownstreamOutDefault(this.commandRuntime.OutputPipe)) + // we should not count it as a redirection. Unless the native command has its stdout redirected + // for example: + // cmd.exe /c "echo test" > somefile.log + // in that case we want to keep output redirection even though Out-Default is the only + // downstream command. + if (IsDownstreamOutDefault(this.commandRuntime.OutputPipe) && StdOutDestination is null) { redirectOutput = false; } } // See if the error output stream has been redirected, either through an explicit 2> foo.txt or - // my merging error into output through 2>&1. + // by merging error into output through 2>&1. if (CommandRuntime.ErrorMergeTo != MshCommandRuntime.MergeDataStream.Output) { // If the error output pipe is the default outputter, for example, calling the native command from command-line host, @@ -1242,7 +1593,9 @@ private void CalculateIORedirection(bool isWindowsApplication, out bool redirect // $powershell.AddScript('ipconfig.exe') // $powershell.AddCommand('Out-Default') // $powershell.Invoke()) - // we should not count that as a redirection. + // we should not count that as a redirection. We do not need to worry + // about StdOutDestination here as if error is redirected then it's assumed + // to be text based and Out-File will be added to the pipeline instead. if (IsDownstreamOutDefault(this.commandRuntime.ErrorOutputPipe)) { redirectError = false; @@ -1290,6 +1643,9 @@ private void CalculateIORedirection(bool isWindowsApplication, out bool redirect { if (s_supportScreenScrape == null) { +#if UNIX + s_supportScreenScrape = false; +#else try { _startPosition = this.Command.Context.EngineHostInterface.UI.RawUI.CursorPosition; @@ -1301,6 +1657,7 @@ private void CalculateIORedirection(bool isWindowsApplication, out bool redirect { s_supportScreenScrape = false; } +#endif } // if screen scraping isn't supported, we enable redirection so that the output is still transcribed @@ -1336,7 +1693,7 @@ private bool IsExecutable(string path) } else { - extensionList = pathext.Split(Utils.Separators.Semicolon); + extensionList = pathext.Split(';'); } foreach (string extension in extensionList) @@ -1351,88 +1708,6 @@ private bool IsExecutable(string path) #endif } - #region Interop for FindExecutable... - - // Constant used to determine the buffer size for a path - // when looking for an executable. MAX_PATH is defined as 260 - // so this is much larger than what should be permitted - private const int MaxExecutablePath = 1024; - - // The FindExecutable API is defined in shellapi.h as - // SHSTDAPI_(HINSTANCE) FindExecutableW(LPCWSTR lpFile, LPCWSTR lpDirectory, __out_ecount(MAX_PATH) LPWSTR lpResult); - // HINSTANCE is void* so we need to use IntPtr as API return value. - - [DllImport("shell32.dll", EntryPoint = "FindExecutable")] - [SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "0")] - [SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "1")] - [SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "2")] - private static extern IntPtr FindExecutableW( - string fileName, string directoryPath, StringBuilder pathFound); - - [ArchitectureSensitive] - private static string FindExecutable(string filename) - { - // Preallocate a - StringBuilder objResultBuffer = new StringBuilder(MaxExecutablePath); - IntPtr resultCode = (IntPtr)0; - - try - { - resultCode = FindExecutableW(filename, string.Empty, objResultBuffer); - } - catch (System.IndexOutOfRangeException e) - { - // If we got an index-out-of-range exception here, it's because - // of a buffer overrun error so we fail fast instead of - // continuing to run in an possibly unstable environment.... - Environment.FailFast(e.Message, e); - } - - // If FindExecutable returns a result >= 32, then it succeeded - // and we return the string that was found, otherwise we - // return null. - if ((long)resultCode >= 32) - { - return objResultBuffer.ToString(); - } - - return null; - } - - #endregion - - #region Interop for SHGetFileInfo - - private const int SCS_32BIT_BINARY = 0; // A 32-bit Windows-based application - private const int SCS_DOS_BINARY = 1; // An MS-DOS - based application - private const int SCS_WOW_BINARY = 2; // A 16-bit Windows-based application - private const int SCS_PIF_BINARY = 3; // A PIF file that executes an MS-DOS - based application - private const int SCS_POSIX_BINARY = 4; // A POSIX - based application - private const int SCS_OS216_BINARY = 5; // A 16-bit OS/2-based application - private const int SCS_64BIT_BINARY = 6; // A 64-bit Windows-based application. - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - private struct SHFILEINFO - { - public IntPtr hIcon; - public int iIcon; - public uint dwAttributes; - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] - public string szDisplayName; - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)] - public string szTypeName; - } - - private const uint SHGFI_EXETYPE = 0x000002000; // flag used to ask to return exe type - - [DllImport("shell32.dll", CharSet = CharSet.Unicode)] - private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, - ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags); - - #endregion - #region Minishell Interop private bool _isMiniShell = false; @@ -1478,7 +1753,19 @@ internal class ProcessOutputHandler private bool _isXmlCliError; private readonly string _processFileName; + private readonly AsyncByteStreamTransfer _stdOutDrainer; + public ProcessOutputHandler(Process process, BlockingCollection queue) + : this(process, queue, null, null, out _) + { + } + + public ProcessOutputHandler( + Process process, + BlockingCollection queue, + BytePipe stdOutDestination, + BytePipe stdOutSource, + out AsyncByteStreamTransfer stdOutDrainer) { Debug.Assert(process.StartInfo.RedirectStandardOutput || process.StartInfo.RedirectStandardError, "Caller should redirect at least one stream"); _refCount = 0; @@ -1487,19 +1774,17 @@ public ProcessOutputHandler(Process process, BlockingCollection public static bool AlwaysCaptureApplicationIO { get; set; } - [DllImport("Kernel32.dll")] - internal static extern IntPtr GetConsoleWindow(); - - internal const int SW_HIDE = 0; - internal const int SW_SHOWNORMAL = 1; - internal const int SW_NORMAL = 1; - internal const int SW_SHOWMINIMIZED = 2; - internal const int SW_SHOWMAXIMIZED = 3; - internal const int SW_MAXIMIZE = 3; - internal const int SW_SHOWNOACTIVATE = 4; - internal const int SW_SHOW = 5; - internal const int SW_MINIMIZE = 6; - internal const int SW_SHOWMINNOACTIVE = 7; - internal const int SW_SHOWNA = 8; - internal const int SW_RESTORE = 9; - internal const int SW_SHOWDEFAULT = 10; - internal const int SW_FORCEMINIMIZE = 11; - internal const int SW_MAX = 11; - - /// - /// Code to control the display properties of the a window... - /// - /// The window to show... - /// The command to do. - /// True if it was successful. - [DllImport("user32.dll")] - internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); - - /// - /// Code to allocate a console... - /// - /// True if a console was created... - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool AllocConsole(); - - /// - /// Called to save the foreground window before allocating a hidden console window. - /// - /// A handle to the foreground window. - [DllImport("user32.dll")] - private static extern IntPtr GetForegroundWindow(); - - /// - /// Called to restore the foreground window after allocating a hidden console window. - /// - /// A handle to the window that should be activated and brought to the foreground. - /// True if the window was brought to the foreground. - [DllImport("user32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool SetForegroundWindow(IntPtr hWnd); - /// /// If no console window is attached to this process, then allocate one, /// hide it and return true. If there's already a console window attached, then @@ -1983,89 +2246,55 @@ internal static class ConsoleVisibility /// internal static bool AllocateHiddenConsole() { +#if UNIX + return false; +#else // See if there is already a console attached. - IntPtr hwnd = ConsoleVisibility.GetConsoleWindow(); - if (hwnd != IntPtr.Zero) + IntPtr hwnd = Interop.Windows.GetConsoleWindow(); + if (hwnd != nint.Zero) { return false; } // save the foreground window since allocating a console window might remove focus from it - IntPtr savedForeground = ConsoleVisibility.GetForegroundWindow(); + IntPtr savedForeground = Interop.Windows.GetForegroundWindow(); // Since there is no console window, allocate and then hide it... // Suppress the PreFAST warning about not using Marshal.GetLastWin32Error() to // get the error code. -#pragma warning disable 56523 - ConsoleVisibility.AllocConsole(); - hwnd = ConsoleVisibility.GetConsoleWindow(); + Interop.Windows.AllocConsole(); + hwnd = Interop.Windows.GetConsoleWindow(); bool returnValue; - if (hwnd == IntPtr.Zero) + if (hwnd == nint.Zero) { returnValue = false; } else { returnValue = true; - ConsoleVisibility.ShowWindow(hwnd, ConsoleVisibility.SW_HIDE); + Interop.Windows.ShowWindow(hwnd, Interop.Windows.SW_HIDE); AlwaysCaptureApplicationIO = true; } - if (savedForeground != IntPtr.Zero && ConsoleVisibility.GetForegroundWindow() != savedForeground) + if (savedForeground != nint.Zero && Interop.Windows.GetForegroundWindow() != savedForeground) { - ConsoleVisibility.SetForegroundWindow(savedForeground); + Interop.Windows.SetForegroundWindow(savedForeground); } return returnValue; - } - - /// - /// If there is a console attached, then make it visible - /// and allow interactive console applications to be run. - /// - public static void Show() - { - IntPtr hwnd = GetConsoleWindow(); - if (hwnd != IntPtr.Zero) - { - ShowWindow(hwnd, SW_SHOW); - AlwaysCaptureApplicationIO = false; - } - else - { - throw PSTraceSource.NewInvalidOperationException(); - } - } - - /// - /// If there is a console attached, then hide it and always capture - /// output from the child process. - /// - public static void Hide() - { - IntPtr hwnd = GetConsoleWindow(); - if (hwnd != IntPtr.Zero) - { - ShowWindow(hwnd, SW_HIDE); - AlwaysCaptureApplicationIO = true; - } - else - { - throw PSTraceSource.NewInvalidOperationException(); - } +#endif } } /// /// Exception used to wrap the error coming from - /// remote instance of Msh. + /// remote instance of PowerShell. /// /// - /// This remote instance of Msh can be in a separate process, + /// This remote instance of PowerShell can be in a separate process, /// appdomain or machine. /// - [Serializable] [SuppressMessage("Microsoft.Usage", "CA2240:ImplementISerializableCorrectly")] public class RemoteException : RuntimeException { @@ -2142,9 +2371,10 @@ PSObject serializedRemoteInvocationInfo /// The that contains contextual information /// about the source or destination. /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected RemoteException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion @@ -2156,7 +2386,7 @@ protected RemoteException(SerializationInfo info, StreamingContext context) private readonly PSObject _serializedRemoteInvocationInfo; /// - /// Original Serialized Exception from remote msh. + /// Original Serialized Exception from remote PowerShell. /// /// This is the exception which was thrown in remote. /// @@ -2172,7 +2402,7 @@ public PSObject SerializedRemoteException /// InvocationInfo, if any, associated with the SerializedRemoteException. /// /// - /// This is the serialized InvocationInfo from the remote msh. + /// This is the serialized InvocationInfo from the remote PowerShell. /// public PSObject SerializedRemoteInvocationInfo { diff --git a/src/System.Management.Automation/engine/OrderedHashtable.cs b/src/System.Management.Automation/engine/OrderedHashtable.cs new file mode 100644 index 00000000000..fd41d0b3ffd --- /dev/null +++ b/src/System.Management.Automation/engine/OrderedHashtable.cs @@ -0,0 +1,236 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Specialized; +using System.Runtime.Serialization; + +#nullable enable + +namespace System.Management.Automation +{ + /// + /// OrderedHashtable is a hashtable that preserves the order of the keys. + /// + public sealed class OrderedHashtable : Hashtable, IEnumerable + { + private readonly OrderedDictionary _orderedDictionary; + + /// + /// Initializes a new instance of the class. + /// + public OrderedHashtable() + { + _orderedDictionary = new OrderedDictionary(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The capacity. + public OrderedHashtable(int capacity) : base(capacity) + { + _orderedDictionary = new OrderedDictionary(capacity); + } + + /// + /// Initializes a new instance of the class. + /// + /// The dictionary to use for initialization. + public OrderedHashtable(IDictionary dictionary) + { + _orderedDictionary = new OrderedDictionary(dictionary.Count); + foreach (DictionaryEntry entry in dictionary) + { + _orderedDictionary.Add(entry.Key, entry.Value); + } + } + + /// + /// Get the number of items in the hashtable. + /// + public override int Count + { + get + { + return _orderedDictionary.Count; + } + } + + /// + /// Get if the hashtable is a fixed size. + /// + public override bool IsFixedSize + { + get + { + return false; + } + } + + /// + /// Get if the hashtable is read-only. + /// + public override bool IsReadOnly + { + get + { + return false; + } + } + + /// + /// Get if the hashtable is synchronized. + /// + public override bool IsSynchronized + { + get + { + return false; + } + } + + /// + /// Gets the keys in the hashtable. + /// + public override ICollection Keys + { + get + { + return _orderedDictionary.Keys; + } + } + + /// + /// Gets the values in the hashtable. + /// + public override ICollection Values + { + get + { + return _orderedDictionary.Values; + } + } + + /// + /// Gets or sets the value associated with the specified key. + /// + /// The key. + /// The value associated with the key. + public override object? this[object key] + { + get + { + return _orderedDictionary[key]; + } + + set + { + _orderedDictionary[key] = value; + } + } + + /// + /// Adds the specified key and value to the hashtable. + /// + /// The key. + /// The value. + public override void Add(object key, object? value) + { + _orderedDictionary.Add(key, value); + } + + /// + /// Removes all keys and values from the hashtable. + /// + public override void Clear() + { + _orderedDictionary.Clear(); + } + + /// + /// Get a shallow clone of the hashtable. + /// + /// A shallow clone of the hashtable. + public override object Clone() + { + return new OrderedHashtable(_orderedDictionary); + } + + /// + /// Determines whether the hashtable contains a specific key. + /// + /// The key to locate in the hashtable. + /// true if the hashtable contains an element with the specified key; otherwise, false. + public override bool Contains(object key) + { + return _orderedDictionary.Contains(key); + } + + /// + /// Determines whether the hashtable contains a specific key. + /// + /// The key to locate in the hashtable. + /// true if the hashtable contains an element with the specified key; otherwise, false. + public override bool ContainsKey(object key) + { + return _orderedDictionary.Contains(key); + } + + /// + /// Determines whether the hashtable contains a specific value. + /// + /// The value to locate in the hashtable. + /// true if the hashtable contains an element with the specified value; otherwise, false. + public override bool ContainsValue(object? value) + { + foreach (DictionaryEntry entry in _orderedDictionary) + { + if (Equals(entry.Value, value)) + { + return true; + } + } + + return false; + } + + /// + /// Copies the elements of the hashtable to an array of type object, starting at the specified array index. + /// + /// The one-dimensional array that is the destination of the elements copied from the hashtable. The array must have zero-based indexing. + /// The zero-based index in array at which copying begins. + public override void CopyTo(Array array, int arrayIndex) + { + _orderedDictionary.CopyTo(array, arrayIndex); + } + + /// + /// Get the enumerator. + /// + /// The enumerator. + public override IDictionaryEnumerator GetEnumerator() + { + return _orderedDictionary.GetEnumerator(); + } + + /// + /// Get the enumerator. + /// + /// The enumerator. + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Removes the specified key from the hashtable. + /// + /// The key to remove. + public override void Remove(object key) + { + _orderedDictionary.Remove(key); + } + } +} diff --git a/src/System.Management.Automation/engine/PSClassInfo.cs b/src/System.Management.Automation/engine/PSClassInfo.cs index fd0ca8d8936..10b2cf1101d 100644 --- a/src/System.Management.Automation/engine/PSClassInfo.cs +++ b/src/System.Management.Automation/engine/PSClassInfo.cs @@ -61,8 +61,7 @@ public sealed class PSClassMemberInfo /// internal PSClassMemberInfo(string name, string memberType, string defaultValue) { - if (string.IsNullOrEmpty(name)) - throw new ArgumentNullException(nameof(name)); + ArgumentException.ThrowIfNullOrEmpty(name); this.Name = name; this.TypeName = memberType; diff --git a/src/System.Management.Automation/engine/PSConfiguration.cs b/src/System.Management.Automation/engine/PSConfiguration.cs index 16273979a69..e321423f768 100644 --- a/src/System.Management.Automation/engine/PSConfiguration.cs +++ b/src/System.Management.Automation/engine/PSConfiguration.cs @@ -181,6 +181,7 @@ private static string GetExecutionPolicySettingKey(string shellId) : string.Concat(shellId, ":", "ExecutionPolicy"); } + /// /// Get the names of experimental features enabled in the config file. /// internal string[] GetExperimentalFeatures() diff --git a/src/System.Management.Automation/engine/PSVersionInfo.cs b/src/System.Management.Automation/engine/PSVersionInfo.cs index 5830cdc7f02..75f75cc112f 100644 --- a/src/System.Management.Automation/engine/PSVersionInfo.cs +++ b/src/System.Management.Automation/engine/PSVersionInfo.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Globalization; -using System.Reflection; using System.Text; using System.Text.RegularExpressions; @@ -27,7 +26,7 @@ namespace System.Management.Automation /// The above statement retrieves the PowerShell edition. /// /// - public class PSVersionInfo + public static partial class PSVersionInfo { internal const string PSVersionTableName = "PSVersionTable"; internal const string PSRemotingProtocolVersionName = "PSRemotingProtocolVersion"; @@ -42,6 +41,18 @@ public class PSVersionInfo private static readonly PSVersionHashTable s_psVersionTable; + /* + The following constants are generated by the source generator 'PSVersionInfoGenerator': + + internal const string ProductVersion; + internal const string GitCommitId; + + private const int Version_Major + private const int Version_Minor; + private const int Version_Patch; + private const string Version_Label; + */ + /// /// A constant to track current PowerShell Version. /// @@ -53,19 +64,16 @@ public class PSVersionInfo /// For each later release of PowerShell, this constant needs to /// be updated to reflect the right version. /// - private static readonly Version s_psV1Version = new Version(1, 0); - private static readonly Version s_psV2Version = new Version(2, 0); - private static readonly Version s_psV3Version = new Version(3, 0); - private static readonly Version s_psV4Version = new Version(4, 0); - private static readonly Version s_psV5Version = new Version(5, 0); - private static readonly Version s_psV51Version = new Version(5, 1, NTVerpVars.PRODUCTBUILD, NTVerpVars.PRODUCTBUILD_QFE); - private static readonly SemanticVersion s_psV6Version = new SemanticVersion(6, 0, 0, preReleaseLabel: null, buildLabel: null); - private static readonly SemanticVersion s_psV61Version = new SemanticVersion(6, 1, 0, preReleaseLabel: null, buildLabel: null); - private static readonly SemanticVersion s_psV62Version = new SemanticVersion(6, 2, 0, preReleaseLabel: null, buildLabel: null); - private static readonly SemanticVersion s_psV7Version = new SemanticVersion(7, 0, 0, preReleaseLabel: null, buildLabel: null); - private static readonly SemanticVersion s_psV71Version = new SemanticVersion(7, 1, 0, preReleaseLabel: null, buildLabel: null); - private static readonly SemanticVersion s_psSemVersion; + private static readonly Version s_psV1Version = new(1, 0); + private static readonly Version s_psV2Version = new(2, 0); + private static readonly Version s_psV3Version = new(3, 0); + private static readonly Version s_psV4Version = new(4, 0); + private static readonly Version s_psV5Version = new(5, 0); + private static readonly Version s_psV51Version = new(5, 1); + private static readonly Version s_psV6Version = new(6, 0); + private static readonly Version s_psV7Version = new(7, 0); private static readonly Version s_psVersion; + private static readonly SemanticVersion s_psSemVersion; /// /// A constant to track current PowerShell Edition. @@ -77,43 +85,18 @@ static PSVersionInfo() { s_psVersionTable = new PSVersionHashTable(StringComparer.OrdinalIgnoreCase); - Assembly currentAssembly = typeof(PSVersionInfo).Assembly; - string productVersion = currentAssembly.GetCustomAttribute().InformationalVersion; - - // Get 'GitCommitId' and 'PSVersion' from the 'productVersion' assembly attribute. - // - // The strings can be one of the following format examples: - // when powershell is built from a commit: - // productVersion = '6.0.0-beta.7 Commits: 29 SHA: 52c6b...' convert to GitCommitId = 'v6.0.0-beta.7-29-g52c6b...' - // PSVersion = '6.0.0-beta.7' - // when powershell is built from a release tag: - // productVersion = '6.0.0-beta.7 SHA: f1ec9...' convert to GitCommitId = 'v6.0.0-beta.7' - // PSVersion = '6.0.0-beta.7' - // when powershell is built from a release tag for RTM: - // productVersion = '6.0.0 SHA: f1ec9...' convert to GitCommitId = 'v6.0.0' - // PSVersion = '6.0.0' - string rawGitCommitId; - string mainVersion = productVersion.Substring(0, productVersion.IndexOf(' ')); - - if (productVersion.Contains(" Commits: ")) - { - rawGitCommitId = productVersion.Replace(" Commits: ", "-").Replace(" SHA: ", "-g"); - } - else - { - rawGitCommitId = mainVersion; - } - - s_psSemVersion = new SemanticVersion(mainVersion); + s_psSemVersion = Version_Label == string.Empty + ? new SemanticVersion(Version_Major, Version_Minor, Version_Patch) + : new SemanticVersion(Version_Major, Version_Minor, Version_Patch, Version_Label, buildLabel: null); s_psVersion = (Version)s_psSemVersion; - s_psVersionTable[PSVersionInfo.PSVersionName] = s_psSemVersion; - s_psVersionTable[PSVersionInfo.PSEditionName] = PSEditionValue; - s_psVersionTable[PSGitCommitIdName] = rawGitCommitId; - s_psVersionTable[PSCompatibleVersionsName] = new Version[] { s_psV1Version, s_psV2Version, s_psV3Version, s_psV4Version, s_psV5Version, s_psV51Version, s_psV6Version, s_psV61Version, s_psV62Version, s_psV7Version, s_psV71Version, s_psVersion }; - s_psVersionTable[PSVersionInfo.SerializationVersionName] = new Version(InternalSerializer.DefaultVersion); - s_psVersionTable[PSVersionInfo.PSRemotingProtocolVersionName] = RemotingConstants.ProtocolVersion; - s_psVersionTable[PSVersionInfo.WSManStackVersionName] = GetWSManStackVersion(); + s_psVersionTable[PSVersionName] = s_psSemVersion; + s_psVersionTable[PSEditionName] = PSEditionValue; + s_psVersionTable[PSGitCommitIdName] = GitCommitId; + s_psVersionTable[PSCompatibleVersionsName] = new Version[] { s_psV1Version, s_psV2Version, s_psV3Version, s_psV4Version, s_psV5Version, s_psV51Version, s_psV6Version, s_psV7Version }; + s_psVersionTable[SerializationVersionName] = new Version(InternalSerializer.DefaultVersion); + s_psVersionTable[PSRemotingProtocolVersionName] = RemotingConstants.ProtocolVersion; + s_psVersionTable[WSManStackVersionName] = GetWSManStackVersion(); s_psVersionTable[PSPlatformName] = Environment.OSVersion.Platform.ToString(); s_psVersionTable[PSOSName] = Runtime.InteropServices.RuntimeInformation.OSDescription; } @@ -182,22 +165,6 @@ public static Version PSVersion } } - internal static string GitCommitId - { - get - { - return (string)s_psVersionTable[PSGitCommitIdName]; - } - } - - internal static Version[] PSCompatibleVersions - { - get - { - return (Version[])s_psVersionTable[PSCompatibleVersionsName]; - } - } - /// /// Gets the edition of PowerShell. /// @@ -205,7 +172,7 @@ public static string PSEdition { get { - return (string)s_psVersionTable[PSVersionInfo.PSEditionName]; + return PSEditionValue; } } @@ -217,21 +184,6 @@ internal static Version SerializationVersion } } - /// - /// - /// - /// For 2.0 PowerShell, we still use "1" as the registry version key. - /// For >=3.0 PowerShell, we still use "1" as the registry version key for - /// Snapin and Custom shell lookup/discovery. - /// - internal static string RegistryVersion1Key - { - get - { - return "1"; - } - } - /// /// /// @@ -267,76 +219,32 @@ internal static string GetRegistryVersionKeyForSnapinDiscovery(string majorVersi return null; } - internal static string FeatureVersionString - { - get - { - return string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}.{1}", PSVersionInfo.PSVersion.Major, PSVersionInfo.PSVersion.Minor); - } - } - internal static bool IsValidPSVersion(Version version) { - if (version.Major == s_psSemVersion.Major) - { - return version.Minor == s_psSemVersion.Minor; - } - - if (version.Major == s_psV6Version.Major) + if (version is null) { - return version.Minor == s_psV6Version.Minor; - } - - if (version.Major == s_psV5Version.Major) - { - return (version.Minor == s_psV5Version.Minor || version.Minor == s_psV51Version.Minor); + return false; } - if (version.Major == s_psV4Version.Major) - { - return (version.Minor == s_psV4Version.Minor); - } - else if (version.Major == s_psV3Version.Major) - { - return version.Minor == s_psV3Version.Minor; - } - else if (version.Major == s_psV2Version.Major) - { - return version.Minor == s_psV2Version.Minor; - } - else if (version.Major == s_psV1Version.Major) + int minor = version.Minor; + switch (version.Major) { - return version.Minor == s_psV1Version.Minor; + case 1: + case 2: + case 3: + case 4: + return minor == 0; + case 5: + return minor == 0 || minor == 1; + case 6: + return minor >= 0 && minor <= 2; + case 7: + return minor >= 0 && minor <= s_psVersion.Minor; } return false; } - internal static Version PSV4Version - { - get { return s_psV4Version; } - } - - internal static Version PSV5Version - { - get { return s_psV5Version; } - } - - internal static Version PSV51Version - { - get { return s_psV51Version; } - } - - internal static SemanticVersion PSV6Version - { - get { return s_psV6Version; } - } - - internal static SemanticVersion PSV7Version - { - get { return s_psV7Version; } - } - internal static SemanticVersion PSCurrentVersion { get { return s_psSemVersion; } @@ -374,7 +282,7 @@ public override ICollection Keys } } - private class PSVersionTableComparer : IComparer + private sealed class PSVersionTableComparer : IComparer { public int Compare(object x, object y) { @@ -427,8 +335,9 @@ IEnumerator IEnumerable.GetEnumerator() public sealed class SemanticVersion : IComparable, IComparable, IEquatable { private const string VersionSansRegEx = @"^(?\d+)(\.(?\d+))?(\.(?\d+))?$"; - private const string LabelRegEx = @"^((?[0-9A-Za-z][0-9A-Za-z\-\.]*))?(\+(?[0-9A-Za-z][0-9A-Za-z\-\.]*))?$"; - private const string LabelUnitRegEx = @"^[0-9A-Za-z][0-9A-Za-z\-\.]*$"; + private const string LabelRegEx = @"^(?(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(?:\+(?[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"; + private const string LabelUnitRegEx = @"^((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)$"; + private const string BuildUnitRegEx = @"^([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)$"; private const string PreLabelPropertyName = "PSSemVerPreReleaseLabel"; private const string BuildLabelPropertyName = "PSSemVerBuildLabel"; private const string TypeNameForVersionWithLabel = "System.Version#IncludeLabel"; @@ -462,21 +371,27 @@ public SemanticVersion(string version) /// The build metadata for the version. /// /// If don't match 'LabelUnitRegEx'. - /// If don't match 'LabelUnitRegEx'. + /// If don't match 'BuildUnitRegEx'. /// public SemanticVersion(int major, int minor, int patch, string preReleaseLabel, string buildLabel) : this(major, minor, patch) { if (!string.IsNullOrEmpty(preReleaseLabel)) { - if (!Regex.IsMatch(preReleaseLabel, LabelUnitRegEx)) throw new FormatException(nameof(preReleaseLabel)); + if (!Regex.IsMatch(preReleaseLabel, LabelUnitRegEx)) + { + throw new FormatException(nameof(preReleaseLabel)); + } PreReleaseLabel = preReleaseLabel; } if (!string.IsNullOrEmpty(buildLabel)) { - if (!Regex.IsMatch(buildLabel, LabelUnitRegEx)) throw new FormatException(nameof(buildLabel)); + if (!Regex.IsMatch(buildLabel, BuildUnitRegEx)) + { + throw new FormatException(nameof(buildLabel)); + } BuildLabel = buildLabel; } @@ -496,13 +411,16 @@ public SemanticVersion(int major, int minor, int patch, string preReleaseLabel, public SemanticVersion(int major, int minor, int patch, string label) : this(major, minor, patch) { - // We presume the SymVer : + // We presume the SemVer : // 1) major.minor.patch-label // 2) 'label' starts with letter or digit. if (!string.IsNullOrEmpty(label)) { var match = Regex.Match(label, LabelRegEx); - if (!match.Success) throw new FormatException(nameof(label)); + if (!match.Success) + { + throw new FormatException(nameof(label)); + } PreReleaseLabel = match.Groups["preLabel"].Value; BuildLabel = match.Groups["buildLabel"].Value; @@ -520,9 +438,20 @@ public SemanticVersion(int major, int minor, int patch, string label) /// public SemanticVersion(int major, int minor, int patch) { - if (major < 0) throw PSTraceSource.NewArgumentException(nameof(major)); - if (minor < 0) throw PSTraceSource.NewArgumentException(nameof(minor)); - if (patch < 0) throw PSTraceSource.NewArgumentException(nameof(patch)); + if (major < 0) + { + throw PSTraceSource.NewArgumentException(nameof(major)); + } + + if (minor < 0) + { + throw PSTraceSource.NewArgumentException(nameof(minor)); + } + + if (patch < 0) + { + throw PSTraceSource.NewArgumentException(nameof(patch)); + } Major = major; Minor = minor; @@ -564,8 +493,15 @@ public SemanticVersion(int major) : this(major, 0, 0) { } /// public SemanticVersion(Version version) { - if (version == null) throw PSTraceSource.NewArgumentNullException(nameof(version)); - if (version.Revision > 0) throw PSTraceSource.NewArgumentException(nameof(version)); + if (version == null) + { + throw PSTraceSource.NewArgumentNullException(nameof(version)); + } + + if (version.Revision > 0) + { + throw PSTraceSource.NewArgumentException(nameof(version)); + } Major = version.Major; Minor = version.Minor; @@ -633,12 +569,12 @@ public static implicit operator Version(SemanticVersion semver) public int Patch { get; } /// - /// PreReleaseLabel position in the SymVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'. + /// PreReleaseLabel position in the SemVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'. /// public string PreReleaseLabel { get; } /// - /// BuildLabel position in the SymVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'. + /// BuildLabel position in the SemVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'. /// public string BuildLabel { get; } @@ -652,8 +588,15 @@ public static implicit operator Version(SemanticVersion semver) /// public static SemanticVersion Parse(string version) { - if (version == null) throw PSTraceSource.NewArgumentNullException(nameof(version)); - if (version == string.Empty) throw new FormatException(nameof(version)); + if (version == null) + { + throw PSTraceSource.NewArgumentNullException(nameof(version)); + } + + if (version == string.Empty) + { + throw new FormatException(nameof(version)); + } var r = new VersionResult(); r.Init(true); @@ -701,7 +644,7 @@ private static bool TryParseVersion(string version, ref VersionResult result) string preLabel = null; string buildLabel = null; - // We parse the SymVer 'version' string 'major.minor.patch-PreReleaseLabel+BuildLabel'. + // We parse the SemVer 'version' string 'major.minor.patch-PreReleaseLabel+BuildLabel'. var dashIndex = version.IndexOf('-'); var plusIndex = version.IndexOf('+'); @@ -726,7 +669,7 @@ private static bool TryParseVersion(string version, ref VersionResult result) } else { - if (dashIndex == -1) + if (plusIndex == -1) { // Here dashIndex == plusIndex == -1 // No preLabel - preLabel == null; @@ -734,6 +677,13 @@ private static bool TryParseVersion(string version, ref VersionResult result) // Format is 'major.minor.patch' versionSansLabel = version; } + else if (dashIndex == -1) + { + // No PreReleaseLabel: preLabel == null + // Format is 'major.minor.patch+BuildLabel' + buildLabel = version.Substring(plusIndex + 1); + versionSansLabel = version.Substring(0, plusIndex); + } else { // Format is 'major.minor.patch-PreReleaseLabel+BuildLabel' @@ -780,7 +730,7 @@ private static bool TryParseVersion(string version, ref VersionResult result) } if (preLabel != null && !Regex.IsMatch(preLabel, LabelUnitRegEx) || - (buildLabel != null && !Regex.IsMatch(buildLabel, LabelUnitRegEx))) + (buildLabel != null && !Regex.IsMatch(buildLabel, BuildUnitRegEx))) { result.SetFailure(ParseFailureKind.FormatException); return false; @@ -799,7 +749,7 @@ public override string ToString() { StringBuilder result = new StringBuilder(); - result.Append(Major).Append(Utils.Separators.Dot).Append(Minor).Append(Utils.Separators.Dot).Append(Patch); + result.Append(Major).Append('.').Append(Minor).Append('.').Append(Patch); if (!string.IsNullOrEmpty(PreReleaseLabel)) { @@ -855,7 +805,7 @@ public int CompareTo(object version) /// /// Implement . - /// Meets SymVer 2.0 p.11 https://semver.org/ + /// Meets SemVer 2.0 p.11 https://semver.org/ /// public int CompareTo(SemanticVersion value) { @@ -871,7 +821,7 @@ public int CompareTo(SemanticVersion value) if (Patch != value.Patch) return Patch > value.Patch ? 1 : -1; - // SymVer 2.0 standard requires to ignore 'BuildLabel' (Build metadata). + // SemVer 2.0 standard requires to ignore 'BuildLabel' (Build metadata). return ComparePreLabel(this.PreReleaseLabel, value.PreReleaseLabel); } @@ -888,7 +838,7 @@ public override bool Equals(object obj) /// public bool Equals(SemanticVersion other) { - // SymVer 2.0 standard requires to ignore 'BuildLabel' (Build metadata). + // SemVer 2.0 standard requires to ignore 'BuildLabel' (Build metadata). return other != null && (Major == other.Major) && (Minor == other.Minor) && (Patch == other.Patch) && string.Equals(PreReleaseLabel, other.PreReleaseLabel, StringComparison.Ordinal); @@ -957,7 +907,7 @@ public override int GetHashCode() private static int ComparePreLabel(string preLabel1, string preLabel2) { - // Symver 2.0 standard p.9 + // SemVer 2.0 standard p.9 // Pre-release versions have a lower precedence than the associated normal version. // Comparing each dot separated identifier from left to right // until a difference is found as follows: @@ -966,9 +916,15 @@ private static int ComparePreLabel(string preLabel1, string preLabel2) // Numeric identifiers always have lower precedence than non-numeric identifiers. // A larger set of pre-release fields has a higher precedence than a smaller set, // if all of the preceding identifiers are equal. - if (string.IsNullOrEmpty(preLabel1)) { return string.IsNullOrEmpty(preLabel2) ? 0 : 1; } + if (string.IsNullOrEmpty(preLabel1)) + { + return string.IsNullOrEmpty(preLabel2) ? 0 : 1; + } - if (string.IsNullOrEmpty(preLabel2)) { return -1; } + if (string.IsNullOrEmpty(preLabel2)) + { + return -1; + } var units1 = preLabel1.Split('.'); var units2 = preLabel2.Split('.'); @@ -985,16 +941,28 @@ private static int ComparePreLabel(string preLabel1, string preLabel2) if (isNumber1 && isNumber2) { - if (number1 != number2) { return number1 < number2 ? -1 : 1; } + if (number1 != number2) + { + return number1 < number2 ? -1 : 1; + } } else { - if (isNumber1) { return -1; } + if (isNumber1) + { + return -1; + } - if (isNumber2) { return 1; } + if (isNumber2) + { + return 1; + } int result = string.CompareOrdinal(ac, bc); - if (result != 0) { return result; } + if (result != 0) + { + return result; + } } } diff --git a/src/System.Management.Automation/engine/ParameterBinderBase.cs b/src/System.Management.Automation/engine/ParameterBinderBase.cs index ee7028c3865..21868b1a28d 100644 --- a/src/System.Management.Automation/engine/ParameterBinderBase.cs +++ b/src/System.Management.Automation/engine/ParameterBinderBase.cs @@ -559,15 +559,13 @@ internal virtual bool BindParameter( parameterMetadata.ObsoleteAttribute.Message); var mshCommandRuntime = this.Command.commandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - // Write out warning only if we are in the context of MshCommandRuntime. - // This is because - // 1. The overload method WriteWarning(WarningRecord) is only available in MshCommandRuntime; - // 2. We write out warnings for obsolete commands and obsolete cmdlet parameters only when in - // the context of MshCommandRuntime. So we do it here to keep consistency. - mshCommandRuntime.WriteWarning(new WarningRecord(FQIDParameterObsolete, obsoleteWarning)); - } + + // Write out warning only if we are in the context of MshCommandRuntime. + // This is because + // 1. The overload method WriteWarning(WarningRecord) is only available in MshCommandRuntime; + // 2. We write out warnings for obsolete commands and obsolete cmdlet parameters only when in + // the context of MshCommandRuntime. So we do it here to keep consistency. + mshCommandRuntime?.WriteWarning(new WarningRecord(FQIDParameterObsolete, obsoleteWarning)); } // Finally bind the argument to the parameter @@ -772,7 +770,10 @@ private void ValidateNullOrEmptyArgument( // Note - we explicitly don't pass the context here because we don't want // the overhead of the calls that check for stopping. - if (ParserOps.MoveNext(null, null, ienum)) { isEmpty = false; } + if (ParserOps.MoveNext(null, null, ienum)) + { + isEmpty = false; + } // If the element of the collection is of value type, then no need to check for null // because a value-type value cannot be null. @@ -999,10 +1000,7 @@ private object CoerceTypeAsNeeded( // Construct the collection type information if it wasn't passed in. - if (collectionTypeInfo == null) - { - collectionTypeInfo = new ParameterCollectionTypeInformation(toType); - } + collectionTypeInfo ??= new ParameterCollectionTypeInformation(toType); object originalValue = currentValue; object result = currentValue; @@ -1246,8 +1244,9 @@ private object CoerceTypeAsNeeded( // However, we don't allow Hashtable-to-Object conversion (PSObject and IDictionary) because // those can lead to property setters that probably aren't expected. This is enforced by // setting 'Context.LanguageModeTransitionInParameterBinding' to true before the conversion. + var currentLanguageMode = Context.LanguageMode; bool changeLanguageModeForTrustedCommand = - Context.LanguageMode == PSLanguageMode.ConstrainedLanguage && + currentLanguageMode == PSLanguageMode.ConstrainedLanguage && this.Command.CommandInfo.DefiningLanguageMode == PSLanguageMode.FullLanguage; bool oldLangModeTransitionStatus = Context.LanguageModeTransitionInParameterBinding; @@ -1265,7 +1264,7 @@ private object CoerceTypeAsNeeded( { if (changeLanguageModeForTrustedCommand) { - Context.LanguageMode = PSLanguageMode.ConstrainedLanguage; + Context.LanguageMode = currentLanguageMode; Context.LanguageModeTransitionInParameterBinding = oldLangModeTransitionStatus; } } @@ -1452,7 +1451,7 @@ private object HandleNullParameterForSpecialTypes( /// could not be created. /// [SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode")] - [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Consider Simplyfing it")] + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Consider Simplifying it")] private object EncodeCollection( CommandParameterInternal argument, string parameterName, diff --git a/src/System.Management.Automation/engine/ParameterBinderController.cs b/src/System.Management.Automation/engine/ParameterBinderController.cs index e6a7153a879..f9781a99435 100644 --- a/src/System.Management.Automation/engine/ParameterBinderController.cs +++ b/src/System.Management.Automation/engine/ParameterBinderController.cs @@ -616,7 +616,7 @@ protected Collection BindNamedParameters(uint paramete { // This named parameter from splatting is also explicitly specified by the user, // which was successfully bound, so we ignore the one from splatting because it - // is superceded by the explicit one. For example: + // is superseded by the explicit one. For example: // $splat = @{ Path = $path1 } // dir @splat -Path $path2 continue; @@ -1029,7 +1029,7 @@ protected void ThrowElaboratedBindingException(ParameterBindingException pbex) StringBuilder defaultParamsGetBound = new StringBuilder(); foreach (string paramName in BoundDefaultParameters) { - defaultParamsGetBound.AppendFormat(CultureInfo.InvariantCulture, " -{0}", paramName); + defaultParamsGetBound.Append(CultureInfo.InvariantCulture, $" -{paramName}"); } string resourceString = ParameterBinderStrings.DefaultBindingErrorElaborationSingle; diff --git a/src/System.Management.Automation/engine/ParameterSetInfo.cs b/src/System.Management.Automation/engine/ParameterSetInfo.cs index 74745fca6da..5d81b8553cf 100644 --- a/src/System.Management.Automation/engine/ParameterSetInfo.cs +++ b/src/System.Management.Automation/engine/ParameterSetInfo.cs @@ -246,15 +246,19 @@ private static void AppendFormatCommandParameterInfo(CommandParameterInfo parame if (parameter.IsMandatory) { - result.AppendFormat(CultureInfo.InvariantCulture, - parameter.Position != int.MinValue ? "[-{0}] <{1}>" : "-{0} <{1}>", - parameter.Name, parameterTypeString); + result.AppendFormat( + CultureInfo.InvariantCulture, + parameter.Position != int.MinValue ? "[-{0}] <{1}>" : "-{0} <{1}>", + parameter.Name, + parameterTypeString); } else { - result.AppendFormat(CultureInfo.InvariantCulture, - parameter.Position != int.MinValue ? "[[-{0}] <{1}>]" : "[-{0} <{1}>]", - parameter.Name, parameterTypeString); + result.AppendFormat( + CultureInfo.InvariantCulture, + parameter.Position != int.MinValue ? "[[-{0}] <{1}>]" : "[-{0} <{1}>]", + parameter.Name, + parameterTypeString); } } } @@ -284,7 +288,7 @@ internal static string GetParameterTypeString(Type type, IEnumerable parameterTypeString = typeName.PSTypeName; // Drop the namespace from the typename, if any. - var lastDotIndex = parameterTypeString.LastIndexOfAny(Utils.Separators.Dot); + var lastDotIndex = parameterTypeString.LastIndexOf('.'); if (lastDotIndex != -1 && lastDotIndex + 1 < parameterTypeString.Length) { parameterTypeString = parameterTypeString.Substring(lastDotIndex + 1); diff --git a/src/System.Management.Automation/engine/PathInterfaces.cs b/src/System.Management.Automation/engine/PathInterfaces.cs index 9a604829295..1cc9c6c173c 100644 --- a/src/System.Management.Automation/engine/PathInterfaces.cs +++ b/src/System.Management.Automation/engine/PathInterfaces.cs @@ -379,7 +379,7 @@ public PathInfoStack SetDefaultLocationStack(string stackName) /// characters which will get resolved. /// /// - /// An array of Msh paths that resolved from the given path. + /// An array of PowerShell paths that resolved from the given path. /// /// /// If is null. @@ -728,7 +728,7 @@ public string GetUnresolvedProviderPathFromPSPath(string path) /// The information for the provider for which the returned path should be used. /// /// - /// The drive of the Msh path that was used to convert the path. Note, this may be null + /// The drive of the PowerShell path that was used to convert the path. Note, this may be null /// if the was a provider-qualified path. /// /// @@ -834,7 +834,7 @@ internal string GetUnresolvedProviderPathFromPSPath( } /// - /// Determines if the give path is an Msh provider-qualified path. + /// Determines if the give path is a PowerShell provider-qualified path. /// /// /// The path to check. @@ -863,11 +863,11 @@ public bool IsProviderQualified(string path) /// The path to check. /// /// - /// If the path is an Msh absolute path then the returned value is + /// If the path is an absolute path then the returned value is /// the name of the drive that the path is absolute to. /// /// - /// True if the specified path is an Msh absolute drive-qualified path. + /// True if the specified path is an absolute drive-qualified path. /// False otherwise. /// /// @@ -1232,7 +1232,7 @@ internal string ParseChildName( /// as a relative path to the basePath that was passed. /// /// - /// An MSH path to an item. The item should exist + /// A PowerShell path to an item. The item should exist /// or the provider should write out an error. /// /// @@ -1312,7 +1312,7 @@ internal string NormalizeRelativePath( #region IsValid /// - /// Determines if the MSH path is a syntactically and semantically valid path for the provider. + /// Determines if the path is a syntactically and semantically valid path for the provider. /// /// /// The path to validate. diff --git a/src/System.Management.Automation/engine/Pipe.cs b/src/System.Management.Automation/engine/Pipe.cs index 8ee000a4faf..d43a0f96a5d 100644 --- a/src/System.Management.Automation/engine/Pipe.cs +++ b/src/System.Management.Automation/engine/Pipe.cs @@ -109,6 +109,13 @@ public override string ToString() /// internal int OutBufferCount { get; set; } = 0; + /// + /// Gets whether the out variable list should be ignored. + /// This is used for scenarios like the `clean` block, where writing to output stream is intentionally + /// disabled and thus out variables should also be ignored. + /// + internal bool IgnoreOutVariableList { get; set; } + /// /// If true, then all input added to this pipe will simply be discarded... /// @@ -226,34 +233,22 @@ internal void AddVariableList(VariableStreamKind kind, IList list) switch (kind) { case VariableStreamKind.Error: - if (_errorVariableList == null) - { - _errorVariableList = new List(); - } + _errorVariableList ??= new List(); _errorVariableList.Add(list); break; case VariableStreamKind.Warning: - if (_warningVariableList == null) - { - _warningVariableList = new List(); - } + _warningVariableList ??= new List(); _warningVariableList.Add(list); break; case VariableStreamKind.Output: - if (_outVariableList == null) - { - _outVariableList = new List(); - } + _outVariableList ??= new List(); _outVariableList.Add(list); break; case VariableStreamKind.Information: - if (_informationVariableList == null) - { - _informationVariableList = new List(); - } + _informationVariableList ??= new List(); _informationVariableList.Add(list); break; @@ -552,15 +547,28 @@ internal object Retrieve() else if (_enumeratorToProcess != null) { if (_enumeratorToProcessIsEmpty) - return AutomationNull.Value; - - if (!ParserOps.MoveNext(_context, null, _enumeratorToProcess)) { - _enumeratorToProcessIsEmpty = true; return AutomationNull.Value; } - return ParserOps.Current(null, _enumeratorToProcess); + while (true) + { + if (!ParserOps.MoveNext(_context, errorPosition: null, _enumeratorToProcess)) + { + _enumeratorToProcessIsEmpty = true; + return AutomationNull.Value; + } + + object retValue = ParserOps.Current(errorPosition: null, _enumeratorToProcess); + if (retValue == AutomationNull.Value) + { + // 'AutomationNull.Value' from the enumerator won't be sent to the pipeline. + // We try to get the next value in this case. + continue; + } + + return retValue; + } } else if (ExternalReader != null) { @@ -595,11 +603,7 @@ internal object Retrieve() /// /// Removes all the objects from the Pipe. /// - internal void Clear() - { - if (ObjectQueue != null) - ObjectQueue.Clear(); - } + internal void Clear() => ObjectQueue?.Clear(); /// /// Returns the currently queued items in the pipe. Note that this will diff --git a/src/System.Management.Automation/engine/ProcessCodeMethods.cs b/src/System.Management.Automation/engine/ProcessCodeMethods.cs index 604ac3787ad..68d47fbec71 100644 --- a/src/System.Management.Automation/engine/ProcessCodeMethods.cs +++ b/src/System.Management.Automation/engine/ProcessCodeMethods.cs @@ -61,32 +61,12 @@ internal static int GetParentPid(Process process) internal static int GetParentPid(Process process) { Diagnostics.Assert(process != null, "Ensure process is not null before calling"); - PROCESS_BASIC_INFORMATION pbi; + Interop.Windows.PROCESS_BASIC_INFORMATION pbi; int size; - var res = NtQueryInformationProcess(process.Handle, 0, out pbi, Marshal.SizeOf(), out size); + var res = Interop.Windows.NtQueryInformationProcess(process.Handle, 0, out pbi, Marshal.SizeOf(), out size); return res != 0 ? InvalidProcessId : pbi.InheritedFromUniqueProcessId.ToInt32(); } - - [StructLayout(LayoutKind.Sequential)] - private struct PROCESS_BASIC_INFORMATION - { - public IntPtr ExitStatus; - public IntPtr PebBaseAddress; - public IntPtr AffinityMask; - public IntPtr BasePriority; - public IntPtr UniqueProcessId; - public IntPtr InheritedFromUniqueProcessId; - } - - [DllImport("ntdll.dll", SetLastError = true)] - private static extern int NtQueryInformationProcess( - IntPtr processHandle, - int processInformationClass, - out PROCESS_BASIC_INFORMATION processInformation, - int processInformationLength, - out int returnLength); #endif - } } diff --git a/src/System.Management.Automation/engine/ProgressRecord.cs b/src/System.Management.Automation/engine/ProgressRecord.cs index b721b4003d9..56d5518dcf8 100644 --- a/src/System.Management.Automation/engine/ProgressRecord.cs +++ b/src/System.Management.Automation/engine/ProgressRecord.cs @@ -15,7 +15,7 @@ namespace System.Management.Automation /// which, according to user preference, forwards that information on to the host for rendering to the user. /// /// - [DataContract()] + [DataContract] public class ProgressRecord { @@ -59,6 +59,25 @@ class ProgressRecord this.status = statusDescription; } + /// + /// Initializes a new instance of the ProgressRecord class and defines the activity Id. + /// + /// + /// A unique numeric key that identifies the activity to which this record applies. + /// + public + ProgressRecord(int activityId) + { + if (activityId < 0) + { + // negative Ids are reserved to indicate "no id" for parent Ids. + + throw PSTraceSource.NewArgumentOutOfRangeException(nameof(activityId), activityId, ProgressRecordStrings.ArgMayNotBeNegative, "activityId"); + } + + this.id = activityId; + } + /// /// Cloning constructor (all fields are value types - can treat our implementation of cloning as "deep" copy) /// @@ -230,7 +249,7 @@ internal ProgressRecord(ProgressRecord other) /// /// Normally displayed beside the progress bar, as "N seconds remaining." /// - /// + /// /// A value less than 0 means "don't display a time remaining." /// public @@ -360,15 +379,8 @@ internal static int GetPercentageComplete(DateTime startTime, TimeSpan expectedD startTime.Kind == DateTimeKind.Utc, "DateTime arithmetic should always be done in utc mode [to avoid problems when some operands are calculated right before and right after switching to /from a daylight saving time"); - if (startTime > now) - { - throw new ArgumentOutOfRangeException(nameof(startTime)); - } - - if (expectedDuration <= TimeSpan.Zero) - { - throw new ArgumentOutOfRangeException(nameof(expectedDuration)); - } + ArgumentOutOfRangeException.ThrowIfGreaterThan(startTime, now); + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(expectedDuration, TimeSpan.Zero); /* * According to the spec of Checkpoint-Computer @@ -417,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 @@ -488,9 +500,13 @@ internal static ProgressRecord FromPSObjectForRemoting(PSObject progressAsPSObje /// This object as a PSObject property bag. internal PSObject ToPSObjectForRemoting() { + // Activity used to be mandatory but that's no longer the case. + // We ensure the string has a value to maintain compatibility with older versions. + string activity = string.IsNullOrEmpty(Activity) ? " " : Activity; + PSObject progressAsPSObject = RemotingEncoder.CreateEmptyPSObject(); - progressAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.ProgressRecord_Activity, this.Activity)); + progressAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.ProgressRecord_Activity, activity)); progressAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.ProgressRecord_ActivityId, this.ActivityId)); progressAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.ProgressRecord_StatusDescription, this.StatusDescription)); @@ -512,10 +528,11 @@ internal PSObject ToPSObjectForRemoting() public enum ProgressRecordType { - /// + /// + /// /// Operation just started or is not yet complete. - /// - /// + /// + /// /// A cmdlet can call WriteProgress with ProgressRecordType.Processing /// as many times as it wishes. However, at the end of the operation, /// it should call once more with ProgressRecordType.Completed. @@ -526,17 +543,20 @@ enum ProgressRecordType /// of the same Id, the host will update that display. /// Finally, when the host receives a 'completed' record /// for that activity, it will remove the progress indicator. - /// + /// + /// Processing, /// + /// /// Operation is complete. - /// - /// + /// + /// /// If a cmdlet uses WriteProgress, it should use /// ProgressRecordType.Completed exactly once, in the last call /// to WriteProgress. - /// + /// + /// Completed } } diff --git a/src/System.Management.Automation/engine/ProxyCommand.cs b/src/System.Management.Automation/engine/ProxyCommand.cs index 5703adec664..80d70377e87 100644 --- a/src/System.Management.Automation/engine/ProxyCommand.cs +++ b/src/System.Management.Automation/engine/ProxyCommand.cs @@ -247,6 +247,30 @@ public static string GetEnd(CommandMetadata commandMetadata) return commandMetadata.GetEndBlock(); } + /// + /// This method constructs a string representing the clean block of the command + /// specified by . The returned string only contains the + /// script, it is not enclosed in "clean { }". + /// + /// + /// An instance of CommandMetadata representing a command. + /// + /// + /// A string representing the end block of the command. + /// + /// + /// If is null. + /// + public static string GetClean(CommandMetadata commandMetadata) + { + if (commandMetadata == null) + { + throw PSTraceSource.NewArgumentNullException(nameof(commandMetadata)); + } + + return commandMetadata.GetCleanBlock(); + } + private static T GetProperty(PSObject obj, string property) where T : class { T result = null; @@ -352,10 +376,7 @@ private static void AppendType(StringBuilder sb, string section, PSObject parent /// When the help argument is not recognized as a HelpInfo object. public static string GetHelpComments(PSObject help) { - if (help == null) - { - throw new ArgumentNullException(nameof(help)); - } + ArgumentNullException.ThrowIfNull(help); bool isHelpObject = false; foreach (string typeName in help.InternalTypeNames) diff --git a/src/System.Management.Automation/engine/PseudoParameters.cs b/src/System.Management.Automation/engine/PseudoParameters.cs index 97b1d60e8ad..a3703a63d90 100644 --- a/src/System.Management.Automation/engine/PseudoParameters.cs +++ b/src/System.Management.Automation/engine/PseudoParameters.cs @@ -174,14 +174,20 @@ internal bool IsDisabled() { if (!hasSeenExpAttribute && attr is ExperimentalAttribute expAttribute) { - if (expAttribute.ToHide) { return true; } + if (expAttribute.ToHide) + { + return true; + } hasSeenExpAttribute = true; } else if (attr is ParameterAttribute paramAttribute) { hasParameterAttribute = true; - if (paramAttribute.ToHide) { continue; } + if (paramAttribute.ToHide) + { + continue; + } hasEnabledParamAttribute = true; } @@ -208,7 +214,6 @@ internal bool IsDisabled() /// /// /// - [Serializable] public class RuntimeDefinedParameterDictionary : Dictionary { /// diff --git a/src/System.Management.Automation/engine/ReflectionParameterBinder.cs b/src/System.Management.Automation/engine/ReflectionParameterBinder.cs index 6488dbcee90..5ee84f4c42f 100644 --- a/src/System.Management.Automation/engine/ReflectionParameterBinder.cs +++ b/src/System.Management.Automation/engine/ReflectionParameterBinder.cs @@ -155,39 +155,39 @@ internal override void BindParameter(string name, object value, CompiledCommandP static ReflectionParameterBinder() { // Statically add delegates that we typically need on startup or every time we run PowerShell - this avoids the JIT - s_getterMethods.TryAdd(Tuple.Create(typeof(OutDefaultCommand), "InputObject"), o => ((OutDefaultCommand)o).InputObject); - s_setterMethods.TryAdd(Tuple.Create(typeof(OutDefaultCommand), "InputObject"), (o, v) => ((OutDefaultCommand)o).InputObject = (PSObject)v); + s_getterMethods.TryAdd(Tuple.Create(typeof(OutDefaultCommand), "InputObject"), static o => ((OutDefaultCommand)o).InputObject); + s_setterMethods.TryAdd(Tuple.Create(typeof(OutDefaultCommand), "InputObject"), static (o, v) => ((OutDefaultCommand)o).InputObject = (PSObject)v); - s_getterMethods.TryAdd(Tuple.Create(typeof(OutLineOutputCommand), "InputObject"), o => ((OutLineOutputCommand)o).InputObject); - s_getterMethods.TryAdd(Tuple.Create(typeof(OutLineOutputCommand), "LineOutput"), o => ((OutLineOutputCommand)o).LineOutput); - s_setterMethods.TryAdd(Tuple.Create(typeof(OutLineOutputCommand), "InputObject"), (o, v) => ((OutLineOutputCommand)o).InputObject = (PSObject)v); - s_setterMethods.TryAdd(Tuple.Create(typeof(OutLineOutputCommand), "LineOutput"), (o, v) => ((OutLineOutputCommand)o).LineOutput = v); + s_getterMethods.TryAdd(Tuple.Create(typeof(OutLineOutputCommand), "InputObject"), static o => ((OutLineOutputCommand)o).InputObject); + s_getterMethods.TryAdd(Tuple.Create(typeof(OutLineOutputCommand), "LineOutput"), static o => ((OutLineOutputCommand)o).LineOutput); + s_setterMethods.TryAdd(Tuple.Create(typeof(OutLineOutputCommand), "InputObject"), static (o, v) => ((OutLineOutputCommand)o).InputObject = (PSObject)v); + s_setterMethods.TryAdd(Tuple.Create(typeof(OutLineOutputCommand), "LineOutput"), static (o, v) => ((OutLineOutputCommand)o).LineOutput = v); - s_getterMethods.TryAdd(Tuple.Create(typeof(FormatDefaultCommand), "InputObject"), o => ((FormatDefaultCommand)o).InputObject); - s_setterMethods.TryAdd(Tuple.Create(typeof(FormatDefaultCommand), "InputObject"), (o, v) => ((FormatDefaultCommand)o).InputObject = (PSObject)v); + s_getterMethods.TryAdd(Tuple.Create(typeof(FormatDefaultCommand), "InputObject"), static o => ((FormatDefaultCommand)o).InputObject); + s_setterMethods.TryAdd(Tuple.Create(typeof(FormatDefaultCommand), "InputObject"), static (o, v) => ((FormatDefaultCommand)o).InputObject = (PSObject)v); - s_setterMethods.TryAdd(Tuple.Create(typeof(SetStrictModeCommand), "Off"), (o, v) => ((SetStrictModeCommand)o).Off = (SwitchParameter)v); - s_setterMethods.TryAdd(Tuple.Create(typeof(SetStrictModeCommand), "Version"), (o, v) => ((SetStrictModeCommand)o).Version = (Version)v); + s_setterMethods.TryAdd(Tuple.Create(typeof(SetStrictModeCommand), "Off"), static (o, v) => ((SetStrictModeCommand)o).Off = (SwitchParameter)v); + s_setterMethods.TryAdd(Tuple.Create(typeof(SetStrictModeCommand), "Version"), static (o, v) => ((SetStrictModeCommand)o).Version = (Version)v); - s_getterMethods.TryAdd(Tuple.Create(typeof(ForEachObjectCommand), "InputObject"), o => ((ForEachObjectCommand)o).InputObject); - s_setterMethods.TryAdd(Tuple.Create(typeof(ForEachObjectCommand), "InputObject"), (o, v) => ((ForEachObjectCommand)o).InputObject = (PSObject)v); - s_setterMethods.TryAdd(Tuple.Create(typeof(ForEachObjectCommand), "Process"), (o, v) => ((ForEachObjectCommand)o).Process = (ScriptBlock[])v); + s_getterMethods.TryAdd(Tuple.Create(typeof(ForEachObjectCommand), "InputObject"), static o => ((ForEachObjectCommand)o).InputObject); + s_setterMethods.TryAdd(Tuple.Create(typeof(ForEachObjectCommand), "InputObject"), static (o, v) => ((ForEachObjectCommand)o).InputObject = (PSObject)v); + s_setterMethods.TryAdd(Tuple.Create(typeof(ForEachObjectCommand), "Process"), static (o, v) => ((ForEachObjectCommand)o).Process = (ScriptBlock[])v); - s_getterMethods.TryAdd(Tuple.Create(typeof(WhereObjectCommand), "InputObject"), o => ((WhereObjectCommand)o).InputObject); - s_setterMethods.TryAdd(Tuple.Create(typeof(WhereObjectCommand), "InputObject"), (o, v) => ((WhereObjectCommand)o).InputObject = (PSObject)v); - s_setterMethods.TryAdd(Tuple.Create(typeof(WhereObjectCommand), "FilterScript"), (o, v) => ((WhereObjectCommand)o).FilterScript = (ScriptBlock)v); + s_getterMethods.TryAdd(Tuple.Create(typeof(WhereObjectCommand), "InputObject"), static o => ((WhereObjectCommand)o).InputObject); + s_setterMethods.TryAdd(Tuple.Create(typeof(WhereObjectCommand), "InputObject"), static (o, v) => ((WhereObjectCommand)o).InputObject = (PSObject)v); + s_setterMethods.TryAdd(Tuple.Create(typeof(WhereObjectCommand), "FilterScript"), static (o, v) => ((WhereObjectCommand)o).FilterScript = (ScriptBlock)v); - s_setterMethods.TryAdd(Tuple.Create(typeof(ImportModuleCommand), "Name"), (o, v) => ((ImportModuleCommand)o).Name = (string[])v); - s_setterMethods.TryAdd(Tuple.Create(typeof(ImportModuleCommand), "ModuleInfo"), (o, v) => ((ImportModuleCommand)o).ModuleInfo = (PSModuleInfo[])v); - s_setterMethods.TryAdd(Tuple.Create(typeof(ImportModuleCommand), "Scope"), (o, v) => ((ImportModuleCommand)o).Scope = (string)v); - s_setterMethods.TryAdd(Tuple.Create(typeof(ImportModuleCommand), "PassThru"), (o, v) => ((ImportModuleCommand)o).PassThru = (SwitchParameter)v); + s_setterMethods.TryAdd(Tuple.Create(typeof(ImportModuleCommand), "Name"), static (o, v) => ((ImportModuleCommand)o).Name = (string[])v); + s_setterMethods.TryAdd(Tuple.Create(typeof(ImportModuleCommand), "ModuleInfo"), static (o, v) => ((ImportModuleCommand)o).ModuleInfo = (PSModuleInfo[])v); + s_setterMethods.TryAdd(Tuple.Create(typeof(ImportModuleCommand), "Scope"), static (o, v) => ((ImportModuleCommand)o).Scope = (string)v); + s_setterMethods.TryAdd(Tuple.Create(typeof(ImportModuleCommand), "PassThru"), static (o, v) => ((ImportModuleCommand)o).PassThru = (SwitchParameter)v); - s_setterMethods.TryAdd(Tuple.Create(typeof(GetCommandCommand), "Name"), (o, v) => ((GetCommandCommand)o).Name = (string[])v); - s_setterMethods.TryAdd(Tuple.Create(typeof(GetCommandCommand), "Module"), (o, v) => ((GetCommandCommand)o).Module = (string[])v); + s_setterMethods.TryAdd(Tuple.Create(typeof(GetCommandCommand), "Name"), static (o, v) => ((GetCommandCommand)o).Name = (string[])v); + s_setterMethods.TryAdd(Tuple.Create(typeof(GetCommandCommand), "Module"), static (o, v) => ((GetCommandCommand)o).Module = (string[])v); - s_setterMethods.TryAdd(Tuple.Create(typeof(GetModuleCommand), "Name"), (o, v) => ((GetModuleCommand)o).Name = (string[])v); - s_setterMethods.TryAdd(Tuple.Create(typeof(GetModuleCommand), "ListAvailable"), (o, v) => ((GetModuleCommand)o).ListAvailable = (SwitchParameter)v); - s_setterMethods.TryAdd(Tuple.Create(typeof(GetModuleCommand), "FullyQualifiedName"), (o, v) => ((GetModuleCommand)o).FullyQualifiedName = (ModuleSpecification[])v); + s_setterMethods.TryAdd(Tuple.Create(typeof(GetModuleCommand), "Name"), static (o, v) => ((GetModuleCommand)o).Name = (string[])v); + s_setterMethods.TryAdd(Tuple.Create(typeof(GetModuleCommand), "ListAvailable"), static (o, v) => ((GetModuleCommand)o).ListAvailable = (SwitchParameter)v); + s_setterMethods.TryAdd(Tuple.Create(typeof(GetModuleCommand), "FullyQualifiedName"), static (o, v) => ((GetModuleCommand)o).FullyQualifiedName = (ModuleSpecification[])v); s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "ErrorAction"), (o, v) => @@ -207,14 +207,20 @@ static ReflectionParameterBinder() v ??= LanguagePrimitives.ThrowInvalidCastException(null, typeof(ActionPreference)); ((CommonParameters)o).InformationAction = (ActionPreference)v; }); - s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "Verbose"), (o, v) => ((CommonParameters)o).Verbose = (SwitchParameter)v); - s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "Debug"), (o, v) => ((CommonParameters)o).Debug = (SwitchParameter)v); - s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "ErrorVariable"), (o, v) => ((CommonParameters)o).ErrorVariable = (string)v); - s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "WarningVariable"), (o, v) => ((CommonParameters)o).WarningVariable = (string)v); - s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "InformationVariable"), (o, v) => ((CommonParameters)o).InformationVariable = (string)v); - s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "OutVariable"), (o, v) => ((CommonParameters)o).OutVariable = (string)v); - s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "OutBuffer"), (o, v) => ((CommonParameters)o).OutBuffer = (int)v); - s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "PipelineVariable"), (o, v) => ((CommonParameters)o).PipelineVariable = (string)v); + s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "ProgressAction"), + (o, v) => + { + v ??= LanguagePrimitives.ThrowInvalidCastException(null, typeof(ActionPreference)); + ((CommonParameters)o).ProgressAction = (ActionPreference)v; + }); + s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "Verbose"), static (o, v) => ((CommonParameters)o).Verbose = (SwitchParameter)v); + s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "Debug"), static (o, v) => ((CommonParameters)o).Debug = (SwitchParameter)v); + s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "ErrorVariable"), static (o, v) => ((CommonParameters)o).ErrorVariable = (string)v); + s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "WarningVariable"), static (o, v) => ((CommonParameters)o).WarningVariable = (string)v); + s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "InformationVariable"), static (o, v) => ((CommonParameters)o).InformationVariable = (string)v); + s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "OutVariable"), static (o, v) => ((CommonParameters)o).OutVariable = (string)v); + s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "OutBuffer"), static (o, v) => ((CommonParameters)o).OutBuffer = (int)v); + s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "PipelineVariable"), static (o, v) => ((CommonParameters)o).PipelineVariable = (string)v); } private static readonly ConcurrentDictionary, Func> s_getterMethods diff --git a/src/System.Management.Automation/engine/ScriptCommandProcessor.cs b/src/System.Management.Automation/engine/ScriptCommandProcessor.cs index 99b685e07d2..32bec9fd5a9 100644 --- a/src/System.Management.Automation/engine/ScriptCommandProcessor.cs +++ b/src/System.Management.Automation/engine/ScriptCommandProcessor.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Management.Automation.Internal; using System.Management.Automation.Language; +using System.Management.Automation.Runspaces; using System.Reflection; using Dbg = System.Management.Automation.Diagnostics; @@ -47,7 +48,7 @@ protected ScriptCommandProcessorBase(IScriptCommandInfo commandInfo, ExecutionCo protected bool _dontUseScopeCommandOrigin; /// - /// If true, then an exit exception will be rethrown to instead of caught and processed... + /// If true, then an exit exception will be rethrown instead of caught and processed... /// protected bool _rethrowExitException; @@ -121,7 +122,7 @@ protected void CommonInitialization(ScriptBlock scriptBlock, ExecutionContext co // language modes (getting internal functions in the user's state) isn't a danger if ((!this.UseLocalScope) && (!this._rethrowExitException)) { - ValidateCompatibleLanguageMode(_scriptBlock, context.LanguageMode, Command.MyInvocation); + ValidateCompatibleLanguageMode(_scriptBlock, context, Command.MyInvocation); } } @@ -142,9 +143,8 @@ internal override bool IsHelpRequested(out string helpTarget, out HelpCategory h if (parameter.IsDashQuestion()) { Dictionary scriptBlockTokenCache = new Dictionary(); - string unused; HelpInfo helpInfo = _scriptBlock.GetHelpInfo(context: Context, commandInfo: CommandInfo, - dontSearchOnRemoteComputer: false, scriptBlockTokenCache: scriptBlockTokenCache, helpFile: out unused, helpUriFromDotLink: out unused); + dontSearchOnRemoteComputer: false, scriptBlockTokenCache: scriptBlockTokenCache, helpFile: out _, helpUriFromDotLink: out _); if (helpInfo == null) { break; @@ -237,6 +237,7 @@ internal sealed class DlrScriptCommandProcessor : ScriptCommandProcessorBase private MutableTuple _localsTuple; private bool _runOptimizedCode; private bool _argsBound; + private bool _anyClauseExecuted; private FunctionContext _functionContext; internal DlrScriptCommandProcessor(ScriptBlock scriptBlock, ExecutionContext context, bool useNewScope, CommandOrigin origin, SessionStateInternal sessionState, object dollarUnderbar) @@ -327,8 +328,7 @@ internal override void DoBegin() ScriptBlock.LogScriptBlockStart(_scriptBlock, Context.CurrentRunspace.InstanceId); - // Even if there is no begin, we need to set up the execution scope for this - // script... + // Even if there is no begin, we need to set up the execution scope for this script... SetCurrentScopeToExecutionScope(); CommandProcessorBase oldCurrentCommandProcessor = Context.CurrentCommandProcessor; try @@ -410,6 +410,7 @@ internal override void Complete() if (_scriptBlock.HasEndBlock) { var endBlock = _runOptimizedCode ? _scriptBlock.EndBlock : _scriptBlock.UnoptimizedEndBlock; + if (this.CommandRuntime.InputPipe.ExternalReader == null) { if (IsPipelineInputExpected()) @@ -433,7 +434,33 @@ internal override void Complete() } finally { - ScriptBlock.LogScriptBlockEnd(_scriptBlock, Context.CurrentRunspace.InstanceId); + if (!_scriptBlock.HasCleanBlock) + { + ScriptBlock.LogScriptBlockEnd(_scriptBlock, Context.CurrentRunspace.InstanceId); + } + } + } + + protected override void CleanResource() + { + if (_scriptBlock.HasCleanBlock && _anyClauseExecuted) + { + // The 'Clean' block doesn't write to pipeline. + Pipe oldOutputPipe = _functionContext._outputPipe; + _functionContext._outputPipe = new Pipe { NullPipe = true }; + + try + { + RunClause( + clause: _runOptimizedCode ? _scriptBlock.CleanBlock : _scriptBlock.UnoptimizedCleanBlock, + dollarUnderbar: AutomationNull.Value, + inputToProcess: AutomationNull.Value); + } + finally + { + _functionContext._outputPipe = oldOutputPipe; + ScriptBlock.LogScriptBlockEnd(_scriptBlock, Context.CurrentRunspace.InstanceId); + } } } @@ -459,6 +486,7 @@ private void RunClause(Action clause, object dollarUnderbar, ob { ExecutionContext.CheckStackDepth(); + _anyClauseExecuted = true; Pipe oldErrorOutputPipe = this.Context.ShellFunctionErrorOutputPipe; // If the script block has a different language mode than the current, @@ -553,7 +581,7 @@ private void RunClause(Action clause, object dollarUnderbar, ob } finally { - this.Context.RestoreErrorPipe(oldErrorOutputPipe); + Context.ShellFunctionErrorOutputPipe = oldErrorOutputPipe; if (oldLanguageMode.HasValue) { @@ -584,15 +612,12 @@ private void RunClause(Action clause, object dollarUnderbar, ob } catch (RuntimeException e) { - ManageScriptException(e); // always throws - // This quiets the compiler which wants to see a return value - // in all codepaths. - throw; + // This method always throws. + ManageScriptException(e); } catch (Exception e) { - // This cmdlet threw an exception, so - // wrap it and bubble it up. + // This cmdlet threw an exception, so wrap it and bubble it up. throw ManageInvocationException(e); } } diff --git a/src/System.Management.Automation/engine/ScriptInfo.cs b/src/System.Management.Automation/engine/ScriptInfo.cs index af91867c452..1abdc1424bf 100644 --- a/src/System.Management.Automation/engine/ScriptInfo.cs +++ b/src/System.Management.Automation/engine/ScriptInfo.cs @@ -7,7 +7,7 @@ namespace System.Management.Automation { /// - /// The command information for MSH scripts that are directly executable by MSH. + /// The command information for scripts that are directly executable by PowerShell. /// public class ScriptInfo : CommandInfo, IScriptCommandInfo { diff --git a/src/System.Management.Automation/engine/SecurityManagerBase.cs b/src/System.Management.Automation/engine/SecurityManagerBase.cs index 31712949cf7..0e4f3359704 100644 --- a/src/System.Management.Automation/engine/SecurityManagerBase.cs +++ b/src/System.Management.Automation/engine/SecurityManagerBase.cs @@ -18,7 +18,7 @@ public enum CommandOrigin Runspace, /// - /// The command was dispatched by the msh engine as a result of + /// The command was dispatched by the engine as a result of /// a dispatch request from an already running command. /// Internal diff --git a/src/System.Management.Automation/engine/SessionState.cs b/src/System.Management.Automation/engine/SessionState.cs index e0bdbd23ee0..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. /// - [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); @@ -391,14 +390,27 @@ internal SessionStateEntryVisibility CheckApplicationVisibility(string applicati private static SessionStateEntryVisibility checkPathVisibility(List list, string path) { - if (list == null || list.Count == 0) return SessionStateEntryVisibility.Private; - if (string.IsNullOrEmpty(path)) return SessionStateEntryVisibility.Private; + if (list == null || list.Count == 0) + { + return SessionStateEntryVisibility.Private; + } + + if (string.IsNullOrEmpty(path)) + { + return SessionStateEntryVisibility.Private; + } + + if (list.Contains("*")) + { + return SessionStateEntryVisibility.Public; + } - if (list.Contains("*")) return SessionStateEntryVisibility.Public; foreach (string p in list) { if (string.Equals(p, path, StringComparison.OrdinalIgnoreCase)) + { return SessionStateEntryVisibility.Public; + } if (WildcardPattern.ContainsWildcardCharacters(p)) { diff --git a/src/System.Management.Automation/engine/SessionStateCmdletAPIs.cs b/src/System.Management.Automation/engine/SessionStateCmdletAPIs.cs index f14c25f7420..5afa8f0169f 100644 --- a/src/System.Management.Automation/engine/SessionStateCmdletAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateCmdletAPIs.cs @@ -35,7 +35,7 @@ internal CmdletInfo GetCmdlet(string cmdletName) /// The name of the cmdlet value to retrieve. /// /// - /// The origin of hte command trying to retrieve this cmdlet. + /// The origin of the command trying to retrieve this cmdlet. /// /// /// The CmdletInfo representing the cmdlet. diff --git a/src/System.Management.Automation/engine/SessionStateContainer.cs b/src/System.Management.Automation/engine/SessionStateContainer.cs index ca7f03a42ca..e97d62c87d1 100644 --- a/src/System.Management.Automation/engine/SessionStateContainer.cs +++ b/src/System.Management.Automation/engine/SessionStateContainer.cs @@ -1324,8 +1324,8 @@ internal void GetChildItems( try { // If we're recursing, do some path fixups to match user - // expectations: - if (recurse) + // expectations, but only if the last part is a file and not a directory: + if (recurse && !path.EndsWith(Path.DirectorySeparatorChar) && !path.EndsWith(Path.AltDirectorySeparatorChar)) { string childName = GetChildName(path, context); @@ -1434,8 +1434,7 @@ internal void GetChildItems( return; } - int unUsedChildrenNotMatchingFilterCriteria = 0; - ProcessPathItems(providerInstance, providerPath, recurse, depth, context, out unUsedChildrenNotMatchingFilterCriteria, ProcessMode.Enumerate); + ProcessPathItems(providerInstance, providerPath, recurse, depth, context, out _, ProcessMode.Enumerate); } } else @@ -1496,12 +1495,11 @@ internal void GetChildItems( { // Do the recursion manually so that we can apply the // include and exclude filters - int unUsedChildrenNotMatchingFilterCriteria = 0; try { - // Temeporary set literal path as false to apply filter + // Temporary set literal path as false to apply filter context.SuppressWildcardExpansion = false; - ProcessPathItems(providerInstance, path, recurse, depth, context, out unUsedChildrenNotMatchingFilterCriteria, ProcessMode.Enumerate); + ProcessPathItems(providerInstance, path, recurse, depth, context, out _, ProcessMode.Enumerate); } finally { @@ -3499,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); @@ -4089,10 +4057,7 @@ internal Collection CopyItem(string[] paths, throw PSTraceSource.NewArgumentNullException(nameof(paths)); } - if (copyPath == null) - { - copyPath = string.Empty; - } + copyPath ??= string.Empty; CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); context.Force = force; @@ -4155,14 +4120,10 @@ internal void CopyItem( throw PSTraceSource.NewArgumentNullException(nameof(paths)); } - if (copyPath == null) - { - copyPath = string.Empty; - } + copyPath ??= string.Empty; // Get the provider specific path for the destination - PSDriveInfo unusedDrive = null; ProviderInfo destinationProvider = null; Microsoft.PowerShell.Commands.CopyItemDynamicParameters dynamicParams = context.DynamicParameters as Microsoft.PowerShell.Commands.CopyItemDynamicParameters; bool destinationIsRemote = false; @@ -4213,7 +4174,7 @@ internal void CopyItem( copyPath, context, out destinationProvider, - out unusedDrive); + out _); } else { @@ -4681,7 +4642,7 @@ internal object CopyItemDynamicParameters( } } - if (providerPath != null) + if (providerInstance != null) { // Get the dynamic parameters for the first resolved path return CopyItemDynamicParameters(providerInstance, providerPath, destination, recurse, newContext); @@ -4733,10 +4694,6 @@ private object CopyItemDynamicParameters( providerInstance != null, "Caller should validate providerInstance before calling this method"); - Dbg.Diagnostics.Assert( - path != null, - "Caller should validate path before calling this method"); - Dbg.Diagnostics.Assert( context != null, "Caller should validate context before calling this method"); diff --git a/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs b/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs index b8a89b9b31f..34c1ac50521 100644 --- a/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs @@ -231,28 +231,12 @@ internal void NewDrive(PSDriveInfo drive, string scopeID, CmdletProviderContext private static bool IsValidDriveName(string name) { - bool result = true; - - do - { - if (string.IsNullOrEmpty(name)) - { - result = false; - break; - } + const string CharactersInvalidInDriveName = ":/\\.~"; - if (name.IndexOfAny(s_charactersInvalidInDriveName) >= 0) - { - result = false; - break; - } - } while (false); - - return result; + return !string.IsNullOrEmpty(name) + && name.AsSpan().IndexOfAny(CharactersInvalidInDriveName) < 0; } - private static readonly char[] s_charactersInvalidInDriveName = new char[] { ':', '/', '\\', '.', '~' }; - /// /// Tries to resolve the drive root as an MSH path. If it successfully resolves /// to a single path then the resolved provider internal path is returned. If it diff --git a/src/System.Management.Automation/engine/SessionStateFunctionAPIs.cs b/src/System.Management.Automation/engine/SessionStateFunctionAPIs.cs index f444f46d2c3..5ef285ddcec 100644 --- a/src/System.Management.Automation/engine/SessionStateFunctionAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateFunctionAPIs.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; +using System.Management.Automation.Security; using Dbg = System.Management.Automation.Diagnostics; @@ -184,7 +185,7 @@ private bool IsFunctionVisibleInDebugger(FunctionInfo fnInfo, CommandOrigin orig // Early out. // Always allow built-in functions needed for command line debugging. - if ((this.ExecutionContext.LanguageMode == PSLanguageMode.FullLanguage) || + if (this.ExecutionContext.LanguageMode == PSLanguageMode.FullLanguage || (fnInfo == null) || (fnInfo.Name.Equals("prompt", StringComparison.OrdinalIgnoreCase)) || (fnInfo.Name.Equals("TabExpansion2", StringComparison.OrdinalIgnoreCase)) || diff --git a/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs b/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs index 6869f5071fb..56de07b8871 100644 --- a/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs @@ -316,10 +316,7 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context, bool l } } - if (context == null) - { - context = new CmdletProviderContext(this.ExecutionContext); - } + context ??= new CmdletProviderContext(this.ExecutionContext); if (CurrentDrive != null) { diff --git a/src/System.Management.Automation/engine/SessionStateNavigation.cs b/src/System.Management.Automation/engine/SessionStateNavigation.cs index f6d1a28eb1c..a1139ebc198 100644 --- a/src/System.Management.Automation/engine/SessionStateNavigation.cs +++ b/src/System.Management.Automation/engine/SessionStateNavigation.cs @@ -1458,7 +1458,6 @@ internal void MoveItem( } else { - PSDriveInfo unusedPSDriveInfo = null; ProviderInfo destinationProvider = null; CmdletProviderContext destinationContext = new CmdletProviderContext(this.ExecutionContext); @@ -1472,7 +1471,7 @@ internal void MoveItem( providerDestinationPaths[0].Path, destinationContext, out destinationProvider, - out unusedPSDriveInfo); + out _); } else { @@ -1484,7 +1483,7 @@ internal void MoveItem( destination, destinationContext, out destinationProvider, - out unusedPSDriveInfo); + out _); } // Now verify the providers are the same. diff --git a/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs b/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs index a6a7d2efc7f..0c94c9ea059 100644 --- a/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs @@ -960,10 +960,7 @@ internal void InitializeProvider( throw PSTraceSource.NewArgumentNullException(nameof(provider)); } - if (context == null) - { - context = new CmdletProviderContext(this.ExecutionContext); - } + context ??= new CmdletProviderContext(this.ExecutionContext); // Initialize the provider so that it can add any drives // that it needs. diff --git a/src/System.Management.Automation/engine/SessionStateScope.cs b/src/System.Management.Automation/engine/SessionStateScope.cs index fc5dff00784..03ab9bdfb4b 100644 --- a/src/System.Management.Automation/engine/SessionStateScope.cs +++ b/src/System.Management.Automation/engine/SessionStateScope.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; +using System.Management.Automation.Security; namespace System.Management.Automation { @@ -421,7 +422,10 @@ internal PSVariable SetVariable(string name, object value, bool asValue, bool fo bool varExists = TryGetVariable(name, origin, true, out variable); // Initialize the private variable dictionary if it's not yet - if (_variables == null) { GetPrivateVariables(); } + if (_variables == null) + { + GetPrivateVariables(); + } if (!asValue && variableToSet != null) { @@ -1245,11 +1249,18 @@ internal FunctionInfo SetFunction( name != null, "The caller should verify the name"); - var functionInfos = GetFunctions(); - FunctionInfo existingValue; + Dictionary functionInfos = GetFunctions(); FunctionInfo result; - if (!functionInfos.TryGetValue(name, out existingValue)) + + // Functions are equal only if they have the same name and if they come from the same module (if any). + // If the function is not associated with a module then the info 'ModuleName' property is set to empty string. + // If the new function has the same name of an existing function, but different module names, then the + // existing table function is replaced with the new function. + if (!functionInfos.TryGetValue(name, out FunctionInfo existingValue) || + (originalFunction != null && + !existingValue.ModuleName.Equals(originalFunction.ModuleName, StringComparison.OrdinalIgnoreCase))) { + // Add new function info to function table and return. result = functionFactory(name, function, originalFunction, options, context, helpFile); functionInfos[name] = result; @@ -1257,81 +1268,78 @@ internal FunctionInfo SetFunction( { GetAllScopeFunctions()[name] = result; } - } - else - { - // Make sure the function isn't constant or readonly - SessionState.ThrowIfNotVisible(origin, existingValue); - - if (IsFunctionOptionSet(existingValue, ScopedItemOptions.Constant) || - (!force && IsFunctionOptionSet(existingValue, ScopedItemOptions.ReadOnly))) - { - SessionStateUnauthorizedAccessException e = - new SessionStateUnauthorizedAccessException( - name, - SessionStateCategory.Function, - "FunctionNotWritable", - SessionStateStrings.FunctionNotWritable); + return result; + } - throw e; - } + // Update the existing function. - // Ensure we are not trying to set the function to constant as this can only be - // done at creation time. + // Make sure the function isn't constant or readonly. + SessionState.ThrowIfNotVisible(origin, existingValue); - if ((options & ScopedItemOptions.Constant) != 0) - { - SessionStateUnauthorizedAccessException e = - new SessionStateUnauthorizedAccessException( - name, - SessionStateCategory.Function, - "FunctionCannotBeMadeConstant", - SessionStateStrings.FunctionCannotBeMadeConstant); + if (IsFunctionOptionSet(existingValue, ScopedItemOptions.Constant) || + (!force && IsFunctionOptionSet(existingValue, ScopedItemOptions.ReadOnly))) + { + SessionStateUnauthorizedAccessException e = + new SessionStateUnauthorizedAccessException( + name, + SessionStateCategory.Function, + "FunctionNotWritable", + SessionStateStrings.FunctionNotWritable); - throw e; - } + throw e; + } - // Ensure we are not trying to remove the AllScope option + // Ensure we are not trying to set the function to constant as this can only be + // done at creation time. + if ((options & ScopedItemOptions.Constant) != 0) + { + SessionStateUnauthorizedAccessException e = + new SessionStateUnauthorizedAccessException( + name, + SessionStateCategory.Function, + "FunctionCannotBeMadeConstant", + SessionStateStrings.FunctionCannotBeMadeConstant); - if ((options & ScopedItemOptions.AllScope) == 0 && - IsFunctionOptionSet(existingValue, ScopedItemOptions.AllScope)) - { - SessionStateUnauthorizedAccessException e = - new SessionStateUnauthorizedAccessException( - name, - SessionStateCategory.Function, - "FunctionAllScopeOptionCannotBeRemoved", - SessionStateStrings.FunctionAllScopeOptionCannotBeRemoved); + throw e; + } - throw e; - } + // Ensure we are not trying to remove the AllScope option. + if ((options & ScopedItemOptions.AllScope) == 0 && + IsFunctionOptionSet(existingValue, ScopedItemOptions.AllScope)) + { + SessionStateUnauthorizedAccessException e = + new SessionStateUnauthorizedAccessException( + name, + SessionStateCategory.Function, + "FunctionAllScopeOptionCannotBeRemoved", + SessionStateStrings.FunctionAllScopeOptionCannotBeRemoved); - FunctionInfo existingFunction = existingValue; - FunctionInfo newValue = null; + throw e; + } - // If the function type changes (i.e.: function to workflow or back) - // then we need to blast what was there - newValue = functionFactory(name, function, originalFunction, options, context, helpFile); + FunctionInfo existingFunction = existingValue; - bool changesFunctionType = existingFunction.GetType() != newValue.GetType(); + // If the function type changes (i.e.: function to workflow or back) + // then we need to replace what was there. + FunctionInfo newValue = functionFactory(name, function, originalFunction, options, context, helpFile); - // Since the options are set after the script block, we have to - // forcefully apply the script block if the options will be - // set to not being ReadOnly - if (changesFunctionType || - ((existingFunction.Options & ScopedItemOptions.ReadOnly) != 0 && force)) - { - result = newValue; - functionInfos[name] = newValue; - } - else - { - bool applyForce = force || (options & ScopedItemOptions.ReadOnly) == 0; + bool changesFunctionType = existingFunction.GetType() != newValue.GetType(); - existingFunction.Update(newValue, applyForce, options, helpFile); - result = existingFunction; - } + // Since the options are set after the script block, we have to + // forcefully apply the script block if the options will be + // set to not being ReadOnly. + if (changesFunctionType || + ((existingFunction.Options & ScopedItemOptions.ReadOnly) != 0 && force)) + { + result = newValue; + functionInfos[name] = newValue; + } + else + { + bool applyForce = force || (options & ScopedItemOptions.ReadOnly) == 0; + existingFunction.Update(newValue, applyForce, options, helpFile); + result = existingFunction; } return result; @@ -1628,19 +1636,21 @@ internal Language.TypeResolutionState TypeResolutionState internal void AddType(string name, Type type) { - if (TypeTable == null) - { - TypeTable = new Dictionary(StringComparer.OrdinalIgnoreCase); - } + TypeTable ??= new Dictionary(StringComparer.OrdinalIgnoreCase); TypeTable[name] = type; } internal Type LookupType(string name) { - if (TypeTable == null) return null; + if (TypeTable == null) + { + return null; + } + Type result; TypeTable.TryGetValue(name, out result); + return result; } @@ -1681,12 +1691,18 @@ private static FunctionInfo CreateFunction(string name, ScriptBlock function, Fu // Then use the creation constructors - workflows don't get here because the workflow info // is created during compilation. - else if (function.IsFilter) { newValue = new FilterInfo(name, function, options, context, helpFile); } + else if (function.IsFilter) + { + newValue = new FilterInfo(name, function, options, context, helpFile); + } else if (function.IsConfiguration) { newValue = new ConfigurationInfo(name, function, options, context, helpFile, function.IsMetaConfiguration()); } - else newValue = new FunctionInfo(name, function, options, context, helpFile); + else + { + newValue = new FunctionInfo(name, function, options, context, helpFile); + } return newValue; } @@ -1969,11 +1985,21 @@ private void CheckVariableChangeInConstrainedLanguage(PSVariable variable) var context = LocalPipeline.GetExecutionContextFromTLS(); if (context?.LanguageMode == PSLanguageMode.ConstrainedLanguage) { - if ((variable.Options & ScopedItemOptions.AllScope) == ScopedItemOptions.AllScope) + if (variable.Options.HasFlag(ScopedItemOptions.AllScope)) { - // Don't let people set AllScope variables in ConstrainedLanguage, as they can be used to - // interfere with the session state of trusted commands. - throw new PSNotSupportedException(); + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + // Don't let people set AllScope variables in ConstrainedLanguage, as they can be used to + // interfere with the session state of trusted commands. + throw new PSNotSupportedException(); + } + + SystemPolicy.LogWDACAuditMessage( + context: context, + title: SessionStateStrings.WDACSessionStateVarLogTitle, + message: StringUtil.Format(SessionStateStrings.WDACSessionStateVarLogMessage, variable.Name), + fqid: "AllScopeVariableNotAllowed", + dropIntoDebugger: true); } // Mark untrusted values for assignments to 'Global:' variables, and 'Script:' variables in diff --git a/src/System.Management.Automation/engine/SessionStateUtils.cs b/src/System.Management.Automation/engine/SessionStateUtils.cs index 754f228633a..6bdc29da198 100644 --- a/src/System.Management.Automation/engine/SessionStateUtils.cs +++ b/src/System.Management.Automation/engine/SessionStateUtils.cs @@ -151,10 +151,7 @@ internal static Collection ConvertArrayToCollection(T[] array) /// internal static bool CollectionContainsValue(IEnumerable collection, object value, IComparer comparer) { - if (collection == null) - { - throw new ArgumentNullException(nameof(collection)); - } + ArgumentNullException.ThrowIfNull(collection); bool result = false; diff --git a/src/System.Management.Automation/engine/SessionStateVariableAPIs.cs b/src/System.Management.Automation/engine/SessionStateVariableAPIs.cs index 8cc9e4bafa4..336c2b77952 100644 --- a/src/System.Management.Automation/engine/SessionStateVariableAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateVariableAPIs.cs @@ -352,8 +352,7 @@ internal object GetVariableValueFromProvider( // First get the provider for the path. ProviderInfo providerInfo = null; - string unused = - this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); + _ = this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); throw NewProviderInvocationException( "ProviderCannotBeUsedAsVariable", @@ -368,8 +367,7 @@ internal object GetVariableValueFromProvider( // First get the provider for the path. ProviderInfo providerInfo = null; - string unused = - this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); + _ = this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); throw NewProviderInvocationException( "ProviderCannotBeUsedAsVariable", @@ -407,8 +405,7 @@ internal object GetVariableValueFromProvider( // First get the provider for the path. ProviderInfo providerInfo = null; - string unused = - this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); + _ = this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); throw NewProviderInvocationException( "ProviderVariableSyntaxInvalid", @@ -445,8 +442,7 @@ internal object GetVariableValueFromProvider( { // First get the provider for the path. ProviderInfo providerInfo = null; - string unused = - this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); + _ = this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); ProviderInvocationException providerException = new ProviderInvocationException( @@ -741,8 +737,7 @@ internal object GetVariableValueAtScope(string name, string scopeID) // First get the provider for the path. ProviderInfo providerInfo = null; - string unused = - this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); + _ = this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); throw NewProviderInvocationException( "ProviderCannotBeUsedAsVariable", @@ -757,8 +752,7 @@ internal object GetVariableValueAtScope(string name, string scopeID) // First get the provider for the path. ProviderInfo providerInfo = null; - string unused = - this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); + _ = this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); throw NewProviderInvocationException( "ProviderCannotBeUsedAsVariable", @@ -796,8 +790,7 @@ internal object GetVariableValueAtScope(string name, string scopeID) // First get the provider for the path. ProviderInfo providerInfo = null; - string unused = - this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); + _ = this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); throw NewProviderInvocationException( "ProviderVariableSyntaxInvalid", @@ -835,8 +828,7 @@ internal object GetVariableValueAtScope(string name, string scopeID) // First get the provider for the path. ProviderInfo providerInfo = null; - string unused = - this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); + _ = this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); ProviderInvocationException providerException = new ProviderInvocationException( @@ -1245,8 +1237,7 @@ internal object SetVariable( // First get the provider for the path. ProviderInfo providerInfo = null; - string unused = - this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); + _ = this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); throw NewProviderInvocationException( "ProviderCannotBeUsedAsVariable", @@ -1261,8 +1252,7 @@ internal object SetVariable( // First get the provider for the path. ProviderInfo providerInfo = null; - string unused = - this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); + _ = this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); throw NewProviderInvocationException( "ProviderCannotBeUsedAsVariable", @@ -1301,8 +1291,7 @@ internal object SetVariable( // First get the provider for the path. ProviderInfo providerInfo = null; - string unused = - this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); + _ = this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); throw NewProviderInvocationException( "ProviderVariableSyntaxInvalid", @@ -1325,8 +1314,7 @@ internal object SetVariable( // First get the provider for the path. ProviderInfo providerInfo = null; - string unused = - this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); + _ = this.Globber.GetProviderPath(variablePath.QualifiedName, out providerInfo); ProviderInvocationException providerException = new ProviderInvocationException( @@ -1839,10 +1827,7 @@ private static void GetScopeVariableTable(SessionStateScope scope, Dictionary diff --git a/src/System.Management.Automation/engine/ShellVariable.cs b/src/System.Management.Automation/engine/ShellVariable.cs index 424be295346..8440845a32d 100644 --- a/src/System.Management.Automation/engine/ShellVariable.cs +++ b/src/System.Management.Automation/engine/ShellVariable.cs @@ -244,6 +244,14 @@ internal void DebuggerCheckVariableWrite() } } + /// + /// Gets the value without triggering debugger check. + /// + internal virtual object GetValueRaw() + { + return _value; + } + /// /// Gets or sets the value of the variable. /// @@ -796,6 +804,11 @@ public override object Value } } + internal override object GetValueRaw() + { + return _tuple.GetValue(_tupleSlot); + } + internal override void SetValueRaw(object newValue, bool preserveValueTypeSemantics) { if (preserveValueTypeSemantics) diff --git a/src/System.Management.Automation/engine/SpecialVariables.cs b/src/System.Management.Automation/engine/SpecialVariables.cs index ba757722e91..7563f89a919 100644 --- a/src/System.Management.Automation/engine/SpecialVariables.cs +++ b/src/System.Management.Automation/engine/SpecialVariables.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; +using System.Management.Automation.Internal; namespace System.Management.Automation { @@ -204,6 +205,7 @@ internal static class SpecialVariables internal static readonly VariablePath PSModuleAutoLoadingPreferenceVarPath = new VariablePath("global:" + PSModuleAutoLoading); #region Platform Variables + internal const string IsLinux = "IsLinux"; internal static readonly VariablePath IsLinuxPath = new VariablePath("IsLinux"); @@ -221,6 +223,7 @@ internal static class SpecialVariables internal static readonly VariablePath IsCoreCLRPath = new VariablePath("IsCoreCLR"); #endregion + #region Preference Variables internal const string DebugPreference = "DebugPreference"; @@ -257,6 +260,16 @@ internal static class SpecialVariables #endregion Preference Variables + internal const string PSNativeCommandUseErrorActionPreference = nameof(PSNativeCommandUseErrorActionPreference); + + internal static readonly VariablePath PSNativeCommandUseErrorActionPreferenceVarPath = + new(PSNativeCommandUseErrorActionPreference); + + // Native command argument passing style + internal const string NativeArgumentPassing = "PSNativeCommandArgumentPassing"; + + internal static readonly VariablePath NativeArgumentPassingVarPath = new VariablePath(NativeArgumentPassing); + internal const string ErrorView = "ErrorView"; internal static readonly VariablePath ErrorViewVarPath = new VariablePath(ErrorView); @@ -316,46 +329,53 @@ internal static class SpecialVariables /* PSCommandPath */ typeof(string), }; - internal static readonly string[] PreferenceVariables = { - SpecialVariables.DebugPreference, - SpecialVariables.VerbosePreference, - SpecialVariables.ErrorActionPreference, - SpecialVariables.WhatIfPreference, - SpecialVariables.WarningPreference, - SpecialVariables.InformationPreference, - SpecialVariables.ConfirmPreference, - }; - - internal static readonly Type[] PreferenceVariableTypes = { - /* DebugPreference */ typeof(ActionPreference), - /* VerbosePreference */ typeof(ActionPreference), - /* ErrorPreference */ typeof(ActionPreference), - /* WhatIfPreference */ typeof(SwitchParameter), - /* WarningPreference */ typeof(ActionPreference), - /* InformationPreference */ typeof(ActionPreference), - /* ConfirmPreference */ typeof(ConfirmImpact), - }; + // This array and the one below it exist to optimize the way common parameters work in advanced functions. + // Common parameters work by setting preference variables in the scope of the function and restoring the old value afterward. + // Variables that don't correspond to common cmdlet parameters don't need to be added here. + internal static readonly string[] PreferenceVariables = + { + SpecialVariables.DebugPreference, + SpecialVariables.VerbosePreference, + SpecialVariables.ErrorActionPreference, + SpecialVariables.WhatIfPreference, + SpecialVariables.WarningPreference, + SpecialVariables.InformationPreference, + SpecialVariables.ConfirmPreference, + SpecialVariables.ProgressPreference, + }; + + internal static readonly Type[] PreferenceVariableTypes = + { + /* DebugPreference */ typeof(ActionPreference), + /* VerbosePreference */ typeof(ActionPreference), + /* ErrorPreference */ typeof(ActionPreference), + /* WhatIfPreference */ typeof(SwitchParameter), + /* 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 // see an assignment to any of these variables so that they get handled properly (either throwing an exception // because they are constant/readonly, or having the value persist in parent scopes where the allscope variable // also exists. - internal static readonly string[] AllScopeVariables = { - SpecialVariables.Question, - SpecialVariables.ExecutionContext, - SpecialVariables.False, - SpecialVariables.Home, - SpecialVariables.Host, - SpecialVariables.PID, - SpecialVariables.PSCulture, - SpecialVariables.PSHome, - SpecialVariables.PSUICulture, - SpecialVariables.PSVersionTable, - SpecialVariables.PSEdition, - SpecialVariables.ShellId, - SpecialVariables.True, - SpecialVariables.EnabledExperimentalFeatures, - }; + internal static readonly Dictionary AllScopeVariables = new(StringComparer.OrdinalIgnoreCase) { + { Question, typeof(bool) }, + { ExecutionContext, typeof(EngineIntrinsics) }, + { False, typeof(bool) }, + { Home, typeof(string) }, + { Host, typeof(object) }, + { PID, typeof(int) }, + { PSCulture, typeof(string) }, + { PSHome, typeof(string) }, + { PSUICulture, typeof(string) }, + { PSVersionTable, typeof(PSVersionHashTable) }, + { PSEdition, typeof(string) }, + { ShellId, typeof(string) }, + { True, typeof(bool) }, + { EnabledExperimentalFeatures, typeof(ReadOnlyBag) } + }; private static readonly HashSet s_classMethodsAccessibleVariables = new HashSet ( @@ -401,5 +421,6 @@ internal enum PreferenceVariable Warning = 13, Information = 14, Confirm = 15, + Progress = 16, } } diff --git a/src/System.Management.Automation/engine/Subsystem/DscSubsystem/ICrossPlatformDsc.cs b/src/System.Management.Automation/engine/Subsystem/DscSubsystem/ICrossPlatformDsc.cs new file mode 100644 index 00000000000..d1fe2234309 --- /dev/null +++ b/src/System.Management.Automation/engine/Subsystem/DscSubsystem/ICrossPlatformDsc.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.ObjectModel; +using System.Collections.Generic; +using System.Management.Automation.Language; + +namespace System.Management.Automation.Subsystem.DSC +{ + /// + /// Interface for implementing a cross platform desired state configuration component. + /// + public interface ICrossPlatformDsc : ISubsystem + { + /// + /// Default implementation. No function is required for this subsystem. + /// + Dictionary? ISubsystem.FunctionsToDefine => null; + + /// + /// DSC initializer function. + /// + void LoadDefaultKeywords(Collection errors); + + /// + /// Clear internal class caches. + /// + void ClearCache(); + + /// + /// Returns resource usage string. + /// + string GetDSCResourceUsageString(DynamicKeyword keyword); + + /// + /// Checks if a string is one of dynamic keywords that can be used in both configuration and meta configuration. + /// + bool IsSystemResourceName(string name); + + /// + /// Checks if a string matches default module name used for meta configuration resources. + /// + bool IsDefaultModuleNameForMetaConfigResource(string name); + } +} diff --git a/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/FeedbackHub.cs b/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/FeedbackHub.cs new file mode 100644 index 00000000000..588cf086d42 --- /dev/null +++ b/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/FeedbackHub.cs @@ -0,0 +1,315 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Management.Automation.Language; +using System.Management.Automation.Runspaces; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PowerShell.Commands; + +namespace System.Management.Automation.Subsystem.Feedback +{ + /// + /// The class represents a result from a feedback provider. + /// + public class FeedbackResult + { + /// + /// Gets the Id of the feedback provider. + /// + public Guid Id { get; } + + /// + /// Gets the name of the feedback provider. + /// + public string Name { get; } + + /// + /// Gets the feedback item. + /// + public FeedbackItem Item { get; } + + internal FeedbackResult(Guid id, string name, FeedbackItem item) + { + Id = id; + Name = name; + Item = item; + } + } + + /// + /// Provides a set of feedbacks for given input. + /// + public static class FeedbackHub + { + /// + /// Collect the feedback from registered feedback providers using the default timeout. + /// + public static List? GetFeedback(Runspace runspace) + { + return GetFeedback(runspace, millisecondsTimeout: 300); + } + + /// + /// Collect the feedback from registered feedback providers using the specified timeout. + /// + public static List? GetFeedback(Runspace runspace, int millisecondsTimeout) + { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(millisecondsTimeout); + + if (runspace is not LocalRunspace localRunspace) + { + return null; + } + + var providers = SubsystemManager.GetSubsystems(); + if (providers.Count is 0) + { + return null; + } + + ExecutionContext executionContext = localRunspace.ExecutionContext; + bool questionMarkValue = executionContext.QuestionMarkVariableValue; + + // The command line would have run successfully in most cases during an interactive use of the shell. + // So, we do a quick check to see whether we can skip proceeding, so as to avoid unneeded allocations + // from the 'TryGetFeedbackContext' call below. + if (questionMarkValue && CanSkip(providers)) + { + return null; + } + + // Get the last history item + HistoryInfo[] histories = localRunspace.History.GetEntries(id: 0, count: 1, newest: true); + if (histories.Length is 0) + { + return null; + } + + // Try creating the feedback context object. + if (!TryGetFeedbackContext(executionContext, questionMarkValue, histories[0], out FeedbackContext? feedbackContext)) + { + return null; + } + + int count = providers.Count; + IFeedbackProvider? generalFeedback = null; + List>? tasks = null; + CancellationTokenSource? cancellationSource = null; + Func? callBack = null; + + foreach (IFeedbackProvider provider in providers) + { + if (!provider.Trigger.HasFlag(feedbackContext.Trigger)) + { + continue; + } + + if (provider is GeneralCommandErrorFeedback) + { + // This built-in feedback provider needs to run on the target Runspace. + generalFeedback = provider; + continue; + } + + if (tasks is null) + { + tasks = new List>(capacity: count); + cancellationSource = new CancellationTokenSource(); + callBack = GetCallBack(feedbackContext, cancellationSource); + } + + // Other feedback providers will run on background threads in parallel. + tasks.Add(Task.Factory.StartNew( + callBack!, + provider, + cancellationSource!.Token, + TaskCreationOptions.DenyChildAttach, + TaskScheduler.Default)); + } + + Task? waitTask = null; + if (tasks is not null) + { + waitTask = Task.WhenAny( + Task.WhenAll(tasks), + Task.Delay(millisecondsTimeout, cancellationSource!.Token)); + } + + List? resultList = null; + if (generalFeedback is not null) + { + FeedbackResult? builtInResult = GetBuiltInFeedback(generalFeedback, localRunspace, feedbackContext, questionMarkValue); + if (builtInResult is not null) + { + resultList ??= new List(count); + resultList.Add(builtInResult); + } + } + + if (waitTask is not null) + { + try + { + waitTask.Wait(); + cancellationSource!.Cancel(); + + foreach (Task task in tasks!) + { + if (task.IsCompletedSuccessfully) + { + FeedbackResult? result = task.Result; + if (result is not null) + { + resultList ??= new List(count); + resultList.Add(result); + } + } + } + } + finally + { + cancellationSource!.Dispose(); + } + } + + return resultList; + } + + private static bool CanSkip(IEnumerable providers) + { + bool canSkip = true; + foreach (IFeedbackProvider provider in providers) + { + if (provider.Trigger.HasFlag(FeedbackTrigger.Success)) + { + canSkip = false; + break; + } + } + + return canSkip; + } + + private static FeedbackResult? GetBuiltInFeedback( + IFeedbackProvider builtInFeedback, + LocalRunspace localRunspace, + FeedbackContext feedbackContext, + bool questionMarkValue) + { + bool changedDefault = false; + Runspace? oldDefault = Runspace.DefaultRunspace; + + try + { + if (oldDefault != localRunspace) + { + changedDefault = true; + Runspace.DefaultRunspace = localRunspace; + } + + FeedbackItem? item = builtInFeedback.GetFeedback(feedbackContext, CancellationToken.None); + if (item is not null) + { + return new FeedbackResult(builtInFeedback.Id, builtInFeedback.Name, item); + } + } + finally + { + if (changedDefault) + { + Runspace.DefaultRunspace = oldDefault; + } + + // Restore $? for the target Runspace. + localRunspace.ExecutionContext.QuestionMarkVariableValue = questionMarkValue; + } + + return null; + } + + private static bool TryGetFeedbackContext( + ExecutionContext executionContext, + bool questionMarkValue, + HistoryInfo lastHistory, + [NotNullWhen(true)] out FeedbackContext? feedbackContext) + { + feedbackContext = null; + Ast ast = Parser.ParseInput(lastHistory.CommandLine, out Token[] tokens, out _); + + FeedbackTrigger trigger; + ErrorRecord? lastError = null; + + if (IsPureComment(tokens)) + { + // Don't trigger anything in this case. + return false; + } + else if (questionMarkValue) + { + trigger = FeedbackTrigger.Success; + } + else if (TryGetLastError(executionContext, lastHistory, out lastError)) + { + trigger = lastError.FullyQualifiedErrorId is "CommandNotFoundException" + ? FeedbackTrigger.CommandNotFound + : FeedbackTrigger.Error; + } + else + { + return false; + } + + PathInfo cwd = executionContext.SessionState.Path.CurrentLocation; + feedbackContext = new(trigger, ast, tokens, cwd, lastError); + return true; + } + + private static bool IsPureComment(Token[] tokens) + { + return tokens.Length is 2 && tokens[0].Kind is TokenKind.Comment && tokens[1].Kind is TokenKind.EndOfInput; + } + + private static bool TryGetLastError(ExecutionContext context, HistoryInfo lastHistory, [NotNullWhen(true)] out ErrorRecord? lastError) + { + lastError = null; + ArrayList errorList = (ArrayList)context.DollarErrorVariable; + if (errorList.Count == 0) + { + return false; + } + + lastError = errorList[0] as ErrorRecord; + if (lastError is null && errorList[0] is RuntimeException rtEx) + { + lastError = rtEx.ErrorRecord; + } + + if (lastError?.InvocationInfo is null || lastError.InvocationInfo.HistoryId != lastHistory.Id) + { + return false; + } + + return true; + } + + // A local helper function to avoid creating an instance of the generated delegate helper class + // when no feedback provider is registered. + private static Func GetCallBack( + FeedbackContext feedbackContext, + CancellationTokenSource cancellationSource) + { + return state => + { + var provider = (IFeedbackProvider)state!; + var item = provider.GetFeedback(feedbackContext, cancellationSource.Token); + return item is null ? null : new FeedbackResult(provider.Id, provider.Name, item); + }; + } + } +} diff --git a/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/IFeedbackProvider.cs b/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/IFeedbackProvider.cs new file mode 100644 index 00000000000..0af184f4e99 --- /dev/null +++ b/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/IFeedbackProvider.cs @@ -0,0 +1,303 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System.Collections.Generic; +using System.IO; +using System.Management.Automation.Internal; +using System.Management.Automation.Language; +using System.Management.Automation.Runspaces; +using System.Threading; +using Microsoft.PowerShell.Telemetry; + +namespace System.Management.Automation.Subsystem.Feedback +{ + /// + /// Types of trigger for the feedback provider. + /// + [Flags] + public enum FeedbackTrigger + { + /// + /// The last command line executed successfully. + /// + Success = 0x0001, + + /// + /// The last command line failed due to a command-not-found error. + /// This is a special case of . + /// + CommandNotFound = 0x0002, + + /// + /// The last command line failed with an error record. + /// This includes the case of command-not-found error. + /// + Error = CommandNotFound | 0x0004, + + /// + /// All possible triggers. + /// + All = Success | Error + } + + /// + /// Layout for displaying the recommended actions. + /// + public enum FeedbackDisplayLayout + { + /// + /// Display one recommended action per row. + /// + Portrait, + + /// + /// Display all recommended actions in the same row. + /// + Landscape, + } + + /// + /// Context information about the last command line. + /// + public sealed class FeedbackContext + { + /// + /// Gets the feedback trigger. + /// + public FeedbackTrigger Trigger { get; } + + /// + /// Gets the last command line that was just executed. + /// + public string CommandLine { get; } + + /// + /// Gets the abstract syntax tree (AST) generated from parsing the last command line. + /// + public Ast CommandLineAst { get; } + + /// + /// Gets the tokens generated from parsing the last command line. + /// + public IReadOnlyList CommandLineTokens { get; } + + /// + /// Gets the current location of the default session. + /// + public PathInfo CurrentLocation { get; } + + /// + /// Gets the last error record generated from executing the last command line. + /// + public ErrorRecord? LastError { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The trigger of this feedback call. + /// The command line that was just executed. + /// The current location of the default session. + /// The error that was triggerd by the last command line. + public FeedbackContext(FeedbackTrigger trigger, string commandLine, PathInfo cwd, ErrorRecord? lastError) + { + ArgumentException.ThrowIfNullOrEmpty(commandLine); + ArgumentNullException.ThrowIfNull(cwd); + + Trigger = trigger; + CommandLine = commandLine; + CommandLineAst = Parser.ParseInput(commandLine, out Token[] tokens, out _); + CommandLineTokens = tokens; + LastError = lastError; + CurrentLocation = cwd; + } + + /// + /// Initializes a new instance of the class. + /// + /// The trigger of this feedback call. + /// The abstract syntax tree (AST) from parsing the last command line. + /// The tokens from parsing the last command line. + /// The current location of the default session. + /// The error that was triggerd by the last command line. + public FeedbackContext(FeedbackTrigger trigger, Ast commandLineAst, Token[] commandLineTokens, PathInfo cwd, ErrorRecord? lastError) + { + ArgumentNullException.ThrowIfNull(commandLineAst); + ArgumentNullException.ThrowIfNull(commandLineTokens); + ArgumentNullException.ThrowIfNull(cwd); + + Trigger = trigger; + CommandLine = commandLineAst.Extent.Text; + CommandLineAst = commandLineAst; + CommandLineTokens = commandLineTokens; + LastError = lastError; + CurrentLocation = cwd; + } + } + + /// + /// The class represents a feedback item generated by the feedback provider. + /// + public sealed class FeedbackItem + { + /// + /// Gets the description message about this feedback. + /// + public string Header { get; } + + /// + /// Gets the footer message about this feedback. + /// + public string? Footer { get; } + + /// + /// Gets the recommended actions -- command lines or even code snippets to run. + /// + public List? RecommendedActions { get; } + + /// + /// Gets the layout to use for displaying the recommended actions. + /// + public FeedbackDisplayLayout Layout { get; } + + /// + /// Gets or sets the next feedback item, if there is one. + /// + public FeedbackItem? Next { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The description message (must be not null or empty). + /// The recommended actions to take (optional). + public FeedbackItem(string header, List? actions) + : this(header, actions, footer: null, FeedbackDisplayLayout.Portrait) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The description message (must be not null or empty). + /// The recommended actions to take (optional). + /// The layout for displaying the actions. + public FeedbackItem(string header, List? actions, FeedbackDisplayLayout layout) + : this(header, actions, footer: null, layout) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The description message (must be not null or empty). + /// The recommended actions to take (optional). + /// The footer message (optional). + /// The layout for displaying the actions. + public FeedbackItem(string header, List? actions, string? footer, FeedbackDisplayLayout layout) + { + ArgumentException.ThrowIfNullOrEmpty(header); + + Header = header; + RecommendedActions = actions; + Footer = footer; + Layout = layout; + } + } + + /// + /// Interface for implementing a feedback provider on command failures. + /// + public interface IFeedbackProvider : ISubsystem + { + /// + /// Default implementation. No function is required for a feedback provider. + /// + Dictionary? ISubsystem.FunctionsToDefine => null; + + /// + /// Gets the types of trigger for this feedback provider. + /// + /// + /// The default implementation triggers a feedback provider by only. + /// + FeedbackTrigger Trigger => FeedbackTrigger.CommandNotFound; + + /// + /// Gets feedback based on the given commandline and error record. + /// + /// The context for the feedback call. + /// The cancellation token to cancel the operation. + /// The feedback item. + FeedbackItem? GetFeedback(FeedbackContext context, CancellationToken token); + } + + internal sealed class GeneralCommandErrorFeedback : IFeedbackProvider + { + private readonly Guid _guid; + + internal GeneralCommandErrorFeedback() + { + _guid = new Guid("A3C6B07E-4A89-40C9-8BE6-2A9AAD2786A4"); + } + + public Guid Id => _guid; + + public string Name => "General Feedback"; + + public string Description => "The built-in general feedback source for command errors."; + + public FeedbackItem? GetFeedback(FeedbackContext context, CancellationToken token) + { + var rsToUse = Runspace.DefaultRunspace; + if (rsToUse is null) + { + return null; + } + + // This feedback provider is only triggered by 'CommandNotFound' error, so the + // 'LastError' property is guaranteed to be not null. + ErrorRecord lastError = context.LastError!; + SessionState sessionState = rsToUse.ExecutionContext.SessionState; + + var target = (string)lastError.TargetObject; + CommandInvocationIntrinsics invocation = sessionState.InvokeCommand; + + // See if target is actually an executable file in current directory. + var localTarget = Path.Combine(".", target); + var command = invocation.GetCommand( + localTarget, + CommandTypes.Application | CommandTypes.ExternalScript); + + if (command is not null) + { + return new FeedbackItem( + StringUtil.Format(SuggestionStrings.Suggestion_CommandExistsInCurrentDirectory, target), + new List { localTarget }); + } + + // Check fuzzy matching command names. + var pwsh = PowerShell.Create(RunspaceMode.CurrentRunspace); + var results = pwsh.AddCommand("Get-Command") + .AddParameter("UseFuzzyMatching") + .AddParameter("FuzzyMinimumDistance", 1) + .AddParameter("Name", target) + .AddCommand("Select-Object") + .AddParameter("First", 5) + .AddParameter("Unique") + .AddParameter("ExpandProperty", "Name") + .Invoke(); + + if (results.Count > 0) + { + ApplicationInsightsTelemetry.SendUseTelemetry("FuzzyMatching", "CommandNotFound"); + return new FeedbackItem( + SuggestionStrings.Suggestion_CommandNotFound, + new List(results), + FeedbackDisplayLayout.Landscape); + } + + return null; + } + } +} diff --git a/src/System.Management.Automation/engine/Subsystem/ISubsystem.cs b/src/System.Management.Automation/engine/Subsystem/ISubsystem.cs index e19f37f06c7..72b8a55d148 100644 --- a/src/System.Management.Automation/engine/Subsystem/ISubsystem.cs +++ b/src/System.Management.Automation/engine/Subsystem/ISubsystem.cs @@ -11,12 +11,26 @@ namespace System.Management.Automation.Subsystem /// /// Define the kinds of subsystems. /// - public enum SubsystemKind + /// + /// This enum uses power of 2 as the values for the enum elements, so as to make sure + /// the bitwise 'or' operation of the elements always results in an invalid value. + /// + public enum SubsystemKind : uint { /// /// Component that provides predictive suggestions to commandline input. /// CommandPredictor = 1, + + /// + /// Cross platform desired state configuration component. + /// + CrossPlatformDsc = 2, + + /// + /// Component that provides feedback when a command fails interactively. + /// + FeedbackProvider = 4, } /// @@ -24,12 +38,8 @@ public enum SubsystemKind /// The API contracts for specific subsystems are defined within the specific interfaces/abstract classes that implements this interface. /// /// - /// There are two purposes to have the internal member `Kind` declared in 'ISubsystem': - /// 1. Make the mapping from an `ISubsystem` implementation to the `SubsystemKind` easy; - /// 2. Make sure a user cannot directly implement 'ISubsystem', but have to derive from one of the concrete subsystem interface or abstract class. - /// - /// The internal member needs to have a default implementation defined by the specific subsystem interfaces or abstract class, - /// because it should be the same for a specific kind of subsystem. + /// A user should not directly implement , but instead should derive from one of the concrete subsystem interfaces or abstract classes. + /// The instance of a type that only implements 'ISubsystem' cannot be registered to the . /// public interface ISubsystem { @@ -53,10 +63,5 @@ public interface ISubsystem /// Key: function name; Value: function script. /// Dictionary? FunctionsToDefine { get; } - - /// - /// Gets the subsystem kind. - /// - internal SubsystemKind Kind { get; } } } diff --git a/src/System.Management.Automation/engine/Subsystem/CommandPrediction/CommandPrediction.cs b/src/System.Management.Automation/engine/Subsystem/PredictionSubsystem/CommandPrediction.cs similarity index 54% rename from src/System.Management.Automation/engine/Subsystem/CommandPrediction/CommandPrediction.cs rename to src/System.Management.Automation/engine/Subsystem/PredictionSubsystem/CommandPrediction.cs index edc8fb272a2..edb46960612 100644 --- a/src/System.Management.Automation/engine/Subsystem/CommandPrediction/CommandPrediction.cs +++ b/src/System.Management.Automation/engine/Subsystem/PredictionSubsystem/CommandPrediction.cs @@ -5,12 +5,11 @@ using System; using System.Collections.Generic; -using System.Management.Automation.Internal; using System.Management.Automation.Language; using System.Threading; using System.Threading.Tasks; -namespace System.Management.Automation.Subsystem +namespace System.Management.Automation.Subsystem.Prediction { /// /// The class represents the prediction result from a predictor. @@ -59,9 +58,9 @@ public static class CommandPrediction /// The object from parsing the current command line input. /// The objects from parsing the current command line input. /// A list of objects. - public static Task?> PredictInput(string client, Ast ast, Token[] astTokens) + public static Task?> PredictInputAsync(PredictionClient client, Ast ast, Token[] astTokens) { - return PredictInput(client, ast, astTokens, millisecondsTimeout: 20); + return PredictInputAsync(client, ast, astTokens, millisecondsTimeout: 20); } /// @@ -72,9 +71,9 @@ public static class CommandPrediction /// The objects from parsing the current command line input. /// The milliseconds to timeout. /// A list of objects. - public static async Task?> PredictInput(string client, Ast ast, Token[] astTokens, int millisecondsTimeout) + public static async Task?> PredictInputAsync(PredictionClient client, Ast ast, Token[] astTokens, int millisecondsTimeout) { - Requires.Condition(millisecondsTimeout > 0, nameof(millisecondsTimeout)); + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(millisecondsTimeout); var predictors = SubsystemManager.GetSubsystems(); if (predictors.Count == 0) @@ -86,17 +85,13 @@ public static class CommandPrediction var tasks = new Task[predictors.Count]; using var cancellationSource = new CancellationTokenSource(); + Func callBack = GetCallBack(client, context, cancellationSource); + for (int i = 0; i < predictors.Count; i++) { ICommandPredictor predictor = predictors[i]; - tasks[i] = Task.Factory.StartNew( - state => - { - var predictor = (ICommandPredictor)state!; - SuggestionPackage pkg = predictor.GetSuggestion(client, context, cancellationSource.Token); - return pkg.SuggestionEntries?.Count > 0 ? new PredictionResult(predictor.Id, predictor.Name, pkg.Session, pkg.SuggestionEntries) : null; - }, + callBack, predictor, cancellationSource.Token, TaskCreationOptions.DenyChildAttach, @@ -122,6 +117,21 @@ await Task.WhenAny( } return resultList; + + // A local helper function to avoid creating an instance of the generated delegate helper class + // when no predictor is registered. + static Func GetCallBack( + PredictionClient client, + PredictionContext context, + CancellationTokenSource cancellationSource) + { + return state => + { + var predictor = (ICommandPredictor)state!; + SuggestionPackage pkg = predictor.GetSuggestion(client, context, cancellationSource.Token); + return pkg.SuggestionEntries?.Count > 0 ? new PredictionResult(predictor.Id, predictor.Name, pkg.Session, pkg.SuggestionEntries) : null; + }; + } } /// @@ -129,26 +139,64 @@ await Task.WhenAny( /// /// Represents the client that initiates the call. /// History command lines provided as references for prediction. - public static void OnCommandLineAccepted(string client, IReadOnlyList history) + public static void OnCommandLineAccepted(PredictionClient client, IReadOnlyList history) { - Requires.NotNull(history, nameof(history)); + ArgumentNullException.ThrowIfNull(history); + + var predictors = SubsystemManager.GetSubsystems(); + if (predictors.Count == 0) + { + return; + } + + Action? callBack = null; + foreach (ICommandPredictor predictor in predictors) + { + if (predictor.CanAcceptFeedback(client, PredictorFeedbackKind.CommandLineAccepted)) + { + callBack ??= GetCallBack(client, history); + ThreadPool.QueueUserWorkItem(callBack, predictor, preferLocal: false); + } + } + + // A local helper function to avoid creating an instance of the generated delegate helper class + // when no predictor is registered, or no registered predictor accepts this feedback. + static Action GetCallBack(PredictionClient client, IReadOnlyList history) + { + return predictor => predictor.OnCommandLineAccepted(client, history); + } + } + /// + /// Allow registered predictors to know the execution result (success/failure) of the last accepted command line. + /// + /// Represents the client that initiates the call. + /// The last accepted command line. + /// Whether the execution of the last command line was successful. + public static void OnCommandLineExecuted(PredictionClient client, string commandLine, bool success) + { var predictors = SubsystemManager.GetSubsystems(); if (predictors.Count == 0) { return; } + Action? callBack = null; foreach (ICommandPredictor predictor in predictors) { - if (predictor.SupportEarlyProcessing) + if (predictor.CanAcceptFeedback(client, PredictorFeedbackKind.CommandLineExecuted)) { - ThreadPool.QueueUserWorkItem( - state => state.StartEarlyProcessing(client, history), - predictor, - preferLocal: false); + callBack ??= GetCallBack(client, commandLine, success); + ThreadPool.QueueUserWorkItem(callBack, predictor, preferLocal: false); } } + + // A local helper function to avoid creating an instance of the generated delegate helper class + // when no predictor is registered, or no registered predictor accepts this feedback. + static Action GetCallBack(PredictionClient client, string commandLine, bool success) + { + return predictor => predictor.OnCommandLineExecuted(client, commandLine, success); + } } /// @@ -161,7 +209,7 @@ public static void OnCommandLineAccepted(string client, IReadOnlyList hi /// When the value is greater than 0, it's the number of displayed suggestions from the list returned in , starting from the index 0. /// When the value is less than or equal to 0, it means a single suggestion from the list got displayed, and the index is the absolute value. /// - public static void OnSuggestionDisplayed(string client, Guid predictorId, uint session, int countOrIndex) + public static void OnSuggestionDisplayed(PredictionClient client, Guid predictorId, uint session, int countOrIndex) { var predictors = SubsystemManager.GetSubsystems(); if (predictors.Count == 0) @@ -171,14 +219,24 @@ public static void OnSuggestionDisplayed(string client, Guid predictorId, uint s foreach (ICommandPredictor predictor in predictors) { - if (predictor.AcceptFeedback && predictor.Id == predictorId) + if (predictor.Id == predictorId) { - ThreadPool.QueueUserWorkItem( - state => state.OnSuggestionDisplayed(client, session, countOrIndex), - predictor, - preferLocal: false); + if (predictor.CanAcceptFeedback(client, PredictorFeedbackKind.SuggestionDisplayed)) + { + Action callBack = GetCallBack(client, session, countOrIndex); + ThreadPool.QueueUserWorkItem(callBack, predictor, preferLocal: false); + } + + break; } } + + // A local helper function to avoid creating an instance of the generated delegate helper class + // when no predictor is registered, or no registered predictor accepts this feedback. + static Action GetCallBack(PredictionClient client, uint session, int countOrIndex) + { + return predictor => predictor.OnSuggestionDisplayed(client, session, countOrIndex); + } } /// @@ -188,9 +246,9 @@ public static void OnSuggestionDisplayed(string client, Guid predictorId, uint s /// The identifier of the predictor whose prediction result was accepted. /// The mini-session where the accepted suggestion came from. /// The accepted suggestion text. - public static void OnSuggestionAccepted(string client, Guid predictorId, uint session, string suggestionText) + public static void OnSuggestionAccepted(PredictionClient client, Guid predictorId, uint session, string suggestionText) { - Requires.NotNullOrEmpty(suggestionText, nameof(suggestionText)); + ArgumentException.ThrowIfNullOrEmpty(suggestionText); var predictors = SubsystemManager.GetSubsystems(); if (predictors.Count == 0) @@ -200,14 +258,24 @@ public static void OnSuggestionAccepted(string client, Guid predictorId, uint se foreach (ICommandPredictor predictor in predictors) { - if (predictor.AcceptFeedback && predictor.Id == predictorId) + if (predictor.Id == predictorId) { - ThreadPool.QueueUserWorkItem( - state => state.OnSuggestionAccepted(client, session, suggestionText), - predictor, - preferLocal: false); + if (predictor.CanAcceptFeedback(client, PredictorFeedbackKind.SuggestionAccepted)) + { + Action callBack = GetCallBack(client, session, suggestionText); + ThreadPool.QueueUserWorkItem(callBack, predictor, preferLocal: false); + } + + break; } } + + // A local helper function to avoid creating an instance of the generated delegate helper class + // when no predictor is registered, or no registered predictor accepts this feedback. + static Action GetCallBack(PredictionClient client, uint session, string suggestionText) + { + return predictor => predictor.OnSuggestionAccepted(client, session, suggestionText); + } } } } diff --git a/src/System.Management.Automation/engine/Subsystem/CommandPrediction/ICommandPredictor.cs b/src/System.Management.Automation/engine/Subsystem/PredictionSubsystem/ICommandPredictor.cs similarity index 66% rename from src/System.Management.Automation/engine/Subsystem/CommandPrediction/ICommandPredictor.cs rename to src/System.Management.Automation/engine/Subsystem/PredictionSubsystem/ICommandPredictor.cs index d55d37a90df..275dc1733b6 100644 --- a/src/System.Management.Automation/engine/Subsystem/CommandPrediction/ICommandPredictor.cs +++ b/src/System.Management.Automation/engine/Subsystem/PredictionSubsystem/ICommandPredictor.cs @@ -9,7 +9,7 @@ using System.Management.Automation.Language; using System.Threading; -namespace System.Management.Automation.Subsystem +namespace System.Management.Automation.Subsystem.Prediction { /// /// Interface for implementing a predictor plugin. @@ -22,61 +22,137 @@ public interface ICommandPredictor : ISubsystem Dictionary? ISubsystem.FunctionsToDefine => null; /// - /// Default implementation for `ISubsystem.Kind`. + /// Get the predictive suggestions. It indicates the start of a suggestion rendering session. /// - SubsystemKind ISubsystem.Kind => SubsystemKind.CommandPredictor; + /// Represents the client that initiates the call. + /// The object to be used for prediction. + /// The cancellation token to cancel the prediction. + /// An instance of . + SuggestionPackage GetSuggestion(PredictionClient client, PredictionContext context, CancellationToken cancellationToken); /// - /// Gets a value indicating whether the predictor supports early processing. + /// Gets a value indicating whether the predictor accepts a specific kind of feedback. /// - bool SupportEarlyProcessing { get; } + /// Represents the client that initiates the call. + /// A specific type of feedback. + /// True or false, to indicate whether the specific feedback is accepted. + bool CanAcceptFeedback(PredictionClient client, PredictorFeedbackKind feedback) => false; /// - /// Gets a value indicating whether the predictor accepts feedback about the previous suggestion. + /// One or more suggestions provided by the predictor were displayed to the user. /// - bool AcceptFeedback { get; } + /// Represents the client that initiates the call. + /// The mini-session where the displayed suggestions came from. + /// + /// When the value is greater than 0, it's the number of displayed suggestions from the list returned in , starting from the index 0. + /// When the value is less than or equal to 0, it means a single suggestion from the list got displayed, and the index is the absolute value. + /// + void OnSuggestionDisplayed(PredictionClient client, uint session, int countOrIndex) { } + + /// + /// The suggestion provided by the predictor was accepted. + /// + /// Represents the client that initiates the call. + /// Represents the mini-session where the accepted suggestion came from. + /// The accepted suggestion text. + void OnSuggestionAccepted(PredictionClient client, uint session, string acceptedSuggestion) { } /// /// A command line was accepted to execute. /// The predictor can start processing early as needed with the latest history. /// - /// Represents the client that initiates the call. + /// Represents the client that initiates the call. /// History command lines provided as references for prediction. - void StartEarlyProcessing(string clientId, IReadOnlyList history); + void OnCommandLineAccepted(PredictionClient client, IReadOnlyList history) { } /// - /// Get the predictive suggestions. It indicates the start of a suggestion rendering session. + /// A command line was done execution. /// - /// Represents the client that initiates the call. - /// The object to be used for prediction. - /// The cancellation token to cancel the prediction. - /// An instance of . - SuggestionPackage GetSuggestion(string clientId, PredictionContext context, CancellationToken cancellationToken); + /// Represents the client that initiates the call. + /// The last accepted command line. + /// Shows whether the execution was successful. + void OnCommandLineExecuted(PredictionClient client, string commandLine, bool success) { } + } + /// + /// Kinds of feedback a predictor can choose to accept. + /// + public enum PredictorFeedbackKind + { /// - /// One or more suggestions provided by the predictor were displayed to the user. + /// Feedback when one or more suggestions are displayed to the user. /// - /// Represents the client that initiates the call. - /// The mini-session where the displayed suggestions came from. - /// - /// When the value is greater than 0, it's the number of displayed suggestions from the list returned in , starting from the index 0. - /// When the value is less than or equal to 0, it means a single suggestion from the list got displayed, and the index is the absolute value. - /// - void OnSuggestionDisplayed(string clientId, uint session, int countOrIndex); + SuggestionDisplayed, /// - /// The suggestion provided by the predictor was accepted. + /// Feedback when a suggestion is accepted by the user. /// - /// Represents the client that initiates the call. - /// Represents the mini-session where the accepted suggestion came from. - /// The accepted suggestion text. - void OnSuggestionAccepted(string clientId, uint session, string acceptedSuggestion); + SuggestionAccepted, + + /// + /// Feedback when a command line is accepted by the user. + /// + CommandLineAccepted, + + /// + /// Feedback when the accepted command line finishes its execution. + /// + CommandLineExecuted, + } + + /// + /// Kinds of prediction clients. + /// + public enum PredictionClientKind + { + /// + /// A terminal client, representing the command-line experience. + /// + Terminal, + + /// + /// An editor client, representing the editor experience. + /// + Editor, + } + + /// + /// The class represents a client that interacts with predictors. + /// + public sealed class PredictionClient + { + /// + /// Gets the client name. + /// + public string Name { get; } + + /// + /// Gets the client kind. + /// + public PredictionClientKind Kind { get; } + + /// + /// Gets the current location of the default session. + /// It returns null if there is no default Runspace or if the default is a remote Runspace. + /// + public PathInfo? CurrentLocation { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// Name of the interactive client. + /// Kind of the interactive client. + public PredictionClient(string name, PredictionClientKind kind) + { + Name = name; + Kind = kind; + } } /// /// Context information about the user input. /// - public class PredictionContext + public sealed class PredictionContext { /// /// Gets the abstract syntax tree (AST) generated from parsing the user input. @@ -111,8 +187,8 @@ public class PredictionContext /// The objects from parsing the current command line input. public PredictionContext(Ast inputAst, Token[] inputTokens) { - Requires.NotNull(inputAst, nameof(inputAst)); - Requires.NotNull(inputTokens, nameof(inputTokens)); + ArgumentNullException.ThrowIfNull(inputAst); + ArgumentNullException.ThrowIfNull(inputTokens); var cursor = inputAst.Extent.EndScriptPosition; var astContext = CompletionAnalysis.ExtractAstContext(inputAst, inputTokens, cursor); @@ -131,7 +207,7 @@ public PredictionContext(Ast inputAst, Token[] inputTokens) /// A object. public static PredictionContext Create(string input) { - Requires.NotNullOrEmpty(input, nameof(input)); + ArgumentException.ThrowIfNullOrEmpty(input); Ast ast = Parser.ParseInput(input, out Token[] tokens, out _); return new PredictionContext(ast, tokens); @@ -169,7 +245,7 @@ public PredictiveSuggestion(string suggestion) /// The tooltip of the suggestion. public PredictiveSuggestion(string suggestion, string? toolTip) { - Requires.NotNullOrEmpty(suggestion, nameof(suggestion)); + ArgumentException.ThrowIfNullOrEmpty(suggestion); SuggestionText = suggestion; ToolTip = toolTip; diff --git a/src/System.Management.Automation/engine/Subsystem/SubsystemInfo.cs b/src/System.Management.Automation/engine/Subsystem/SubsystemInfo.cs index 24adf990d9e..8756fd69c9b 100644 --- a/src/System.Management.Automation/engine/Subsystem/SubsystemInfo.cs +++ b/src/System.Management.Automation/engine/Subsystem/SubsystemInfo.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation.Internal; +using Microsoft.PowerShell.Telemetry; namespace System.Management.Automation.Subsystem { @@ -18,22 +19,22 @@ public abstract class SubsystemInfo #region "Metadata of a Subsystem (public)" /// - /// The kind of a concrete subsystem. + /// Gets the kind of a concrete subsystem. /// public SubsystemKind Kind { get; } /// - /// The type of a concrete subsystem. + /// Gets the type of a concrete subsystem. /// public Type SubsystemType { get; } /// - /// Indicate whether the subsystem allows to unregister an implementation. + /// Gets a value indicating whether the subsystem allows to unregister an implementation. /// public bool AllowUnregistration { get; private set; } /// - /// Indicate whether the subsystem allows to have multiple implementations registered. + /// Gets a value indicating whether the subsystem allows to have multiple implementations registered. /// public bool AllowMultipleRegistration { get; private set; } @@ -96,6 +97,7 @@ private protected SubsystemInfo(SubsystemKind kind, Type subsystemType) internal void RegisterImplementation(ISubsystem impl) { AddImplementation(impl); + ApplicationInsightsTelemetry.SendUseTelemetry(ApplicationInsightsTelemetry.s_subsystemRegistration, impl.Name); } internal ISubsystem UnregisterImplementation(Guid id) @@ -159,10 +161,10 @@ internal static SubsystemInfo Create( /// public class ImplementationInfo { - internal ImplementationInfo(ISubsystem implementation) + internal ImplementationInfo(SubsystemKind kind, ISubsystem implementation) { Id = implementation.Id; - Kind = implementation.Kind; + Kind = kind; Name = implementation.Name; Description = implementation.Description; ImplementationType = implementation.GetType(); @@ -217,6 +219,7 @@ internal SubsystemInfoImpl(SubsystemKind kind) /// In the subsystem scenario, registration operations will be minimum, and in most cases, the registered /// implementation will never be unregistered, so optimization for reading is more important. /// + /// The subsystem implementation to be added. private protected override void AddImplementation(ISubsystem rawImpl) { lock (_syncObj) @@ -226,7 +229,7 @@ private protected override void AddImplementation(ISubsystem rawImpl) if (_registeredImpls.Count == 0) { _registeredImpls = new ReadOnlyCollection(new[] { impl }); - _cachedImplInfos = new ReadOnlyCollection(new[] { new ImplementationInfo(impl) }); + _cachedImplInfos = new ReadOnlyCollection(new[] { new ImplementationInfo(Kind, impl) }); return; } @@ -250,12 +253,17 @@ private protected override void AddImplementation(ISubsystem rawImpl) } } - var list = new List(_registeredImpls.Count + 1); - list.AddRange(_registeredImpls); - list.Add(impl); + int newCapacity = _registeredImpls.Count + 1; + var implList = new List(newCapacity); + implList.AddRange(_registeredImpls); + implList.Add(impl); - _registeredImpls = new ReadOnlyCollection(list); - _cachedImplInfos = new ReadOnlyCollection(list.ConvertAll(s => new ImplementationInfo(s))); + var implInfo = new List(newCapacity); + implInfo.AddRange(_cachedImplInfos); + implInfo.Add(new ImplementationInfo(Kind, impl)); + + _registeredImpls = new ReadOnlyCollection(implList); + _cachedImplInfos = new ReadOnlyCollection(implInfo); } } @@ -268,6 +276,8 @@ private protected override void AddImplementation(ISubsystem rawImpl) /// In the subsystem scenario, registration operations will be minimum, and in most cases, the registered /// implementation will never be unregistered, so optimization for reading is more important. /// + /// The id of the subsystem implementation to be removed. + /// The subsystem implementation that was removed. private protected override ISubsystem RemoveImplementation(Guid id) { if (!AllowUnregistration) @@ -314,7 +324,10 @@ private protected override ISubsystem RemoveImplementation(Guid id) } else { - var list = new List(_registeredImpls.Count - 1); + int newCapacity = _registeredImpls.Count - 1; + var implList = new List(newCapacity); + var implInfo = new List(newCapacity); + for (int i = 0; i < _registeredImpls.Count; i++) { if (index == i) @@ -322,11 +335,12 @@ private protected override ISubsystem RemoveImplementation(Guid id) continue; } - list.Add(_registeredImpls[i]); + implList.Add(_registeredImpls[i]); + implInfo.Add(_cachedImplInfos[i]); } - _registeredImpls = new ReadOnlyCollection(list); - _cachedImplInfos = new ReadOnlyCollection(list.ConvertAll(s => new ImplementationInfo(s))); + _registeredImpls = new ReadOnlyCollection(implList); + _cachedImplInfos = new ReadOnlyCollection(implInfo); } return target; diff --git a/src/System.Management.Automation/engine/Subsystem/SubsystemManager.cs b/src/System.Management.Automation/engine/Subsystem/SubsystemManager.cs index 81a27f61279..e389040899e 100644 --- a/src/System.Management.Automation/engine/Subsystem/SubsystemManager.cs +++ b/src/System.Management.Automation/engine/Subsystem/SubsystemManager.cs @@ -6,8 +6,10 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; using System.Management.Automation.Internal; +using System.Management.Automation.Subsystem.DSC; +using System.Management.Automation.Subsystem.Feedback; +using System.Management.Automation.Subsystem.Prediction; namespace System.Management.Automation.Subsystem { @@ -28,6 +30,16 @@ static SubsystemManager() SubsystemKind.CommandPredictor, allowUnregistration: true, allowMultipleRegistration: true), + + SubsystemInfo.Create( + SubsystemKind.CrossPlatformDsc, + allowUnregistration: true, + allowMultipleRegistration: false), + + SubsystemInfo.Create( + SubsystemKind.FeedbackProvider, + allowUnregistration: true, + allowMultipleRegistration: true), }; var subSystemTypeMap = new Dictionary(subsystems.Length); @@ -42,6 +54,9 @@ static SubsystemManager() s_subsystems = new ReadOnlyCollection(subsystems); s_subSystemTypeMap = new ReadOnlyDictionary(subSystemTypeMap); s_subSystemKindMap = new ReadOnlyDictionary(subSystemKindMap); + + // Register built-in suggestion providers. + RegisterSubsystem(SubsystemKind.FeedbackProvider, new GeneralCommandErrorFeedback()); } #region internal - Retrieve subsystem proxy object @@ -52,14 +67,14 @@ static SubsystemManager() /// /// /// Design point: - /// The implemnentation proxy object is not supposed to expose to users. + /// The implementation proxy object is not supposed to expose to users. /// Users shouldn't depend on a implementation proxy object directly, but instead should depend on PowerShell APIs. /// /// Example: if a user want to use prediction functionality, he/she should use the PowerShell prediction API instead of /// directly interacting with the implementation proxy object of `IPrediction`. /// /// The concrete subsystem base type. - /// The most recently registered implmentation object of the concrete subsystem. + /// The most recently registered implementation object of the concrete subsystem. internal static TConcreteSubsystem? GetSubsystem() where TConcreteSubsystem : class, ISubsystem { @@ -80,7 +95,7 @@ static SubsystemManager() /// Return an empty collection when the given subsystem is not registered. /// /// The concrete subsystem base type. - /// A readonly collection of all implmentation objects registered for the concrete subsystem. + /// A readonly collection of all implementation objects registered for the concrete subsystem. internal static ReadOnlyCollection GetSubsystems() where TConcreteSubsystem : class, ISubsystem { @@ -116,7 +131,7 @@ public static ReadOnlyCollection GetAllSubsystemInfo() /// The object that represents the concrete subsystem. public static SubsystemInfo GetSubsystemInfo(Type subsystemType) { - Requires.NotNull(subsystemType, nameof(subsystemType)); + ArgumentNullException.ThrowIfNull(subsystemType); if (s_subSystemTypeMap.TryGetValue(subsystemType, out SubsystemInfo? subsystemInfo)) { @@ -124,9 +139,12 @@ public static SubsystemInfo GetSubsystemInfo(Type subsystemType) } throw new ArgumentException( - StringUtil.Format( - SubsystemStrings.SubsystemTypeUnknown, - subsystemType.FullName)); + subsystemType == typeof(ISubsystem) + ? SubsystemStrings.MustUseConcreteSubsystemType + : StringUtil.Format( + SubsystemStrings.SubsystemTypeUnknown, + subsystemType.FullName), + nameof(subsystemType)); } /// @@ -144,7 +162,8 @@ public static SubsystemInfo GetSubsystemInfo(SubsystemKind kind) throw new ArgumentException( StringUtil.Format( SubsystemStrings.SubsystemKindUnknown, - kind.ToString())); + kind.ToString()), + nameof(kind)); } #endregion @@ -161,7 +180,7 @@ public static void RegisterSubsystem(TImple where TConcreteSubsystem : class, ISubsystem where TImplementation : class, TConcreteSubsystem { - Requires.NotNull(proxy, nameof(proxy)); + ArgumentNullException.ThrowIfNull(proxy); RegisterSubsystem(GetSubsystemInfo(typeof(TConcreteSubsystem)), proxy); } @@ -173,19 +192,20 @@ public static void RegisterSubsystem(TImple /// An instance of the implementation. public static void RegisterSubsystem(SubsystemKind kind, ISubsystem proxy) { - Requires.NotNull(proxy, nameof(proxy)); + ArgumentNullException.ThrowIfNull(proxy); - if (kind != proxy.Kind) + SubsystemInfo info = GetSubsystemInfo(kind); + if (!info.SubsystemType.IsAssignableFrom(proxy.GetType())) { throw new ArgumentException( StringUtil.Format( - SubsystemStrings.ImplementationMismatch, - proxy.Kind.ToString(), - kind.ToString()), + SubsystemStrings.ConcreteSubsystemNotImplemented, + kind.ToString(), + info.SubsystemType.Name), nameof(proxy)); } - RegisterSubsystem(GetSubsystemInfo(kind), proxy); + RegisterSubsystem(info, proxy); } private static void RegisterSubsystem(SubsystemInfo subsystemInfo, ISubsystem proxy) @@ -273,7 +293,14 @@ private static void UnregisterSubsystem(SubsystemInfo subsystemInfo, Guid id) ISubsystem impl = subsystemInfo.UnregisterImplementation(id); if (impl is IDisposable disposable) { - disposable.Dispose(); + try + { + disposable.Dispose(); + } + catch + { + // It's OK to ignore all exceptions when disposing the object. + } } } diff --git a/src/System.Management.Automation/engine/ThirdPartyAdapter.cs b/src/System.Management.Automation/engine/ThirdPartyAdapter.cs index bde0c0f3483..d77e700e90e 100644 --- a/src/System.Management.Automation/engine/ThirdPartyAdapter.cs +++ b/src/System.Management.Automation/engine/ThirdPartyAdapter.cs @@ -296,10 +296,7 @@ public abstract class PSPropertyAdapter [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "object")] public virtual Collection GetTypeNameHierarchy(object baseObject) { - if (baseObject == null) - { - throw new ArgumentNullException(nameof(baseObject)); - } + ArgumentNullException.ThrowIfNull(baseObject); Collection types = new Collection(); diff --git a/src/System.Management.Automation/engine/TransactedString.cs b/src/System.Management.Automation/engine/TransactedString.cs index 948cf1d9d81..22a0afe9a9a 100644 --- a/src/System.Management.Automation/engine/TransactedString.cs +++ b/src/System.Management.Automation/engine/TransactedString.cs @@ -8,7 +8,7 @@ namespace Microsoft.PowerShell.Commands.Management { /// - /// Represents a a string that can be used in transactions. + /// Represents a string that can be used in transactions. /// public class TransactedString : IEnlistmentNotification { @@ -25,10 +25,10 @@ public TransactedString() : this(string.Empty) /// /// Constructor for the TransactedString class. + /// /// /// The initial value of the transacted string. /// - /// public TransactedString(string value) { _value = new StringBuilder(value); @@ -71,10 +71,10 @@ void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment) /// /// Append text to the transacted string. + /// /// /// The text to append. /// - /// public void Append(string text) { ValidateTransactionOrEnlist(); @@ -91,13 +91,13 @@ public void Append(string text) /// /// Remove text from the transacted string. + /// /// /// The position in the string from which to start removing. /// /// /// The length of text to remove. /// - /// public void Remove(int startIndex, int length) { ValidateTransactionOrEnlist(); diff --git a/src/System.Management.Automation/engine/TransactionManager.cs b/src/System.Management.Automation/engine/TransactionManager.cs index bf86d90e812..2add6f288f5 100644 --- a/src/System.Management.Automation/engine/TransactionManager.cs +++ b/src/System.Management.Automation/engine/TransactionManager.cs @@ -185,10 +185,10 @@ public void Dispose() /// /// Disposes the PSTransaction object, which disposes the /// underlying transaction. + /// /// /// Whether to actually dispose the object. /// - /// public void Dispose(bool disposing) { if (disposing) @@ -237,10 +237,10 @@ public void Dispose() /// /// Disposes the PSTransactionContext object, which resets the /// active PSTransaction. + /// /// /// Whether to actually dispose the object. /// - /// private void Dispose(bool disposing) { if (disposing) @@ -682,10 +682,10 @@ public void Dispose() /// /// Disposes the PSTransactionContext object, which resets the /// active PSTransaction. + /// /// /// Whether to actually dispose the object. /// - /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "baseTransaction", Justification = "baseTransaction should not be disposed since we do not own it - it belongs to the caller")] public void Dispose(bool disposing) { diff --git a/src/System.Management.Automation/engine/TypeMetadata.cs b/src/System.Management.Automation/engine/TypeMetadata.cs index 31a6adeab73..926b07fb15a 100644 --- a/src/System.Management.Automation/engine/TypeMetadata.cs +++ b/src/System.Management.Automation/engine/TypeMetadata.cs @@ -761,6 +761,7 @@ internal bool IsMatchingType(PSTypeName psTypeName) private const string AliasesFormat = @"{0}[Alias({1})]"; private const string ValidateLengthFormat = @"{0}[ValidateLength({1}, {2})]"; private const string ValidateRangeRangeKindFormat = @"{0}[ValidateRange([System.Management.Automation.ValidateRangeKind]::{1})]"; + private const string ValidateRangeEnumFormat = @"{0}[ValidateRange([{3}]::{1}, [{3}]::{2})]"; private const string ValidateRangeFloatFormat = @"{0}[ValidateRange({1:R}, {2:R})]"; private const string ValidateRangeFormat = @"{0}[ValidateRange({1}, {2})]"; private const string ValidatePatternFormat = "{0}[ValidatePattern('{1}')]"; @@ -769,6 +770,7 @@ internal bool IsMatchingType(PSTypeName psTypeName) private const string ValidateSetFormat = @"{0}[ValidateSet({1})]"; private const string ValidateNotNullFormat = @"{0}[ValidateNotNull()]"; private const string ValidateNotNullOrEmptyFormat = @"{0}[ValidateNotNullOrEmpty()]"; + private const string ValidateNotNullOrWhiteSpaceFormat = @"{0}[ValidateNotNullOrWhiteSpace()]"; private const string AllowNullFormat = @"{0}[AllowNull()]"; private const string AllowEmptyStringFormat = @"{0}[AllowEmptyString()]"; private const string AllowEmptyCollectionFormat = @"{0}[AllowEmptyCollection()]"; @@ -931,6 +933,10 @@ private static string GetProxyAttributeData(Attribute attrib, string prefix) { format = ValidateRangeFloatFormat; } + else if (rangeType.IsEnum) + { + format = ValidateRangeEnumFormat; + } else { format = ValidateRangeFormat; @@ -941,7 +947,8 @@ private static string GetProxyAttributeData(Attribute attrib, string prefix) format, prefix, validRangeAttrib.MinRange, - validRangeAttrib.MaxRange); + validRangeAttrib.MaxRange, + rangeType.FullName); return result; } } @@ -976,7 +983,7 @@ private static string GetProxyAttributeData(Attribute attrib, string prefix) /* TODO: Validate Pattern dont support Options in ScriptCmdletText. StringBuilder regexOps = new System.Text.StringBuilder(); string or = string.Empty; - string[] regexOptionEnumValues = Enum.GetNames(typeof(System.Text.RegularExpressions.RegexOptions)); + string[] regexOptionEnumValues = Enum.GetNames(); foreach (string regexOption in regexOptionEnumValues) { @@ -1025,6 +1032,14 @@ private static string GetProxyAttributeData(Attribute attrib, string prefix) return result; } + ValidateNotNullOrWhiteSpaceAttribute notNullWhiteSpaceAttrib = attrib as ValidateNotNullOrWhiteSpaceAttribute; + if (notNullWhiteSpaceAttrib != null) + { + result = string.Format(CultureInfo.InvariantCulture, + ValidateNotNullOrWhiteSpaceFormat, prefix); + return result; + } + ValidateSetAttribute setAttrib = attrib as ValidateSetAttribute; if (setAttrib != null) { diff --git a/src/System.Management.Automation/engine/TypeTable.cs b/src/System.Management.Automation/engine/TypeTable.cs index 0e407d57ac4..bffeb9e0ceb 100644 --- a/src/System.Management.Automation/engine/TypeTable.cs +++ b/src/System.Management.Automation/engine/TypeTable.cs @@ -139,17 +139,17 @@ private void UnknownNode(string node, string expectedNodes) else { _context.AddError(_readerLineInfo.LineNumber, TypesXmlStrings.UnknownNode, _reader.LocalName, expectedNodes); - SkipUntillNodeEnd(_reader.LocalName); + SkipUntilNodeEnd(_reader.LocalName); } } - private void SkipUntillNodeEnd(string nodeName) + private void SkipUntilNodeEnd(string nodeName) { while (_reader.Read()) { if (_reader.IsStartElement() && _reader.LocalName.Equals(nodeName)) { - SkipUntillNodeEnd(nodeName); + SkipUntilNodeEnd(nodeName); } else if ((_reader.NodeType == XmlNodeType.EndElement) && _reader.LocalName.Equals(nodeName)) { @@ -459,7 +459,7 @@ private TypeData Read_Type() { if (m.Name.Equals(TypeTable.DefaultDisplayProperty, StringComparison.OrdinalIgnoreCase)) { - CheckStandardNote(m, typeData, (t, v) => t.DefaultDisplayProperty = v, Converter); + CheckStandardNote(m, typeData, static (t, v) => t.DefaultDisplayProperty = v, Converter); } else if (m.Name.Equals(TypeTable.DefaultDisplayPropertySet, StringComparison.OrdinalIgnoreCase)) { @@ -477,11 +477,11 @@ private TypeData Read_Type() } else if (m.Name.Equals(TypeTable.SerializationMethodNode, StringComparison.OrdinalIgnoreCase)) { - CheckStandardNote(m, typeData, (t, v) => t.SerializationMethod = v, Converter); + CheckStandardNote(m, typeData, static (t, v) => t.SerializationMethod = v, Converter); } else if (m.Name.Equals(TypeTable.SerializationDepth, StringComparison.OrdinalIgnoreCase)) { - CheckStandardNote(m, typeData, (t, v) => t.SerializationDepth = v, Converter); + CheckStandardNote(m, typeData, static (t, v) => t.SerializationDepth = v, Converter); } else if (m.Name.Equals(TypeTable.StringSerializationSource, StringComparison.OrdinalIgnoreCase)) { @@ -504,11 +504,11 @@ private TypeData Read_Type() } else if (m.Name.Equals(TypeTable.InheritPropertySerializationSet, StringComparison.OrdinalIgnoreCase)) { - CheckStandardNote(m, typeData, (t, v) => t.InheritPropertySerializationSet = v, BoolConverter); + CheckStandardNote(m, typeData, static (t, v) => t.InheritPropertySerializationSet = v, BoolConverter); } else if (m.Name.Equals(TypeTable.TargetTypeForDeserialization, StringComparison.OrdinalIgnoreCase)) { - CheckStandardNote(m, typeData, (t, v) => t.TargetTypeForDeserialization = v, Converter); + CheckStandardNote(m, typeData, static (t, v) => t.TargetTypeForDeserialization = v, Converter); } else { @@ -771,10 +771,7 @@ private MemberSetData Read_MemberSet() } // Somewhat pointlessly (backcompat), we allow a missing Member node - if (members == null) - { - members = new Collection(); - } + members ??= new Collection(); if (_context.errors.Count != errorCount) { @@ -841,10 +838,7 @@ private PropertySetData Read_PropertySet() { if ((object)_reader.LocalName == (object)_idName) { - if (referencedProperties == null) - { - referencedProperties = new List(8); - } + referencedProperties ??= new List(8); referencedProperties.Add(ReadElementString(_idName)); } @@ -1683,7 +1677,7 @@ public ConsolidatedString(IEnumerable strings) internal static readonly IEqualityComparer EqualityComparer = new ConsolidatedStringEqualityComparer(); - private class ConsolidatedStringEqualityComparer : IEqualityComparer + private sealed class ConsolidatedStringEqualityComparer : IEqualityComparer { bool IEqualityComparer.Equals(ConsolidatedString x, ConsolidatedString y) { @@ -1747,9 +1741,8 @@ internal void AddError(string typeName, int errorLineNumber, string resourceStri /// /// This exception is used by TypeTable constructor to indicate errors - /// occured during construction time. + /// occurred during construction time. /// - [Serializable] public class TypeTableLoadException : RuntimeException { private readonly Collection _errors; @@ -1796,7 +1789,7 @@ public TypeTableLoadException(string message, Exception innerException) /// time. /// /// - /// The errors that occured + /// The errors that occurred /// internal TypeTableLoadException(ConcurrentBag loadErrors) : base(TypesXmlStrings.TypeTableLoadErrors) @@ -1811,56 +1804,14 @@ internal TypeTableLoadException(ConcurrentBag loadErrors) /// /// /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected TypeTableLoadException(SerializationInfo info, StreamingContext context) - : base(info, context) { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - int errorCount = info.GetInt32("ErrorCount"); - if (errorCount > 0) - { - _errors = new Collection(); - for (int index = 0; index < errorCount; index++) - { - string key = string.Format(CultureInfo.InvariantCulture, "Error{0}", index); - _errors.Add(info.GetString(key)); - } - } + throw new NotSupportedException(); } #endregion Constructors - /// - /// Serializes the exception data. - /// - /// Serialization information. - /// Streaming context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - - // If there are simple fields, serialize them with info.AddValue - if (_errors != null) - { - int errorCount = _errors.Count; - info.AddValue("ErrorCount", errorCount); - - for (int index = 0; index < errorCount; index++) - { - string key = string.Format(CultureInfo.InvariantCulture, "Error{0}", index); - info.AddValue(key, _errors[index]); - } - } - } - /// /// Set the default ErrorRecord. /// @@ -3042,7 +2993,7 @@ private static bool GetCheckMemberType(ConcurrentBag errors, string type /// /// Issue appropriate errors and remove members as necessary if: /// - The serialization settings do not fall into one of the combinations of the table below - /// - If the serialization settings notes' values cannot be converted to the propper type + /// - If the serialization settings notes' values cannot be converted to the proper type /// - If serialization settings members are of the wrong member type /// - DefaultDisplayPropertySet is not an PSPropertySet /// - DefaultDisplayProperty is not an PSPropertyInfo @@ -3724,10 +3675,7 @@ private void ProcessTypeDataToAdd(ConcurrentBag errors, TypeData typeDat if (hasStandardMembers) { - if (typeMembers == null) - { - typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); - } + typeMembers ??= _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); ProcessStandardMembers(errors, typeName, typeData.StandardMembers, propertySets, typeMembers, typeData.IsOverride); } @@ -3937,8 +3885,7 @@ internal TypeTable(IEnumerable typeFiles, AuthorizationManager authoriza throw PSTraceSource.NewArgumentException("typeFile", TypesXmlStrings.TypeFileNotRooted, typefile); } - bool unused; - Initialize(string.Empty, typefile, errors, authorizationManager, host, out unused); + Initialize(string.Empty, typefile, errors, authorizationManager, host, out _); _typeFileList.Add(typefile); } @@ -4151,7 +4098,7 @@ internal PSObject.AdapterSet GetTypeAdapter(Type type) #endif } - private TypeMemberData GetTypeMemberDataFromPSMemberInfo(PSMemberInfo member) + private static TypeMemberData GetTypeMemberDataFromPSMemberInfo(PSMemberInfo member) { var note = member as PSNoteProperty; if (note != null) @@ -4218,7 +4165,7 @@ private TypeMemberData GetTypeMemberDataFromPSMemberInfo(PSMemberInfo member) /// /// /// - private void LoadMembersToTypeData(PSMemberInfo member, TypeData typeData) + private static void LoadMembersToTypeData(PSMemberInfo member, TypeData typeData) { Dbg.Assert(member != null, "caller should guarantee that member is not null"); Dbg.Assert(typeData != null, "caller should guarantee that typeData is not null"); @@ -4254,7 +4201,7 @@ private static T GetParameterType(object sourceValue) /// /// Load the standard members into the passed-in TypeData. /// - private void LoadStandardMembersToTypeData(PSMemberSet memberSet, TypeData typeData) + private static void LoadStandardMembersToTypeData(PSMemberSet memberSet, TypeData typeData) { foreach (PSMemberInfo member in memberSet.InternalMembers) { @@ -4742,15 +4689,9 @@ internal void Update( PSHost host, out bool failToLoadFile) { - if (filePath == null) - { - throw new ArgumentNullException(nameof(filePath)); - } + ArgumentNullException.ThrowIfNull(filePath); - if (errors == null) - { - throw new ArgumentNullException(nameof(errors)); - } + ArgumentNullException.ThrowIfNull(errors); if (isShared) { @@ -4820,10 +4761,9 @@ internal void Update( ConcurrentBag errors, bool isRemove) { - if (type == null) - throw new ArgumentNullException(nameof(type)); - if (errors == null) - throw new ArgumentNullException(nameof(errors)); + ArgumentNullException.ThrowIfNull(type); + + ArgumentNullException.ThrowIfNull(errors); if (isShared) { diff --git a/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs b/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs index 5a7133b7cd3..c1a134cdfbf 100644 --- a/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs +++ b/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs @@ -17,10 +17,7 @@ public sealed partial class TypeTable private static Func> GetValueFactoryBasedOnInitCapacity(int capacity) { - if (capacity <= 0) - { - throw new ArgumentOutOfRangeException(nameof(capacity)); - } + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(capacity); if (capacity > ValueFactoryCacheCount) { @@ -639,7 +636,7 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag errors) #region System.IO.DirectoryInfo typeName = @"System.IO.DirectoryInfo"; - typeMembers = _extendedMembers.GetOrAdd(typeName, key => new PSMemberInfoInternalCollection(capacity: 9)); + typeMembers = _extendedMembers.GetOrAdd(typeName, static key => new PSMemberInfoInternalCollection(capacity: 9)); // Process regular members. newMembers.Add(@"Mode"); @@ -676,17 +673,25 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag errors) typeMembers, isOverride: false); - newMembers.Add(@"Target"); + newMembers.Add(@"ResolvedTarget"); AddMember( errors, typeName, new PSCodeProperty( - @"Target", - GetMethodInfo(typeof(Microsoft.PowerShell.Commands.InternalSymbolicLinkLinkCodeMethods), @"GetTarget"), + @"ResolvedTarget", + GetMethodInfo(typeof(Microsoft.PowerShell.Commands.InternalSymbolicLinkLinkCodeMethods), @"ResolvedTarget"), setterCodeReference: null), typeMembers, isOverride: false); + newMembers.Add(@"Target"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"Target", @"LinkTarget", conversionType: null), + typeMembers, + isOverride: false); + newMembers.Add(@"LinkType"); AddMember( errors, @@ -755,7 +760,7 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag errors) #region System.IO.FileInfo typeName = @"System.IO.FileInfo"; - typeMembers = _extendedMembers.GetOrAdd(typeName, key => new PSMemberInfoInternalCollection(capacity: 10)); + typeMembers = _extendedMembers.GetOrAdd(typeName, static key => new PSMemberInfoInternalCollection(capacity: 10)); // Process regular members. newMembers.Add(@"Mode"); @@ -804,17 +809,25 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag errors) typeMembers, isOverride: false); - newMembers.Add(@"Target"); + newMembers.Add(@"ResolvedTarget"); AddMember( errors, typeName, new PSCodeProperty( - @"Target", - GetMethodInfo(typeof(Microsoft.PowerShell.Commands.InternalSymbolicLinkLinkCodeMethods), @"GetTarget"), + @"ResolvedTarget", + GetMethodInfo(typeof(Microsoft.PowerShell.Commands.InternalSymbolicLinkLinkCodeMethods), @"ResolvedTarget"), setterCodeReference: null), typeMembers, isOverride: false); + newMembers.Add(@"Target"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"Target", @"LinkTarget", conversionType: null), + typeMembers, + isOverride: false); + newMembers.Add(@"LinkType"); AddMember( errors, @@ -1047,7 +1060,7 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag errors) #region System.Diagnostics.Process typeName = @"System.Diagnostics.Process"; - typeMembers = _extendedMembers.GetOrAdd(typeName, key => new PSMemberInfoInternalCollection(capacity: 19)); + typeMembers = _extendedMembers.GetOrAdd(typeName, static key => new PSMemberInfoInternalCollection(capacity: 19)); // Process regular members. newMembers.Add(@"PSConfiguration"); @@ -1148,7 +1161,8 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag errors) if ($IsWindows) { (Get-CimInstance Win32_Process -Filter ""ProcessId = $($this.Id)"").CommandLine } elseif ($IsLinux) { - Get-Content -LiteralPath ""/proc/$($this.Id)/cmdline"" + $rawCmd = Get-Content -LiteralPath ""/proc/$($this.Id)/cmdline"" + $rawCmd.Substring(0, $rawCmd.Length - 1) -replace ""`0"", "" "" } "), setterScript: null, @@ -4065,152 +4079,6 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag errors) #endregion System.Management.ManagementObject - #region System.Security.AccessControl.ObjectSecurity - - typeName = @"System.Security.AccessControl.ObjectSecurity"; - typeMembers = _extendedMembers.GetOrAdd(typeName, key => new PSMemberInfoInternalCollection(capacity: 7)); - Type securityDescriptorCommandsBaseType = TypeResolver.ResolveType("Microsoft.PowerShell.Commands.SecurityDescriptorCommandsBase", exception: out _); - - // Process regular members. - newMembers.Add(@"Path"); - AddMember( - errors, - typeName, - new PSCodeProperty( - @"Path", - GetMethodInfo(securityDescriptorCommandsBaseType, @"GetPath"), - setterCodeReference: null), - typeMembers, - isOverride: false); - - newMembers.Add(@"Owner"); - AddMember( - errors, - typeName, - new PSCodeProperty( - @"Owner", - GetMethodInfo(securityDescriptorCommandsBaseType, @"GetOwner"), - setterCodeReference: null), - typeMembers, - isOverride: false); - - newMembers.Add(@"Group"); - AddMember( - errors, - typeName, - new PSCodeProperty( - @"Group", - GetMethodInfo(securityDescriptorCommandsBaseType, @"GetGroup"), - setterCodeReference: null), - typeMembers, - isOverride: false); - - newMembers.Add(@"Access"); - AddMember( - errors, - typeName, - new PSCodeProperty( - @"Access", - GetMethodInfo(securityDescriptorCommandsBaseType, @"GetAccess"), - setterCodeReference: null), - typeMembers, - isOverride: false); - - newMembers.Add(@"Sddl"); - AddMember( - errors, - typeName, - new PSCodeProperty( - @"Sddl", - GetMethodInfo(securityDescriptorCommandsBaseType, @"GetSddl"), - setterCodeReference: null), - typeMembers, - isOverride: false); - - newMembers.Add(@"AccessToString"); - AddMember( - errors, - typeName, - new PSScriptProperty( - @"AccessToString", - GetScriptBlock(@"$toString = """"; - $first = $true; - if ( ! $this.Access ) { return """" } - - foreach($ace in $this.Access) - { - if($first) - { - $first = $false; - } - else - { - $tostring += ""`n""; - } - - $toString += $ace.IdentityReference.ToString(); - $toString += "" ""; - $toString += $ace.AccessControlType.ToString(); - $toString += "" ""; - if($ace -is [System.Security.AccessControl.FileSystemAccessRule]) - { - $toString += $ace.FileSystemRights.ToString(); - } - elseif($ace -is [System.Security.AccessControl.RegistryAccessRule]) - { - $toString += $ace.RegistryRights.ToString(); - } - } - - return $toString;"), - setterScript: null, - shouldCloneOnAccess: true), - typeMembers, - isOverride: false); - - newMembers.Add(@"AuditToString"); - AddMember( - errors, - typeName, - new PSScriptProperty( - @"AuditToString", - GetScriptBlock(@"$toString = """"; - $first = $true; - if ( ! (& { Set-StrictMode -Version 1; $this.audit }) ) { return """" } - - foreach($ace in (& { Set-StrictMode -Version 1; $this.audit })) - { - if($first) - { - $first = $false; - } - else - { - $tostring += ""`n""; - } - - $toString += $ace.IdentityReference.ToString(); - $toString += "" ""; - $toString += $ace.AuditFlags.ToString(); - $toString += "" ""; - if($ace -is [System.Security.AccessControl.FileSystemAuditRule]) - { - $toString += $ace.FileSystemRights.ToString(); - } - elseif($ace -is [System.Security.AccessControl.RegistryAuditRule]) - { - $toString += $ace.RegistryRights.ToString(); - } - } - - return $toString;"), - setterScript: null, - shouldCloneOnAccess: true), - typeMembers, - isOverride: false); - - #endregion System.Security.AccessControl.ObjectSecurity - #region Microsoft.PowerShell.Commands.HistoryInfo typeName = @"Microsoft.PowerShell.Commands.HistoryInfo"; @@ -9227,45 +9095,42 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag errors) #if UNIX #region UnixStat - if (ExperimentalFeature.IsEnabled("PSUnixFileStat")) - { - typeName = @"System.IO.FileSystemInfo"; - typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); - - // Where we have a method to invoke below, first check to be sure that the object is present - // to avoid null reference issues - newMembers.Add(@"UnixMode"); - AddMember( - errors, - typeName, - new PSScriptProperty(@"UnixMode", GetScriptBlock(@"if ($this.UnixStat) { $this.UnixStat.GetModeString() }")), - typeMembers, - isOverride: false); - - newMembers.Add(@"User"); - AddMember( - errors, - typeName, - new PSScriptProperty(@"User", GetScriptBlock(@" if ($this.UnixStat) { $this.UnixStat.GetUserName() } ")), - typeMembers, - isOverride: false); - - newMembers.Add(@"Group"); - AddMember( - errors, - typeName, - new PSScriptProperty(@"Group", GetScriptBlock(@" if ($this.UnixStat) { $this.UnixStat.GetGroupName() } ")), - typeMembers, - isOverride: false); - - newMembers.Add(@"Size"); - AddMember( - errors, - typeName, - new PSScriptProperty(@"Size", GetScriptBlock(@"$this.UnixStat.Size")), - typeMembers, - isOverride: false); - } + typeName = @"System.IO.FileSystemInfo"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Where we have a method to invoke below, first check to be sure that the object is present + // to avoid null reference issues + newMembers.Add(@"UnixMode"); + AddMember( + errors, + typeName, + new PSScriptProperty(@"UnixMode", GetScriptBlock(@"if ($this.UnixStat) { $this.UnixStat.GetModeString() }")), + typeMembers, + isOverride: false); + + newMembers.Add(@"User"); + AddMember( + errors, + typeName, + new PSScriptProperty(@"User", GetScriptBlock(@" if ($this.UnixStat) { $this.UnixStat.GetUserName() } ")), + typeMembers, + isOverride: false); + + newMembers.Add(@"Group"); + AddMember( + errors, + typeName, + new PSScriptProperty(@"Group", GetScriptBlock(@" if ($this.UnixStat) { $this.UnixStat.GetGroupName() } ")), + typeMembers, + isOverride: false); + + newMembers.Add(@"Size"); + AddMember( + errors, + typeName, + new PSScriptProperty(@"Size", GetScriptBlock(@"$this.UnixStat.Size")), + typeMembers, + isOverride: false); #endregion #endif diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index 94957ced8b8..2a2091af31a 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -5,21 +5,16 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Management.Automation.Configuration; using System.Management.Automation.Internal; -using System.Management.Automation.Language; using System.Management.Automation.Remoting; -using System.Management.Automation.Runspaces; using System.Management.Automation.Security; using System.Numerics; using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security; #if !UNIX @@ -307,9 +302,9 @@ internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int /// /// Helper fn to check byte[] arg for null. /// - /// arg to check - /// name of the arg - /// Does not return a value. + /// arg to check + /// name of the arg + /// Does not return a value. internal static void CheckKeyArg(byte[] arg, string argName) { if (arg == null) @@ -334,9 +329,9 @@ internal static void CheckKeyArg(byte[] arg, string argName) /// Helper fn to check arg for empty or null. /// Throws ArgumentNullException on either condition. /// - /// arg to check - /// name of the arg - /// Does not return a value. + /// arg to check + /// name of the arg + /// Does not return a value. internal static void CheckArgForNullOrEmpty(string arg, string argName) { if (arg == null) @@ -353,9 +348,9 @@ internal static void CheckArgForNullOrEmpty(string arg, string argName) /// Helper fn to check arg for null. /// Throws ArgumentNullException on either condition. /// - /// arg to check - /// name of the arg - /// Does not return a value. + /// arg to check + /// name of the arg + /// Does not return a value. internal static void CheckArgForNull(object arg, string argName) { if (arg == null) @@ -367,9 +362,9 @@ internal static void CheckArgForNull(object arg, string argName) /// /// Helper fn to check arg for null. /// - /// arg to check - /// name of the arg - /// Does not return a value. + /// arg to check + /// name of the arg + /// Does not return a value. internal static void CheckSecureStringArg(SecureString arg, string argName) { if (arg == null) @@ -378,7 +373,6 @@ internal static void CheckSecureStringArg(SecureString arg, string argName) } } - [ArchitectureSensitive] internal static string GetStringFromSecureString(SecureString ss) { IntPtr p = IntPtr.Zero; @@ -488,9 +482,16 @@ internal static string GetWindowsPowerShellVersionFromRegistry() internal static string GetApplicationBase(string shellId) { - // Use the location of SMA.dll as the application base. - Assembly assembly = typeof(PSObject).Assembly; - return Path.GetDirectoryName(assembly.Location); + // Use the location of SMA.dll as the application base if it exists, + // otherwise, use the base directory from `AppContext`. + var baseDirectory = Path.GetDirectoryName(typeof(PSObject).Assembly.Location); + if (string.IsNullOrEmpty(baseDirectory)) + { + // Need to remove any trailing directory separator characters + baseDirectory = AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar); + } + + return baseDirectory; } private static string[] s_productFolderDirectories; @@ -576,10 +577,7 @@ internal static bool IsWinPEHost() catch (ObjectDisposedException) { } finally { - if (winPEKey != null) - { - winPEKey.Dispose(); - } + winPEKey?.Dispose(); } #endif return false; @@ -645,41 +643,6 @@ internal static Version StringToVersion(string versionString) return null; } - /// - /// Checks whether current monad session supports version specified - /// by ver. - /// - /// Version to check. - /// True if supported, false otherwise. - internal static bool IsPSVersionSupported(string ver) - { - // Convert version to supported format ie., x.x - Version inputVersion = StringToVersion(ver); - return IsPSVersionSupported(inputVersion); - } - - /// - /// Checks whether current monad session supports version specified - /// by checkVersion. - /// - /// Version to check. - /// True if supported, false otherwise. - internal static bool IsPSVersionSupported(Version checkVersion) - { - if (checkVersion == null) - { - return false; - } - - foreach (Version compatibleVersion in PSVersionInfo.PSCompatibleVersions) - { - if (checkVersion.Major == compatibleVersion.Major && checkVersion.Minor <= compatibleVersion.Minor) - return true; - } - - return false; - } - /// /// Checks whether current PowerShell session supports edition specified /// by checkEdition. @@ -688,7 +651,7 @@ internal static bool IsPSVersionSupported(Version checkVersion) /// True if supported, false otherwise. internal static bool IsPSEditionSupported(string checkEdition) { - return PSVersionInfo.PSEdition.Equals(checkEdition, StringComparison.OrdinalIgnoreCase); + return PSVersionInfo.PSEditionValue.Equals(checkEdition, StringComparison.OrdinalIgnoreCase); } /// @@ -698,7 +661,7 @@ internal static bool IsPSEditionSupported(string checkEdition) /// True if the edition is supported by this runtime, false otherwise. internal static bool IsPSEditionSupported(IEnumerable editions) { - string currentPSEdition = PSVersionInfo.PSEdition; + string currentPSEdition = PSVersionInfo.PSEditionValue; foreach (string edition in editions) { if (currentPSEdition.Equals(edition, StringComparison.OrdinalIgnoreCase)) @@ -1069,10 +1032,7 @@ internal static void EnsureModuleLoaded(string module, ExecutionContext context) finally { context.AutoLoadingModuleInProgress.Remove(module); - if (ps != null) - { - ps.Dispose(); - } + ps?.Dispose(); } } } @@ -1129,10 +1089,7 @@ internal static List GetModules(string module, ExecutionContext co } finally { - if (ps != null) - { - ps.Dispose(); - } + ps?.Dispose(); } return result; @@ -1190,10 +1147,7 @@ internal static List GetModules(ModuleSpecification fullyQualified } finally { - if (ps != null) - { - ps.Dispose(); - } + ps?.Dispose(); } return result; @@ -1283,7 +1237,7 @@ internal static bool IsReservedDeviceName(string destinationPath) return false; } - internal static bool PathIsUnc(string path) + internal static bool PathIsUnc(string path, bool networkOnly = false) { #if UNIX return false; @@ -1293,8 +1247,8 @@ internal static bool PathIsUnc(string path) return false; } - // handle special cases like \\wsl$\ubuntu which isn't a UNC path, but we can say it is so the filesystemprovider can use it - if (path.StartsWith(WslRootPath, StringComparison.OrdinalIgnoreCase)) + // handle special cases like '\\wsl$\ubuntu', '\\?\', and '\\.\pipe\' which aren't a UNC path, but we can say it is so the filesystemprovider can use it + if (!networkOnly && (path.StartsWith(WslRootPath, StringComparison.OrdinalIgnoreCase) || PathIsDevicePath(path))) { return true; } @@ -1304,6 +1258,16 @@ internal static bool PathIsUnc(string path) #endif } + internal static bool PathIsDevicePath(string path) + { +#if UNIX + return false; +#else + // device paths can be network paths, we would need windows to parse it. + return path.StartsWith(@"\\.\") || path.StartsWith(@"\\?\") || path.StartsWith(@"\\;"); +#endif + } + internal static readonly string PowerShellAssemblyStrongNameFormat = "{0}, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"; @@ -1397,9 +1361,6 @@ internal static bool Succeeded(int hresult) // Add-Member ScriptProperty Preamble { $this.GetEncoding().GetPreamble() -join "-" } -PassThru | // Format-Table -Auto - internal static readonly UTF8Encoding utf8NoBom = - new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); - #if !UNIX /// /// Queues a CLR worker thread with impersonation of provided Windows identity. @@ -1447,7 +1408,7 @@ private static void WorkItemCallback(object callBackArgs) /// Command name and as appropriate Module name in out parameter. internal static string ParseCommandName(string commandName, out string moduleName) { - var names = commandName.Split(Separators.Backslash, 2); + var names = commandName.Split('\\', 2); if (names.Length == 2) { moduleName = names[0]; @@ -1474,22 +1435,8 @@ internal static class Separators internal static readonly char[] Backslash = new char[] { '\\' }; internal static readonly char[] Directory = new char[] { '\\', '/' }; internal static readonly char[] DirectoryOrDrive = new char[] { '\\', '/', ':' }; - - internal static readonly char[] Colon = new char[] { ':' }; - internal static readonly char[] Dot = new char[] { '.' }; - internal static readonly char[] Pipe = new char[] { '|' }; - internal static readonly char[] Comma = new char[] { ',' }; - internal static readonly char[] Semicolon = new char[] { ';' }; - internal static readonly char[] StarOrQuestion = new char[] { '*', '?' }; - internal static readonly char[] ColonOrBackslash = new char[] { '\\', ':' }; - internal static readonly char[] PathSeparator = new char[] { Path.PathSeparator }; - - internal static readonly char[] QuoteChars = new char[] { '\'', '"' }; - internal static readonly char[] Space = new char[] { ' ' }; - internal static readonly char[] QuotesSpaceOrTab = new char[] { ' ', '\t', '\'', '"' }; internal static readonly char[] SpaceOrTab = new char[] { ' ', '\t' }; - internal static readonly char[] Newline = new char[] { '\n' }; - internal static readonly char[] CrLf = new char[] { '\r', '\n' }; + internal static readonly char[] StarOrQuestion = new char[] { '*', '?' }; // (Copied from System.IO.Path so we can call TrimEnd in the same way that Directory.EnumerateFiles would on the search patterns). // Trim trailing white spaces, tabs etc but don't be aggressive in removing everything that has UnicodeCategory of trailing space. @@ -1528,535 +1475,80 @@ internal static bool IsComObject(object obj) /// NoLanguage -> NoLanguage. /// /// ExecutionContext. - /// Previous language mode or null for no language mode change. - internal static PSLanguageMode? EnforceSystemLockDownLanguageMode(ExecutionContext context) - { - PSLanguageMode? oldMode = null; - - if (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) - { - switch (context.LanguageMode) - { - case PSLanguageMode.FullLanguage: - oldMode = context.LanguageMode; - context.LanguageMode = PSLanguageMode.ConstrainedLanguage; - break; - - case PSLanguageMode.RestrictedLanguage: - oldMode = context.LanguageMode; - context.LanguageMode = PSLanguageMode.NoLanguage; - break; - - case PSLanguageMode.ConstrainedLanguage: - case PSLanguageMode.NoLanguage: - break; - - default: - Diagnostics.Assert(false, "Unexpected PSLanguageMode"); - oldMode = context.LanguageMode; - context.LanguageMode = PSLanguageMode.NoLanguage; - break; - } - } - - return oldMode; - } - - #region Implicit Remoting Batching - - // Commands allowed to run on target remote session along with implicit remote commands - private static readonly HashSet AllowedCommands = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "ForEach-Object", - "Measure-Command", - "Measure-Object", - "Sort-Object", - "Where-Object" - }; - - // Determines if the typed command invokes implicit remoting module proxy functions in such - // a way as to allow simple batching, to reduce round trips between client and server sessions. - // Requirements: - // a. All commands must be implicit remoting module proxy commands targeted to the same remote session - // b. Except for *allowed* commands that can be safely run on remote session rather than client session - // c. Commands must be in a simple pipeline - internal static bool TryRunAsImplicitBatch(string command, Runspace runspace) + /// The current ExecutionContext language mode. + internal static PSLanguageMode EnforceSystemLockDownLanguageMode(ExecutionContext context) { - using (var ps = System.Management.Automation.PowerShell.Create()) + switch (SystemPolicy.GetSystemLockdownPolicy()) { - ps.Runspace = runspace; - - try - { - var scriptBlock = ScriptBlock.Create(command); - if (!(scriptBlock.Ast is ScriptBlockAst scriptBlockAst)) - { - return false; - } - - // Make sure that this is a simple pipeline - string errorId; - string errorMsg; - scriptBlockAst.GetSimplePipeline(true, out errorId, out errorMsg); - if (errorId != null) - { - WriteVerbose(ps, ParserStrings.ImplicitRemotingPipelineBatchingNotASimplePipeline); - return false; - } - - // Run checker - var checker = new PipelineForBatchingChecker { ScriptBeingConverted = scriptBlockAst }; - scriptBlockAst.InternalVisit(checker); - - // If this is just a single command, there is no point in batching it - if (checker.Commands.Count < 2) - { - return false; - } - - // We have a valid batching candidate - - // Check commands - if (!TryGetCommandInfoList(ps, checker.Commands, out Collection cmdInfoList)) + case SystemEnforcementMode.Enforce: + switch (context.LanguageMode) { - return false; - } - - // All command modules must be implicit remoting modules from the same PSSession - var success = true; - var psSessionId = Guid.Empty; - foreach (var cmdInfo in cmdInfoList) - { - // Check for allowed command - string cmdName = (cmdInfo is AliasInfo aliasInfo) ? aliasInfo.ReferencedCommand.Name : cmdInfo.Name; - if (AllowedCommands.Contains(cmdName)) - { - continue; - } - - // Commands must be from implicit remoting module - if (cmdInfo.Module == null || string.IsNullOrEmpty(cmdInfo.ModuleName)) - { - WriteVerbose(ps, string.Format(CultureInfo.CurrentCulture, ParserStrings.ImplicitRemotingPipelineBatchingNotImplicitCommand, cmdInfo.Name)); - success = false; + case PSLanguageMode.FullLanguage: + context.LanguageMode = PSLanguageMode.ConstrainedLanguage; break; - } - - // Commands must be from modules imported into the same remote session - if (cmdInfo.Module.PrivateData is System.Collections.Hashtable privateData) - { - var sessionIdString = privateData["ImplicitSessionId"] as string; - if (string.IsNullOrEmpty(sessionIdString)) - { - WriteVerbose(ps, string.Format(CultureInfo.CurrentCulture, ParserStrings.ImplicitRemotingPipelineBatchingNotImplicitCommand, cmdInfo.Name)); - success = false; - break; - } - var sessionId = new Guid(sessionIdString); - if (psSessionId == Guid.Empty) - { - psSessionId = sessionId; - } - else if (psSessionId != sessionId) - { - WriteVerbose(ps, string.Format(CultureInfo.CurrentCulture, ParserStrings.ImplicitRemotingPipelineBatchingWrongSession, cmdInfo.Name)); - success = false; - break; - } - } - else - { - WriteVerbose(ps, string.Format(CultureInfo.CurrentCulture, ParserStrings.ImplicitRemotingPipelineBatchingNotImplicitCommand, cmdInfo.Name)); - success = false; + case PSLanguageMode.RestrictedLanguage: + context.LanguageMode = PSLanguageMode.NoLanguage; break; - } - } - - if (success) - { - // - // Invoke command pipeline as entire pipeline on remote session - // - - // Update script to declare variables via Using keyword - if (checker.ValidVariables.Count > 0) - { - foreach (var variableName in checker.ValidVariables) - { - command = command.Replace(variableName, ("Using:" + variableName), StringComparison.OrdinalIgnoreCase); - } - - scriptBlock = ScriptBlock.Create(command); - } - - // Retrieve the PSSession runspace in which to run the batch script on - ps.Commands.Clear(); - ps.Commands.AddCommand("Get-PSSession").AddParameter("InstanceId", psSessionId); - var psSession = ps.Invoke().FirstOrDefault(); - if (psSession == null || (ps.Streams.Error.Count > 0) || (psSession.Availability != RunspaceAvailability.Available)) - { - WriteVerbose(ps, ParserStrings.ImplicitRemotingPipelineBatchingNoPSSession); - return false; - } - - WriteVerbose(ps, ParserStrings.ImplicitRemotingPipelineBatchingSuccess); - - // Create and invoke implicit remoting command pipeline - ps.Commands.Clear(); - ps.AddCommand("Invoke-Command").AddParameter("Session", psSession).AddParameter("ScriptBlock", scriptBlock).AddParameter("HideComputerName", true) - .AddCommand("Out-Default"); - foreach (var cmd in ps.Commands.Commands) - { - cmd.MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output); - } - - try - { - ps.Invoke(); - } - catch (Exception ex) - { - var errorRecord = new ErrorRecord(ex, "ImplicitRemotingBatchExecutionTerminatingError", ErrorCategory.InvalidOperation, null); - - ps.Commands.Clear(); - ps.AddCommand("Write-Error").AddParameter("InputObject", errorRecord).Invoke(); - } - - return true; - } - } - catch (ImplicitRemotingBatchingNotSupportedException ex) - { - WriteVerbose(ps, string.Format(CultureInfo.CurrentCulture, "{0} : {1}", ex.Message, ex.ErrorId)); - } - catch (Exception ex) - { - WriteVerbose(ps, string.Format(CultureInfo.CurrentCulture, ParserStrings.ImplicitRemotingPipelineBatchingException, ex.Message)); - } - } - - return false; - } - - private static void WriteVerbose(PowerShell ps, string msg) - { - ps.Commands.Clear(); - ps.AddCommand("Write-Verbose").AddParameter("Message", msg).Invoke(); - } - - private const string WhereObjectCommandAlias = "?"; - private static bool TryGetCommandInfoList(PowerShell ps, HashSet commandNames, out Collection cmdInfoList) - { - if (commandNames.Count == 0) - { - cmdInfoList = null; - return false; - } - - bool specialCaseWhereCommandAlias = commandNames.Contains(WhereObjectCommandAlias); - if (specialCaseWhereCommandAlias) - { - commandNames.Remove(WhereObjectCommandAlias); - } - - // Use Get-Command to collect CommandInfo from candidate commands, with correct precedence so - // that implicit remoting proxy commands will appear when available. - ps.Commands.Clear(); - ps.Commands.AddCommand("Get-Command").AddParameter("Name", commandNames.ToArray()); - cmdInfoList = ps.Invoke(); - if (ps.Streams.Error.Count > 0) - { - return false; - } - - // For special case '?' alias don't use Get-Command to retrieve command info, and instead - // use the GetCommand API. - if (specialCaseWhereCommandAlias) - { - var cmdInfo = ps.Runspace.ExecutionContext.SessionState.InvokeCommand.GetCommand(WhereObjectCommandAlias, CommandTypes.Alias); - if (cmdInfo == null) - { - return false; - } - - cmdInfoList.Add(cmdInfo); - } - - return true; - } - - internal static bool ShouldOutputPlainText(bool isHost, bool? supportsVirtualTerminal) - { - var outputRendering = OutputRendering.Ansi; - - if (ExperimentalFeature.IsEnabled("PSAnsiRendering")) - { - if (supportsVirtualTerminal != false) - { - switch (PSStyle.Instance.OutputRendering) - { - case OutputRendering.Automatic: - outputRendering = OutputRendering.Ansi; - break; - case OutputRendering.Host: - outputRendering = isHost ? OutputRendering.Ansi : OutputRendering.PlainText; + case PSLanguageMode.ConstrainedLanguage: + case PSLanguageMode.NoLanguage: break; + default: - outputRendering = PSStyle.Instance.OutputRendering; + Diagnostics.Assert(false, "Unexpected PSLanguageMode"); + context.LanguageMode = PSLanguageMode.NoLanguage; break; } - } - } - - return outputRendering == OutputRendering.PlainText; - } - - internal static string GetOutputString(string s, bool isHost, bool? supportsVirtualTerminal = null, bool isOutputRedirected = false) - { - if (ExperimentalFeature.IsEnabled("PSAnsiRendering")) - { - var sd = new ValueStringDecorated(s); - - if (sd.IsDecorated) - { - var outputRendering = OutputRendering.Ansi; - if (InternalTestHooks.BypassOutputRedirectionCheck) - { - isOutputRedirected = false; - } + break; - if (isOutputRedirected || ShouldOutputPlainText(isHost, supportsVirtualTerminal)) + case SystemEnforcementMode.Audit: + switch (context.LanguageMode) { - outputRendering = OutputRendering.PlainText; + case PSLanguageMode.FullLanguage: + // Set to ConstrainedLanguage mode. But no restrictions are applied in audit mode + // and only audit messages will be emitted to logs. + context.LanguageMode = PSLanguageMode.ConstrainedLanguage; + break; } - - s = sd.ToString(outputRendering); - } - } - - return s; - } - - internal enum FormatStyle - { - Reset, - FormatAccent, - ErrorAccent, - Error, - Warning, - Verbose, - Debug, - } - - internal static string GetFormatStyleString(FormatStyle formatStyle) - { - // redirected console gets plaintext output to preserve existing behavior - if (!InternalTestHooks.BypassOutputRedirectionCheck && - ((PSStyle.Instance.OutputRendering == OutputRendering.PlainText) || - (formatStyle == FormatStyle.Error && Console.IsErrorRedirected) || - (formatStyle != FormatStyle.Error && Console.IsOutputRedirected))) - { - return string.Empty; - } - - if (ExperimentalFeature.IsEnabled("PSAnsiRendering")) - { - PSStyle psstyle = PSStyle.Instance; - switch (formatStyle) - { - case FormatStyle.Reset: - return psstyle.Reset; - case FormatStyle.FormatAccent: - return psstyle.Formatting.FormatAccent; - case FormatStyle.ErrorAccent: - return psstyle.Formatting.ErrorAccent; - case FormatStyle.Error: - return psstyle.Formatting.Error; - case FormatStyle.Warning: - return psstyle.Formatting.Warning; - case FormatStyle.Verbose: - return psstyle.Formatting.Verbose; - case FormatStyle.Debug: - return psstyle.Formatting.Debug; - default: - return string.Empty; - } + break; } - return string.Empty; + return context.LanguageMode; } - #endregion - } - - #region ImplicitRemotingBatching - - // A visitor to walk an AST and validate that it is a candidate for implicit remoting batching. - // Based on ScriptBlockToPowerShellChecker. - internal class PipelineForBatchingChecker : AstVisitor - { - internal readonly HashSet ValidVariables = new HashSet(StringComparer.OrdinalIgnoreCase); - internal readonly HashSet Commands = new HashSet(StringComparer.OrdinalIgnoreCase); - - internal ScriptBlockAst ScriptBeingConverted { get; set; } - - public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) + internal static string DisplayHumanReadableFileSize(long bytes) { - if (!variableExpressionAst.VariablePath.IsAnyLocal()) - { - ThrowError( - new ImplicitRemotingBatchingNotSupportedException( - "VariableTypeNotSupported"), - variableExpressionAst); - } - - if (variableExpressionAst.VariablePath.UnqualifiedPath != "_") + return bytes switch { - ValidVariables.Add(variableExpressionAst.VariablePath.UnqualifiedPath); - } - - return AstVisitAction.Continue; + < 1024 and >= 0 => $"{bytes} Bytes", + < 1048576 and >= 1024 => $"{(bytes / 1024.0).ToString("0.0")} KB", + < 1073741824 and >= 1048576 => $"{(bytes / 1048576.0).ToString("0.0")} MB", + < 1099511627776 and >= 1073741824 => $"{(bytes / 1073741824.0).ToString("0.000")} GB", + < 1125899906842624 and >= 1099511627776 => $"{(bytes / 1099511627776.0).ToString("0.00000")} TB", + < 1152921504606847000 and >= 1125899906842624 => $"{(bytes / 1125899906842624.0).ToString("0.0000000")} PB", + >= 1152921504606847000 => $"{(bytes / 1152921504606847000.0).ToString("0.000000000")} EB", + _ => $"0 Bytes", + }; } - public override AstVisitAction VisitPipeline(PipelineAst pipelineAst) + /// + /// Returns true if the current session is restricted (JEA or similar sessions) + /// + /// ExecutionContext. + /// True if the session is restricted. + internal static bool IsSessionRestricted(ExecutionContext context) { - if (pipelineAst.PipelineElements[0] is CommandExpressionAst) - { - // If the first element is a CommandExpression, this pipeline should be the value - // of a parameter. We want to avoid a scriptblock that contains only a pure expression. - // The check "pipelineAst.Parent.Parent == ScriptBeingConverted" guarantees we throw - // error on that kind of scriptblock. - - // Disallow pure expressions at the "top" level, but allow them otherwise. - // We want to catch: - // 1 | echo - // But we don't want to error out on: - // echo $(1) - // See the comment in VisitCommand on why it's safe to check Parent.Parent, we - // know that we have at least: - // * a NamedBlockAst (the end block) - // * a ScriptBlockAst (the ast we're comparing to) - if (pipelineAst.GetPureExpression() == null || pipelineAst.Parent.Parent == ScriptBeingConverted) + 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) { - ThrowError( - new ImplicitRemotingBatchingNotSupportedException( - "PipelineStartingWithExpressionNotSupported"), - pipelineAst); + return false; } - } - - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitCommand(CommandAst commandAst) - { - if (commandAst.InvocationOperator == TokenKind.Dot) - { - ThrowError( - new ImplicitRemotingBatchingNotSupportedException( - "DotSourcingNotSupported"), - commandAst); - } - - /* - // Up front checking ensures that we have a simple script block, - // so we can safely assume that the parents are: - // * a PipelineAst - // * a NamedBlockAst (the end block) - // * a ScriptBlockAst (the ast we're comparing to) - // If that isn't the case, the conversion isn't allowed. It - // is also safe to assume that we have at least 3 parents, a script block can't be simpler. - if (commandAst.Parent.Parent.Parent != ScriptBeingConverted) - { - ThrowError( - new ImplicitRemotingBatchingNotSupportedException( - "CantConvertWithCommandInvocations not supported"), - commandAst); - } - */ - - if (commandAst.CommandElements[0] is ScriptBlockExpressionAst) - { - ThrowError( - new ImplicitRemotingBatchingNotSupportedException( - "ScriptBlockInvocationNotSupported"), - commandAst); - } - - var commandName = commandAst.GetCommandName(); - if (commandName != null) - { - Commands.Add(commandName); - } - - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitMergingRedirection(MergingRedirectionAst redirectionAst) - { - if (redirectionAst.ToStream != RedirectionStream.Output) - { - ThrowError( - new ImplicitRemotingBatchingNotSupportedException( - "MergeRedirectionNotSupported"), - redirectionAst); - } - - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitFileRedirection(FileRedirectionAst redirectionAst) - { - ThrowError( - new ImplicitRemotingBatchingNotSupportedException( - "FileRedirectionNotSupported"), - redirectionAst); - - return AstVisitAction.Continue; - } - - /* - public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst) - { - ThrowError(new ImplicitRemotingBatchingNotSupportedException( - "ScriptBlocks not supported"), - scriptBlockExpressionAst); - - return AstVisitAction.SkipChildren; - } - */ - - public override AstVisitAction VisitUsingExpression(UsingExpressionAst usingExpressionAst) - { - // Using expressions are not expected in Implicit remoting commands. - ThrowError(new ImplicitRemotingBatchingNotSupportedException( - "UsingExpressionNotSupported"), - usingExpressionAst); - - return AstVisitAction.SkipChildren; - } - - internal static void ThrowError(ImplicitRemotingBatchingNotSupportedException ex, Ast ast) - { - InterpreterError.UpdateExceptionErrorRecordPosition(ex, ast.Extent); - throw ex; - } - } - - internal class ImplicitRemotingBatchingNotSupportedException : Exception - { - internal string ErrorId { get; } - - internal ImplicitRemotingBatchingNotSupportedException(string errorId) : base( - ParserStrings.ImplicitRemotingPipelineBatchingNotSupported) - { - ErrorId = errorId; + return true; } } - - #endregion } namespace System.Management.Automation.Internal @@ -2071,8 +1563,12 @@ public static class InternalTestHooks internal static bool BypassAppLockerPolicyCaching; internal static bool BypassOnlineHelpRetrieval; internal static bool ForcePromptForChoiceDefaultOption; - internal static bool BypassOutputRedirectionCheck; internal static bool NoPromptForPassword; + internal static bool ForceFormatListFixedLabelWidth; + + // Update-Help tests + internal static bool ThrowHelpCultureNotSupported; + internal static CultureInfo CurrentUICulture; // Stop/Restart/Rename Computer tests internal static bool TestStopComputer; @@ -2089,6 +1585,11 @@ public static class InternalTestHooks internal static bool SetConsoleWidthToZero; internal static bool SetConsoleHeightToZero; + // Simulate 'MyDocuments' returning empty string + internal static bool SetMyDocumentsSpecialFolderToBlank; + + internal static bool SetDate; + // A location to test PSEdition compatibility functionality for Windows PowerShell modules with // since we can't manipulate the System32 directory in a test internal static string TestWindowsPowerShellPSHomeLocation; @@ -2100,27 +1601,22 @@ public static class InternalTestHooks internal static bool ThrowExdevErrorOnMoveDirectory; + // To emulate OneDrive behavior we use the hard-coded symlink. + // If OneDriveTestRecurseOn is false then the symlink works as regular symlink. + // If OneDriveTestRecurseOn is true then we recurse into the symlink as OneDrive should work. + // OneDriveTestSymlinkName defines the symlink name used in tests. + internal static bool OneDriveTestOn; + internal static bool OneDriveTestRecurseOn; + internal static string OneDriveTestSymlinkName = "link-Beta"; + + // Test out smaller connection buffer size when calling WNetGetConnection. + internal static int WNetGetConnectionBufferSize = -1; + /// This member is used for internal test purposes. public static void SetTestHook(string property, object value) { var fieldInfo = typeof(InternalTestHooks).GetField(property, BindingFlags.Static | BindingFlags.NonPublic); - if (fieldInfo != null) - { - fieldInfo.SetValue(null, value); - } - } - - /// - /// Test hook used to test implicit remoting batching. A local runspace must be provided that has imported a - /// remote session, i.e., has run the Import-PSSession cmdlet. This hook will return true if the provided commandPipeline - /// is successfully batched and run in the remote session, and false if it is rejected for batching. - /// - /// Command pipeline to test. - /// Runspace with imported remote session. - /// True if commandPipeline is batched successfully. - public static bool TestImplicitRemotingBatching(string commandPipeline, System.Management.Automation.Runspaces.Runspace runspace) - { - return Utils.TryRunAsImplicitBatch(commandPipeline, runspace); + fieldInfo?.SetValue(null, value); } /// @@ -2258,10 +1754,7 @@ internal sealed class ReadOnlyBag : IEnumerable /// internal ReadOnlyBag(HashSet hashset) { - if (hashset == null) - { - throw new ArgumentNullException(nameof(hashset)); - } + ArgumentNullException.ThrowIfNull(hashset); _hashset = hashset; } @@ -2297,36 +1790,12 @@ internal ReadOnlyBag(HashSet hashset) /// internal static class Requires { - internal static void NotNull(object value, string paramName) - { - if (value == null) - { - throw new ArgumentNullException(paramName); - } - } - - internal static void NotNullOrEmpty(string value, string paramName) - { - if (string.IsNullOrEmpty(value)) - { - throw new ArgumentNullException(paramName); - } - } - internal static void NotNullOrEmpty(ICollection value, string paramName) { - if (value == null || value.Count == 0) + if (value is null || value.Count == 0) { throw new ArgumentNullException(paramName); } } - - internal static void Condition([DoesNotReturnIf(false)] bool precondition, string paramName) - { - if (!precondition) - { - throw new ArgumentException(paramName); - } - } } } diff --git a/src/System.Management.Automation/engine/WinRT/IInspectable.cs b/src/System.Management.Automation/engine/WinRT/IInspectable.cs index abc589b4875..28544d42af4 100644 --- a/src/System.Management.Automation/engine/WinRT/IInspectable.cs +++ b/src/System.Management.Automation/engine/WinRT/IInspectable.cs @@ -4,6 +4,7 @@ using System.Reflection; using System.Runtime.InteropServices; +#nullable enable namespace System.Management.Automation { /// diff --git a/src/System.Management.Automation/engine/cmdlet.cs b/src/System.Management.Automation/engine/cmdlet.cs index b049193fd87..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 { @@ -23,7 +23,7 @@ namespace System.Management.Automation /// deriving from the PSCmdlet base class. The Cmdlet base class is the primary means by /// which users create their own Cmdlets. Extending this class provides support for the most /// common functionality, including object output and record processing. - /// If your Cmdlet requires access to the MSH Runtime (for example, variables in the session state, + /// If your Cmdlet requires access to the PowerShell Runtime (for example, variables in the session state, /// access to the host, or information about the current Cmdlet Providers,) then you should instead /// derive from the PSCmdlet base class. /// In both cases, users should first develop and implement an object model to accomplish their @@ -50,7 +50,7 @@ public static HashSet CommonParameters () => { return new HashSet(StringComparer.OrdinalIgnoreCase) { - "Verbose", "Debug", "ErrorAction", "WarningAction", "InformationAction", + "Verbose", "Debug", "ErrorAction", "WarningAction", "InformationAction", "ProgressAction", "ErrorVariable", "WarningVariable", "OutVariable", "OutBuffer", "PipelineVariable", "InformationVariable" }; } @@ -100,6 +100,11 @@ public bool Stopping } } + /// + /// Gets the CancellationToken that is signaled when the pipeline is stopping. + /// + public CancellationToken PipelineStopToken => StopToken; + /// /// The name of the parameter set in effect. /// @@ -453,6 +458,9 @@ public void WriteVerbose(string text) } } + internal bool IsWriteVerboseEnabled() + => commandRuntime is not MshCommandRuntime mshRuntime || mshRuntime.IsWriteVerboseEnabled(); + /// /// Display warning information. /// @@ -490,6 +498,9 @@ public void WriteWarning(string text) } } + internal bool IsWriteWarningEnabled() + => commandRuntime is not MshCommandRuntime mshRuntime || mshRuntime.IsWriteWarningEnabled(); + /// /// Write text into pipeline execution log. /// @@ -511,7 +522,7 @@ public void WriteWarning(string text) /// pipeline execution log. /// /// If LogPipelineExecutionDetail is turned on, this information will be written - /// to monad log under log category "Pipeline execution detail" + /// to PowerShell log under log category "Pipeline execution detail" /// /// /// @@ -598,6 +609,9 @@ internal void WriteProgress( throw new System.NotImplementedException("WriteProgress"); } + internal bool IsWriteProgressEnabled() + => commandRuntime is not MshCommandRuntime mshRuntime || mshRuntime.IsWriteProgressEnabled(); + /// /// Display debug information. /// @@ -641,6 +655,9 @@ public void WriteDebug(string text) } } + internal bool IsWriteDebugEnabled() + => commandRuntime is not MshCommandRuntime mshRuntime || mshRuntime.IsWriteDebugEnabled(); + /// /// Route information to the user or host. /// @@ -748,6 +765,9 @@ public void WriteInformation(InformationRecord informationRecord) } } + internal bool IsWriteInformationEnabled() + => commandRuntime is not MshCommandRuntime mshRuntime || mshRuntime.IsWriteInformationEnabled(); + #endregion Write #region ShouldProcess @@ -801,8 +821,8 @@ public void WriteInformation(InformationRecord informationRecord) /// , /// /// - /// - /// namespace Microsoft.Samples.MSH.Cmdlet + /// + /// namespace Microsoft.Samples.Cmdlet /// { /// [Cmdlet(VerbsCommon.Remove,"myobjecttype1")] /// public class RemoveMyObjectType1 : Cmdlet @@ -824,7 +844,7 @@ public void WriteInformation(InformationRecord informationRecord) /// } /// } /// } - /// + /// /// /// /// @@ -897,8 +917,8 @@ public bool ShouldProcess(string target) /// , /// /// - /// - /// namespace Microsoft.Samples.MSH.Cmdlet + /// + /// namespace Microsoft.Samples.Cmdlet /// { /// [Cmdlet(VerbsCommon.Remove,"myobjecttype2")] /// public class RemoveMyObjectType2 : Cmdlet @@ -920,7 +940,7 @@ public bool ShouldProcess(string target) /// } /// } /// } - /// + /// /// /// /// @@ -1001,8 +1021,8 @@ public bool ShouldProcess(string target, string action) /// , /// /// - /// - /// namespace Microsoft.Samples.MSH.Cmdlet + /// + /// namespace Microsoft.Samples.Cmdlet /// { /// [Cmdlet(VerbsCommon.Remove,"myobjecttype3")] /// public class RemoveMyObjectType3 : Cmdlet @@ -1018,8 +1038,8 @@ public bool ShouldProcess(string target, string action) /// public override void ProcessRecord() /// { /// if (ShouldProcess( - /// string.Format("Deleting file {0}",filename), - /// string.Format("Are you sure you want to delete file {0}?", filename), + /// string.Format($"Deleting file {filename}"), + /// string.Format($"Are you sure you want to delete file {filename}?"), /// "Delete file")) /// { /// // delete the object @@ -1027,7 +1047,7 @@ public bool ShouldProcess(string target, string action) /// } /// } /// } - /// + /// /// /// /// @@ -1117,8 +1137,8 @@ public bool ShouldProcess( /// , /// /// - /// - /// namespace Microsoft.Samples.MSH.Cmdlet + /// + /// namespace Microsoft.Samples.Cmdlet /// { /// [Cmdlet(VerbsCommon.Remove,"myobjecttype3")] /// public class RemoveMyObjectType3 : Cmdlet @@ -1135,8 +1155,8 @@ public bool ShouldProcess( /// { /// ShouldProcessReason shouldProcessReason; /// if (ShouldProcess( - /// string.Format("Deleting file {0}",filename), - /// string.Format("Are you sure you want to delete file {0}?", filename), + /// string.Format($"Deleting file {filename}"), + /// string.Format($"Are you sure you want to delete file {filename}?"), /// "Delete file", /// out shouldProcessReason)) /// { @@ -1145,7 +1165,7 @@ public bool ShouldProcess( /// } /// } /// } - /// + /// /// /// /// @@ -1233,8 +1253,8 @@ public bool ShouldProcess( /// to ShouldProcess for the Cmdlet instance. /// /// - /// - /// namespace Microsoft.Samples.MSH.Cmdlet + /// + /// namespace Microsoft.Samples.Cmdlet /// { /// [Cmdlet(VerbsCommon.Remove,"myobjecttype4")] /// public class RemoveMyObjectType4 : Cmdlet @@ -1258,14 +1278,14 @@ public bool ShouldProcess( /// public override void ProcessRecord() /// { /// if (ShouldProcess( - /// string.Format("Deleting file {0}",filename), - /// string.Format("Are you sure you want to delete file {0}", filename), + /// string.Format($"Deleting file {filename}"), + /// string.Format($"Are you sure you want to delete file {filename}"), /// "Delete file")) /// { /// if (IsReadOnly(filename)) /// { /// if (!Force && !ShouldContinue( - /// string.Format("File {0} is read-only. Are you sure you want to delete read-only file {0}?", filename), + /// string.Format($"File {filename} is read-only. Are you sure you want to delete read-only file {filename}?"), /// "Delete file")) /// ) /// { @@ -1277,7 +1297,7 @@ public bool ShouldProcess( /// } /// } /// } - /// + /// /// /// /// @@ -1311,11 +1331,11 @@ public bool ShouldContinue(string query, string caption) /// It may be displayed by some hosts, but not all. /// /// - /// true iff user selects YesToAll. If this is already true, + /// true if-and-only-if user selects YesToAll. If this is already true, /// ShouldContinue will bypass the prompt and return true. /// /// - /// true iff user selects NoToAll. If this is already true, + /// true if-and-only-if user selects NoToAll. If this is already true, /// ShouldContinue will bypass the prompt and return false. /// /// @@ -1362,8 +1382,8 @@ public bool ShouldContinue(string query, string caption) /// to ShouldProcess for the Cmdlet instance. /// /// - /// - /// namespace Microsoft.Samples.MSH.Cmdlet + /// + /// namespace Microsoft.Samples.Cmdlet /// { /// [Cmdlet(VerbsCommon.Remove,"myobjecttype4")] /// public class RemoveMyObjectType5 : Cmdlet @@ -1390,14 +1410,14 @@ public bool ShouldContinue(string query, string caption) /// public override void ProcessRecord() /// { /// if (ShouldProcess( - /// string.Format("Deleting file {0}",filename), - /// string.Format("Are you sure you want to delete file {0}", filename), + /// string.Format($"Deleting file {filename}"), + /// string.Format($"Are you sure you want to delete file {filename}"), /// "Delete file")) /// { /// if (IsReadOnly(filename)) /// { /// if (!Force && !ShouldContinue( - /// string.Format("File {0} is read-only. Are you sure you want to delete read-only file {0}?", filename), + /// string.Format($"File {filename} is read-only. Are you sure you want to delete read-only file {filename}?"), /// "Delete file"), /// ref yesToAll, /// ref noToAll @@ -1411,7 +1431,7 @@ public bool ShouldContinue(string query, string caption) /// } /// } /// } - /// + /// /// /// /// @@ -1451,11 +1471,11 @@ public bool ShouldContinue( /// the default option selected in the selection menu is 'No'. /// /// - /// true iff user selects YesToAll. If this is already true, + /// true if-and-only-if user selects YesToAll. If this is already true, /// ShouldContinue will bypass the prompt and return true. /// /// - /// true iff user selects NoToAll. If this is already true, + /// true if-and-only-if user selects NoToAll. If this is already true, /// ShouldContinue will bypass the prompt and return false. /// /// @@ -1502,8 +1522,8 @@ public bool ShouldContinue( /// to ShouldProcess for the Cmdlet instance. /// /// - /// - /// namespace Microsoft.Samples.MSH.Cmdlet + /// + /// namespace Microsoft.Samples.Cmdlet /// { /// [Cmdlet(VerbsCommon.Remove,"myobjecttype4")] /// public class RemoveMyObjectType5 : Cmdlet @@ -1530,14 +1550,14 @@ public bool ShouldContinue( /// public override void ProcessRecord() /// { /// if (ShouldProcess( - /// string.Format("Deleting file {0}",filename), - /// string.Format("Are you sure you want to delete file {0}", filename), + /// string.Format($"Deleting file {filename}"), + /// string.Format($"Are you sure you want to delete file {filename}"), /// "Delete file")) /// { /// if (IsReadOnly(filename)) /// { /// if (!Force && !ShouldContinue( - /// string.Format("File {0} is read-only. Are you sure you want to delete read-only file {0}?", filename), + /// string.Format($"File {filename} is read-only. Are you sure you want to delete read-only file {filename}?"), /// "Delete file"), /// ref yesToAll, /// ref noToAll @@ -1551,7 +1571,7 @@ public bool ShouldContinue( /// } /// } /// } - /// + /// /// /// /// @@ -1714,12 +1734,12 @@ public PSTransactionContext CurrentPSTransaction /// . /// etc. /// + [System.Diagnostics.CodeAnalysis.DoesNotReturn] public void ThrowTerminatingError(ErrorRecord errorRecord) { using (PSTransactionManager.GetEngineProtectionScope()) { - if (errorRecord == null) - throw new ArgumentNullException(nameof(errorRecord)); + ArgumentNullException.ThrowIfNull(errorRecord); if (commandRuntime != null) { @@ -1820,14 +1840,16 @@ public enum ShouldProcessReason None = 0x0, /// + /// /// WhatIf behavior was requested. - /// - /// - /// In the MSH host, WhatIf behavior can be requested explicitly + /// + /// + /// In the host, WhatIf behavior can be requested explicitly /// for one cmdlet instance using the -WhatIf commandline parameter, /// or implicitly for all SupportsShouldProcess cmdlets with $WhatIfPreference. /// Other hosts may have other ways to request WhatIf behavior. - /// + /// + /// WhatIf = 0x1, } } diff --git a/src/System.Management.Automation/engine/debugger/Breakpoint.cs b/src/System.Management.Automation/engine/debugger/Breakpoint.cs index ca1c9818206..c4b699bd152 100644 --- a/src/System.Management.Automation/engine/debugger/Breakpoint.cs +++ b/src/System.Management.Automation/engine/debugger/Breakpoint.cs @@ -439,7 +439,7 @@ public override string ToString() internal BitArray BreakpointBitArray { get; set; } - private class CheckBreakpointInScript : AstVisitor + private sealed class CheckBreakpointInScript : AstVisitor { public static bool IsInNestedScriptBlock(Ast ast, LineBreakpoint breakpoint) { @@ -482,9 +482,6 @@ internal bool TrySetBreakpoint(string scriptFile, FunctionContext functionContex { Diagnostics.Assert(SequencePointIndex == -1, "shouldn't be trying to set on a pending breakpoint"); - if (!scriptFile.Equals(this.Script, StringComparison.OrdinalIgnoreCase)) - return false; - // A quick check to see if the breakpoint is within the scriptblock. bool couldBeInNestedScriptBlock; var scriptBlock = functionContext._scriptBlock; @@ -531,15 +528,16 @@ internal bool TrySetBreakpoint(string scriptFile, FunctionContext functionContex // Not found. First, we check if the line/column is before any real code. If so, we'll // move the breakpoint to the first interesting sequence point (could be a dynamicparam, - // begin, process, or end block.) + // begin, process, end, or clean block.) if (scriptBlock != null) { var ast = scriptBlock.Ast; var bodyAst = ((IParameterMetadataProvider)ast).Body; - if ((bodyAst.DynamicParamBlock == null || bodyAst.DynamicParamBlock.Extent.IsAfter(Line, Column)) && - (bodyAst.BeginBlock == null || bodyAst.BeginBlock.Extent.IsAfter(Line, Column)) && - (bodyAst.ProcessBlock == null || bodyAst.ProcessBlock.Extent.IsAfter(Line, Column)) && - (bodyAst.EndBlock == null || bodyAst.EndBlock.Extent.IsAfter(Line, Column))) + if ((bodyAst.DynamicParamBlock == null || bodyAst.DynamicParamBlock.Extent.IsAfter(Line, Column)) + && (bodyAst.BeginBlock == null || bodyAst.BeginBlock.Extent.IsAfter(Line, Column)) + && (bodyAst.ProcessBlock == null || bodyAst.ProcessBlock.Extent.IsAfter(Line, Column)) + && (bodyAst.EndBlock == null || bodyAst.EndBlock.Extent.IsAfter(Line, Column)) + && (bodyAst.CleanBlock == null || bodyAst.CleanBlock.Extent.IsAfter(Line, Column))) { SetBreakpoint(functionContext, 0); return true; @@ -594,11 +592,11 @@ internal override bool RemoveSelf(ScriptDebugger debugger) var boundBreakPoints = debugger.GetBoundBreakpoints(this.SequencePoints); if (boundBreakPoints != null) { - Diagnostics.Assert(boundBreakPoints.Contains(this), + Diagnostics.Assert(boundBreakPoints[this.SequencePointIndex].Contains(this), "If we set _scriptBlock, we should have also added the breakpoint to the bound breakpoint list"); - boundBreakPoints.Remove(this); + boundBreakPoints[this.SequencePointIndex].Remove(this); - if (boundBreakPoints.All(breakpoint => breakpoint.SequencePointIndex != this.SequencePointIndex)) + if (boundBreakPoints[this.SequencePointIndex].All(breakpoint => breakpoint.SequencePointIndex != this.SequencePointIndex)) { // No other line breakpoints are at the same sequence point, so disable the breakpoint so // we don't go looking for breakpoints the next time we hit the sequence point. diff --git a/src/System.Management.Automation/engine/debugger/debugger.cs b/src/System.Management.Automation/engine/debugger/debugger.cs index 30e83c38f90..bdad4960ac0 100644 --- a/src/System.Management.Automation/engine/debugger/debugger.cs +++ b/src/System.Management.Automation/engine/debugger/debugger.cs @@ -790,6 +790,16 @@ internal virtual void Break(object triggerObject = null) throw new PSNotImplementedException(); } + /// + /// Returns script position message of current execution stack item. + /// This is used for WDAC audit mode logging for script information enhancement. + /// + /// Script position message string. + internal virtual string GetCurrentScriptPosition() + { + throw new PSNotImplementedException(); + } + /// /// Passes the debugger command to the internal script debugger command processor. This /// is used internally to handle debugger commands such as list, help, etc. @@ -971,7 +981,8 @@ internal ScriptDebugger(ExecutionContext context) _context = context; _inBreakpoint = false; _idToBreakpoint = new ConcurrentDictionary(); - _pendingBreakpoints = new ConcurrentDictionary(); + // The string key is function context file path. The int key is sequencePoint index. + _pendingBreakpoints = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); _boundBreakpoints = new ConcurrentDictionary>>(StringComparer.OrdinalIgnoreCase); _commandBreakpoints = new ConcurrentDictionary(); _variableBreakpoints = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); @@ -1057,12 +1068,9 @@ private bool IsLocalSession { get { - if (_isLocalSession == null) - { - // Remote debug sessions always have a ServerRemoteHost. Otherwise it is a local session. - _isLocalSession = !(((_context.InternalHost.ExternalHost != null) && - (_context.InternalHost.ExternalHost is System.Management.Automation.Remoting.ServerRemoteHost))); - } + // Remote debug sessions always have a ServerRemoteHost. Otherwise it is a local session. + _isLocalSession ??= !((_context.InternalHost.ExternalHost != null) && + (_context.InternalHost.ExternalHost is System.Management.Automation.Remoting.ServerRemoteHost)); return _isLocalSession.Value; } @@ -1177,7 +1185,7 @@ internal void EnterScriptFunction(FunctionContext functionContext) private void SetupBreakpoints(FunctionContext functionContext) { var scriptDebugData = _mapScriptToBreakpoints.GetValue(functionContext._sequencePoints, - _ => Tuple.Create(new List(), + _ => Tuple.Create(new Dictionary>(), new BitArray(functionContext._sequencePoints.Length))); functionContext._boundBreakpoints = scriptDebugData.Item1; functionContext._breakPoints = scriptDebugData.Item2; @@ -1257,10 +1265,19 @@ private CommandBreakpoint AddCommandBreakpoint(CommandBreakpoint breakpoint) private LineBreakpoint AddLineBreakpoint(LineBreakpoint breakpoint) { AddBreakpointCommon(breakpoint); - _pendingBreakpoints[breakpoint.Id] = breakpoint; + AddPendingBreakpoint(breakpoint); + return breakpoint; } + private void AddPendingBreakpoint(LineBreakpoint breakpoint) + { + _pendingBreakpoints.AddOrUpdate( + breakpoint.Script, + new ConcurrentDictionary { [breakpoint.Id] = breakpoint }, + (_, dictionary) => { dictionary.TryAdd(breakpoint.Id, breakpoint); return dictionary; }); + } + private void AddNewBreakpoint(Breakpoint breakpoint) { LineBreakpoint lineBreakpoint = breakpoint as LineBreakpoint; @@ -1313,13 +1330,9 @@ private void UpdateBreakpoints(FunctionContext functionContext) return; } - foreach ((int breakpointId, LineBreakpoint item) in _pendingBreakpoints) + if (_pendingBreakpoints.TryGetValue(functionContext._file, out var dictionary) && !dictionary.IsEmpty) { - if (item.IsScriptBreakpoint && item.Script.Equals(functionContext._file, StringComparison.OrdinalIgnoreCase)) - { - SetPendingBreakpoints(functionContext); - break; - } + SetPendingBreakpoints(functionContext); } } } @@ -1345,7 +1358,11 @@ internal bool RemoveCommandBreakpoint(CommandBreakpoint breakpoint) => internal bool RemoveLineBreakpoint(LineBreakpoint breakpoint) { - bool removed = _pendingBreakpoints.Remove(breakpoint.Id, out _); + bool removed = false; + if (_pendingBreakpoints.TryGetValue(breakpoint.Script, out var dictionary)) + { + removed = dictionary.Remove(breakpoint.Id, out _); + } Tuple> value; if (_boundBreakpoints.TryGetValue(breakpoint.Script, out value)) @@ -1363,8 +1380,8 @@ internal bool RemoveLineBreakpoint(LineBreakpoint breakpoint) // The bit array is used to detect if a breakpoint is set or not for a given scriptblock. This bit array // is checked when hitting sequence points. Enabling/disabling a line breakpoint is as simple as flipping // the bit. - private readonly ConditionalWeakTable, BitArray>> _mapScriptToBreakpoints = - new ConditionalWeakTable, BitArray>>(); + private readonly ConditionalWeakTable>, BitArray>> _mapScriptToBreakpoints = + new ConditionalWeakTable>, BitArray>>(); /// /// Checks for command breakpoints. @@ -1465,9 +1482,9 @@ internal void TriggerVariableBreakpoints(List breakpoints) // Return the line breakpoints bound in a specific script block (used when a sequence point // is hit, to find which breakpoints are set on that sequence point.) - internal List GetBoundBreakpoints(IScriptExtent[] sequencePoints) + internal Dictionary> GetBoundBreakpoints(IScriptExtent[] sequencePoints) { - Tuple, BitArray> tuple; + Tuple>, BitArray> tuple; if (_mapScriptToBreakpoints.TryGetValue(sequencePoints, out tuple)) { return tuple.Item1; @@ -1519,7 +1536,16 @@ private List TriggerBreakpoints(List 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); } @@ -1553,16 +1579,25 @@ internal void OnSequencePointHit(FunctionContext functionContext) { if (functionContext._breakPoints[functionContext._currentSequencePointIndex]) { - var breakpoints = (from breakpoint in functionContext._boundBreakpoints - where - breakpoint.SequencePointIndex == functionContext._currentSequencePointIndex && - breakpoint.Enabled - select breakpoint).ToList(); - - breakpoints = TriggerBreakpoints(breakpoints); - if (breakpoints.Count > 0) + if (functionContext._boundBreakpoints.TryGetValue(functionContext._currentSequencePointIndex, out var sequencePointBreakpoints)) { - StopOnSequencePoint(functionContext, breakpoints); + var enabledBreakpoints = new List(); + foreach (Breakpoint breakpoint in sequencePointBreakpoints) + { + if (breakpoint.Enabled) + { + enabledBreakpoints.Add(breakpoint); + } + } + + if (enabledBreakpoints.Count > 0) + { + enabledBreakpoints = TriggerBreakpoints(enabledBreakpoints); + if (enabledBreakpoints.Count > 0) + { + StopOnSequencePoint(functionContext, enabledBreakpoints); + } + } } } } @@ -1575,7 +1610,7 @@ internal void OnSequencePointHit(FunctionContext functionContext) #region private members [DebuggerDisplay("{FunctionContext.CurrentPosition}")] - private class CallStackInfo + private sealed class CallStackInfo { internal InvocationInfo InvocationInfo { get; set; } @@ -1676,7 +1711,7 @@ internal void Clear() } private readonly ExecutionContext _context; - private ConcurrentDictionary _pendingBreakpoints; + private readonly ConcurrentDictionary> _pendingBreakpoints; private readonly ConcurrentDictionary>> _boundBreakpoints; private readonly ConcurrentDictionary _commandBreakpoints; private readonly ConcurrentDictionary> _variableBreakpoints; @@ -1807,7 +1842,8 @@ private void OnDebuggerStop(InvocationInfo invocationInfo, List brea { // Fix up prompt. ++index; - string debugPrompt = "\"[DBG]: " + originalPromptString.Substring(index, originalPromptString.Length - index); + string debugPrompt = string.Concat("\"[DBG]: ", originalPromptString.AsSpan(index, originalPromptString.Length - index)); + defaultPromptInfo.Update( ScriptBlock.Create(debugPrompt), true, ScopedItemOptions.Unspecified); } @@ -1948,10 +1984,7 @@ private bool WaitForDebugStopSubscriber() if (_preserveUnhandledDebugStopEvent) { // Lazily create the event object. - if (_preserveDebugStopEvent == null) - { - _preserveDebugStopEvent = new ManualResetEventSlim(true); - } + _preserveDebugStopEvent ??= new ManualResetEventSlim(true); // Set the event handle to non-signaled. if (!_preserveDebugStopEvent.IsSet) @@ -1986,16 +2019,20 @@ private void UnbindBoundBreakpoints(List boundBreakpoints) foreach (var breakpoint in boundBreakpoints) { // Also remove unbound breakpoints from the script to breakpoint map. - Tuple, BitArray> lineBreakTuple; + Tuple>, BitArray> lineBreakTuple; if (_mapScriptToBreakpoints.TryGetValue(breakpoint.SequencePoints, out lineBreakTuple)) { - lineBreakTuple.Item1.Remove(breakpoint); + if (lineBreakTuple.Item1.TryGetValue(breakpoint.SequencePointIndex, out var lineBreakList)) + { + lineBreakList.Remove(breakpoint); + } } breakpoint.SequencePoints = null; breakpoint.SequencePointIndex = -1; breakpoint.BreakpointBitArray = null; - _pendingBreakpoints[breakpoint.Id] = breakpoint; + + AddPendingBreakpoint(breakpoint); } boundBreakpoints.Clear(); @@ -2003,23 +2040,24 @@ private void UnbindBoundBreakpoints(List boundBreakpoints) private void SetPendingBreakpoints(FunctionContext functionContext) { - if (_pendingBreakpoints.IsEmpty) - return; - - var newPendingBreakpoints = new Dictionary(); var currentScriptFile = functionContext._file; // If we're not in a file, we can't have any line breakpoints. if (currentScriptFile == null) return; + if (!_pendingBreakpoints.TryGetValue(currentScriptFile, out var breakpoints) || breakpoints.IsEmpty) + { + return; + } + // Normally we register a script file when the script is run or the module is imported, // but if there weren't any breakpoints when the script was run and the script was dotted, // we will end up here with pending breakpoints, but we won't have cached the list of // breakpoints in the script. RegisterScriptFile(currentScriptFile, functionContext.CurrentPosition.StartScriptPosition.GetFullScript()); - Tuple, BitArray> tuple; + Tuple>, BitArray> tuple; if (!_mapScriptToBreakpoints.TryGetValue(functionContext._sequencePoints, out tuple)) { Diagnostics.Assert(false, "If the script block is still alive, the entry should not be collected."); @@ -2027,7 +2065,7 @@ private void SetPendingBreakpoints(FunctionContext functionContext) Diagnostics.Assert(tuple.Item1 == functionContext._boundBreakpoints, "What's up?"); - foreach ((int breakpointId, LineBreakpoint breakpoint) in _pendingBreakpoints) + foreach ((int breakpointId, LineBreakpoint breakpoint) in breakpoints) { bool bound = false; if (breakpoint.TrySetBreakpoint(currentScriptFile, functionContext)) @@ -2038,21 +2076,32 @@ private void SetPendingBreakpoints(FunctionContext functionContext) } bound = true; - tuple.Item1.Add(breakpoint); + if (tuple.Item1.TryGetValue(breakpoint.SequencePointIndex, out var list)) + { + list.Add(breakpoint); + } + else + { + tuple.Item1.Add(breakpoint.SequencePointIndex, new List { 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; boundBreakpoints[breakpoint.Id] = breakpoint; } - if (!bound) + if (bound) { - newPendingBreakpoints.Add(breakpoint.Id, breakpoint); + breakpoints.TryRemove(breakpointId, out _); } } - _pendingBreakpoints = new ConcurrentDictionary(newPendingBreakpoints); + // 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. + // 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. } private void StopOnSequencePoint(FunctionContext functionContext, List breakpoints) @@ -2134,7 +2183,7 @@ private bool CanDisableDebugger { get { - // The debugger can be disbled if there are no breakpoints + // The debugger can be disabled if there are no breakpoints // left and if we are not currently stepping in the debugger. return _idToBreakpoint.IsEmpty && _currentDebuggerAction != DebuggerResumeAction.StepInto && @@ -2151,11 +2200,8 @@ private bool IsSystemLockedDown { lock (_syncObject) { - if (_isSystemLockedDown == null) - { - _isSystemLockedDown = (System.Management.Automation.Security.SystemPolicy.GetSystemLockdownPolicy() == - System.Management.Automation.Security.SystemEnforcementMode.Enforce); - } + _isSystemLockedDown ??= (System.Management.Automation.Security.SystemPolicy.GetSystemLockdownPolicy() == + System.Management.Automation.Security.SystemEnforcementMode.Enforce); } } @@ -2394,10 +2440,7 @@ public override void StopProcessCommand() } PowerShell ps = _psDebuggerCommand; - if (ps != null) - { - ps.BeginStop(null, null); - } + ps?.BeginStop(null, null); } /// @@ -2530,6 +2573,29 @@ internal override void Break(object triggerObject = null) } } + /// + /// Returns script position message of current execution stack item. + /// This is used for WDAC audit mode logging for script information enhancement. + /// + /// Script position message string. + internal override string GetCurrentScriptPosition() + { + using (IEnumerator enumerator = GetCallStack().GetEnumerator()) + { + if (enumerator.MoveNext()) + { + var functionContext = enumerator.Current.FunctionContext; + if (functionContext is not null) + { + var invocationInfo = new InvocationInfo(commandInfo: null, functionContext.CurrentPosition, _context); + return $"\n{invocationInfo.PositionMessage}"; + } + } + } + + return null; + } + /// /// Passes the debugger command to the internal script debugger command processor. This /// is used internally to handle debugger commands such as list, help, etc. @@ -3248,11 +3314,7 @@ private void SetRunspaceListToStep(bool enableStepping) try { Debugger nestedDebugger = item.NestedDebugger; - - if (nestedDebugger != null) - { - nestedDebugger.SetDebuggerStepMode(enableStepping); - } + nestedDebugger?.SetDebuggerStepMode(enableStepping); } catch (PSNotImplementedException) { } } @@ -3554,7 +3616,7 @@ private DebuggerCommandResults ProcessCommandForActiveDebugger(PSCommand command else if ((command.Commands.Count > 0) && (command.Commands[0].CommandText.IndexOf(".EnterNestedPrompt()", StringComparison.OrdinalIgnoreCase) > 0)) { - // Prevent a host EnterNestedPrompt() call from occuring in an active debugger. + // Prevent a host EnterNestedPrompt() call from occurring in an active debugger. // Host nested prompt makes no sense in this case and can cause host to stop responding depending on host implementation. throw new PSNotSupportedException(); } @@ -4505,7 +4567,7 @@ protected virtual bool HandleListCommand(PSDataCollection output) /// /// Attempts to fix up the debugger stop invocation information so that /// the correct stack and source can be displayed in the debugger, for - /// cases where the debugged runspace is called inside a parent sccript, + /// cases where the debugged runspace is called inside a parent script, /// such as with script Invoke-Command cases. /// /// @@ -4544,10 +4606,7 @@ internal void CheckStateAndRaiseStopEvent() // If this is a remote server debugger then we want to convert the pending remote // debugger stop to a local debugger stop event for this Debug-Runspace to handle. ServerRemoteDebugger serverRemoteDebugger = this._wrappedDebugger as ServerRemoteDebugger; - if (serverRemoteDebugger != null) - { - serverRemoteDebugger.ReleaseAndRaiseDebugStopLocal(); - } + serverRemoteDebugger?.ReleaseAndRaiseDebugStopLocal(); } } @@ -4779,7 +4838,7 @@ protected override bool HandleListCommand(PSDataCollection output) /// /// Attempts to fix up the debugger stop invocation information so that /// the correct stack and source can be displayed in the debugger, for - /// cases where the debugged runspace is called inside a parent sccript, + /// cases where the debugged runspace is called inside a parent script, /// such as with script Invoke-Command cases. /// /// Invocation information from debugger stop. @@ -4959,10 +5018,7 @@ private static void RestoreRemoteOutput(object runningCmd) else { Pipeline pipelineCommand = runningCmd as Pipeline; - if (pipelineCommand != null) - { - pipelineCommand.ResumeIncomingData(); - } + pipelineCommand?.ResumeIncomingData(); } } @@ -5322,10 +5378,9 @@ private void DisplayScript(PSHost host, IList output, InvocationInfo i for (int lineNumber = start; lineNumber <= _lines.Length && lineNumber < start + count; lineNumber++) { WriteLine( - lineNumber == invocationInfo.ScriptLineNumber ? - string.Format(CultureInfo.CurrentCulture, "{0,5}:* {1}", lineNumber, _lines[lineNumber - 1]) - : - string.Format(CultureInfo.CurrentCulture, "{0,5}: {1}", lineNumber, _lines[lineNumber - 1]), + lineNumber == invocationInfo.ScriptLineNumber + ? string.Format(CultureInfo.CurrentCulture, "{0,5}:* {1}", lineNumber, _lines[lineNumber - 1]) + : string.Format(CultureInfo.CurrentCulture, "{0,5}: {1}", lineNumber, _lines[lineNumber - 1]), host, output); @@ -5337,47 +5392,29 @@ private void DisplayScript(PSHost host, IList output, InvocationInfo i private static void WriteLine(string line, PSHost host, IList output) { - if (host != null) - { - host.UI.WriteLine(line); - } + host?.UI.WriteLine(line); - if (output != null) - { - output.Add(new PSObject(line)); - } + output?.Add(new PSObject(line)); } private static void WriteCR(PSHost host, IList output) { - if (host != null) - { - host.UI.WriteLine(); - } + host?.UI.WriteLine(); - if (output != null) - { - output.Add(new PSObject(Crlf)); - } + output?.Add(new PSObject(Crlf)); } private static void WriteErrorLine(string error, PSHost host, IList output) { - if (host != null) - { - host.UI.WriteErrorLine(error); - } + host?.UI.WriteErrorLine(error); - if (output != null) - { - output.Add( - new PSObject( - new ErrorRecord( - new RuntimeException(error), - "DebuggerError", - ErrorCategory.InvalidOperation, - null))); - } + output?.Add( + new PSObject( + new ErrorRecord( + new RuntimeException(error), + "DebuggerError", + ErrorCategory.InvalidOperation, + null))); } } @@ -5686,7 +5723,7 @@ public static void StartMonitoringRunspace(Debugger debugger, PSMonitorRunspaceI } /// - /// End monitoring a runspace on the target degbugger. + /// End monitoring a runspace on the target debugger. /// /// Target debugger. /// PSMonitorRunspaceInfo. diff --git a/src/System.Management.Automation/engine/hostifaces/AsyncResult.cs b/src/System.Management.Automation/engine/hostifaces/AsyncResult.cs index f1e57e6fe39..74bcefae2a8 100644 --- a/src/System.Management.Automation/engine/hostifaces/AsyncResult.cs +++ b/src/System.Management.Automation/engine/hostifaces/AsyncResult.cs @@ -16,7 +16,7 @@ internal class AsyncResult : IAsyncResult #region Private Data private ManualResetEvent _completedWaitHandle; - // exception occured in the async thread. + // exception occurred in the async thread. // user supplied state object // Invoke on thread (remote debugging support). @@ -85,10 +85,7 @@ public WaitHandle AsyncWaitHandle { lock (SyncObject) { - if (_completedWaitHandle == null) - { - _completedWaitHandle = new ManualResetEvent(IsCompleted); - } + _completedWaitHandle ??= new ManualResetEvent(IsCompleted); } } @@ -125,7 +122,7 @@ public WaitHandle AsyncWaitHandle /// Marks the async operation as completed. /// /// - /// Exception occured. null if no exception occured + /// Exception occurred. null if no exception occurred /// internal void SetAsCompleted(Exception exception) { @@ -178,10 +175,7 @@ internal void SignalWaitHandle() { lock (SyncObject) { - if (_completedWaitHandle != null) - { - _completedWaitHandle.Set(); - } + _completedWaitHandle?.Set(); } } @@ -222,7 +216,7 @@ internal void EndInvoke() _invokeOnThreadEvent.Dispose(); _invokeOnThreadEvent = null; // Allow early GC - // Operation is done: if an exception occured, throw it + // Operation is done: if an exception occurred, throw it if (Exception != null) { throw Exception; diff --git a/src/System.Management.Automation/engine/hostifaces/ChoiceDescription.cs b/src/System.Management.Automation/engine/hostifaces/ChoiceDescription.cs index 1b55485afdc..0cac1d02b6f 100644 --- a/src/System.Management.Automation/engine/hostifaces/ChoiceDescription.cs +++ b/src/System.Management.Automation/engine/hostifaces/ChoiceDescription.cs @@ -6,8 +6,8 @@ namespace System.Management.Automation.Host { /// - /// Provides a description of a choice for use by . - /// + /// Provides a description of a choice for use by . + /// /// public sealed class ChoiceDescription @@ -84,7 +84,7 @@ class ChoiceDescription /// /// Note that the special character & (ampersand) may be embedded in the label string to identify the next character in the label /// as a "hot key" (aka "keyboard accelerator") that the Console.PromptForChoice implementation may use to allow the user to - /// quickly set input focus to this choice. The implementation of + /// quickly set input focus to this choice. The implementation of /// is responsible for parsing the label string for this special character and rendering it accordingly. /// /// For examples, a choice named "Yes to All" might have "Yes to &All" as it's label. diff --git a/src/System.Management.Automation/engine/hostifaces/Connection.cs b/src/System.Management.Automation/engine/hostifaces/Connection.cs index 5af011adb82..3f5911d1c62 100644 --- a/src/System.Management.Automation/engine/hostifaces/Connection.cs +++ b/src/System.Management.Automation/engine/hostifaces/Connection.cs @@ -19,7 +19,6 @@ namespace System.Management.Automation.Runspaces /// Exception thrown when state of the runspace is different from /// expected state of runspace. /// - [Serializable] public class InvalidRunspaceStateException : SystemException { /// @@ -96,9 +95,10 @@ RunspaceState expectedState /// The that contains contextual information /// about the source or destination. /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected InvalidRunspaceStateException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion @@ -219,12 +219,9 @@ public enum PSThreadOptions ReuseThread = 2, /// - /// Doesn't create a new thread; the execution occurs on the - /// thread that calls Invoke. + /// Doesn't create a new thread; the execution occurs on the thread + /// that calls Invoke. This option is not valid for asynchronous calls. /// - /// - /// This option is not valid for asynchronous calls - /// UseCurrentThread = 3 } @@ -414,7 +411,7 @@ internal RunspaceAvailabilityEventArgs(RunspaceAvailability runspaceAvailability public enum RunspaceCapability { /// - /// No additional capabilities beyond a default runspace. + /// Legacy capabilities for WinRM only, from Win7 timeframe. /// Default = 0x0, @@ -436,13 +433,18 @@ public enum RunspaceCapability /// /// Runspace is based on SSH transport. /// - SSHTransport = 0x8 + SSHTransport = 0x8, + + /// + /// Runspace is based on open custom connection/transport support. + /// + CustomTransport = 0x100 } #endregion /// - /// Public interface to Msh Runtime. Provides APIs for creating pipelines, + /// Public interface to PowerShell Runtime. Provides APIs for creating pipelines, /// access session state etc. /// public abstract class Runspace : IDisposable @@ -698,7 +700,7 @@ public Guid InstanceId /// /// Runspace is not opened. /// - internal System.Management.Automation.ExecutionContext ExecutionContext + internal ExecutionContext ExecutionContext { get { @@ -1508,7 +1510,10 @@ internal PowerShell PopRunningPowerShell() if (count > 0) { - if (count == 1) { _baseRunningPowerShell = null; } + if (count == 1) + { + _baseRunningPowerShell = null; + } return _runningPowerShells.Pop(); } @@ -1569,7 +1574,7 @@ protected virtual void Dispose(bool disposing) /// /// Gets the execution context. /// - internal abstract System.Management.Automation.ExecutionContext GetExecutionContext + internal abstract ExecutionContext GetExecutionContext { get; } @@ -1615,8 +1620,8 @@ public abstract PSEventManager Events /// /// Sets the base transaction for the runspace; any transactions created on this runspace will be nested to this instance. /// - ///The base transaction - ///This overload uses RollbackSeverity.Error; i.e. the transaction will be rolled back automatically on a non-terminating error or worse + /// The base transaction + /// This overload uses RollbackSeverity.Error; i.e. the transaction will be rolled back automatically on a non-terminating error or worse public void SetBaseTransaction(System.Transactions.CommittableTransaction transaction) { this.ExecutionContext.TransactionManager.SetBaseTransaction(transaction, RollbackSeverity.Error); @@ -1625,8 +1630,8 @@ public void SetBaseTransaction(System.Transactions.CommittableTransaction transa /// /// Sets the base transaction for the runspace; any transactions created on this runspace will be nested to this instance. /// - ///The base transaction - ///The severity of error that causes PowerShell to automatically rollback the transaction + /// The base transaction + /// The severity of error that causes PowerShell to automatically rollback the transaction public void SetBaseTransaction(System.Transactions.CommittableTransaction transaction, RollbackSeverity severity) { this.ExecutionContext.TransactionManager.SetBaseTransaction(transaction, severity); diff --git a/src/System.Management.Automation/engine/hostifaces/ConnectionBase.cs b/src/System.Management.Automation/engine/hostifaces/ConnectionBase.cs index 99151b7b0d4..0969d7b08c6 100644 --- a/src/System.Management.Automation/engine/hostifaces/ConnectionBase.cs +++ b/src/System.Management.Automation/engine/hostifaces/ConnectionBase.cs @@ -25,6 +25,28 @@ internal abstract class RunspaceBase : Runspace { #region constructors + /// + /// Initialize powershell AssemblyLoadContext and register the 'Resolving' event, if it's not done already. + /// If powershell is hosted by a native host such as DSC, then PS ALC may be initialized via 'SetPowerShellAssemblyLoadContext' before loading S.M.A. + /// + /// + /// We do this both here and during the initialization of the 'ClrFacade' type. + /// This is because we want to make sure the assembly/library resolvers are: + /// 1. registered before any script/cmdlet can run. + /// 2. registered before 'ClrFacade' gets used for assembly related operations. + /// + /// The 'ClrFacade' type may be used without a Runspace created, for example, by calling type conversion methods in the 'LanguagePrimitive' type. + /// And at the mean time, script or cmdlet may run without the 'ClrFacade' type initialized. + /// That's why we attempt to create the singleton of 'PowerShellAssemblyLoadContext' at both places. + /// + static RunspaceBase() + { + if (PowerShellAssemblyLoadContext.Instance is null) + { + PowerShellAssemblyLoadContext.InitializeSingleton(string.Empty, throwOnReentry: false); + } + } + /// /// Construct an instance of an Runspace using a custom /// implementation of PSHost. @@ -237,7 +259,11 @@ public override void OpenAsync() private void CoreOpen(bool syncCall) { bool etwEnabled = RunspaceEventSource.Log.IsEnabled(); - if (etwEnabled) RunspaceEventSource.Log.OpenRunspaceStart(); + if (etwEnabled) + { + RunspaceEventSource.Log.OpenRunspaceStart(); + } + lock (SyncRoot) { // Call fails if RunspaceState is not BeforeOpen. @@ -260,10 +286,13 @@ private void CoreOpen(bool syncCall) RaiseRunspaceStateEvents(); OpenHelper(syncCall); - if (etwEnabled) RunspaceEventSource.Log.OpenRunspaceStop(); + if (etwEnabled) + { + RunspaceEventSource.Log.OpenRunspaceStop(); + } #if LEGACYTELEMETRY - // We report startup telementry when opening the runspace - because this is the first time + // We report startup telemetry when opening the runspace - because this is the first time // we are really using PowerShell. This isn't the cleanest place though, because // sometimes there are many runspaces created - the callee ensures telemetry is only // reported once. Note that if the host implements IHostProvidesTelemetryData, we rely @@ -661,7 +690,7 @@ protected RunspaceState RunspaceState } /// - /// This is queue of all the state change event which have occured for + /// This is queue of all the state change event which have occurred for /// this runspace. RaiseRunspaceStateEvents raises event for each /// item in this queue. We don't raise events from with SetRunspaceState /// because SetRunspaceState is often called from with in the a lock. @@ -670,7 +699,7 @@ protected RunspaceState RunspaceState /// private Queue _runspaceEventQueue = new Queue(); - private class RunspaceEventQueueItem + private sealed class RunspaceEventQueueItem { public RunspaceEventQueueItem(RunspaceStateInfo runspaceStateInfo, RunspaceAvailability currentAvailability, RunspaceAvailability newAvailability) { diff --git a/src/System.Management.Automation/engine/hostifaces/ConnectionFactory.cs b/src/System.Management.Automation/engine/hostifaces/ConnectionFactory.cs index b812e8fe82b..45591ee2906 100644 --- a/src/System.Management.Automation/engine/hostifaces/ConnectionFactory.cs +++ b/src/System.Management.Automation/engine/hostifaces/ConnectionFactory.cs @@ -476,58 +476,64 @@ connectionInfo is not VMConnectionInfo && #region Runspace - Remote Factory /// + /// Creates a remote Runspace. /// + /// It defines connection path to a remote runspace that needs to be created. + /// The explicit PSHost implementation. /// /// The TypeTable to use while deserializing/serializing remote objects. /// TypeTable has the following information used by serializer: /// 1. SerializationMethod /// 2. SerializationDepth /// 3. SpecificSerializationProperties + /// /// TypeTable has the following information used by deserializer: /// 1. TargetTypeForDeserialization /// 2. TypeConverter /// - /// - /// - /// + /// A remote Runspace. public static Runspace CreateRunspace(RunspaceConnectionInfo connectionInfo, PSHost host, TypeTable typeTable) { return CreateRunspace(connectionInfo, host, typeTable, null, null); } /// + /// Creates a remote Runspace. /// + /// It defines connection path to a remote runspace that needs to be created. + /// The explicit PSHost implementation. /// /// The TypeTable to use while deserializing/serializing remote objects. /// TypeTable has the following information used by serializer: /// 1. SerializationMethod /// 2. SerializationDepth /// 3. SpecificSerializationProperties + /// /// TypeTable has the following information used by deserializer: /// 1. TargetTypeForDeserialization /// 2. TypeConverter /// - /// - /// /// /// Application arguments the server can see in /// - /// + /// A remote Runspace. public static Runspace CreateRunspace(RunspaceConnectionInfo connectionInfo, PSHost host, TypeTable typeTable, PSPrimitiveDictionary applicationArguments) { return CreateRunspace(connectionInfo, host, typeTable, applicationArguments, null); } /// + /// Creates a remote Runspace. /// - /// - /// + /// It defines connection path to a remote runspace that needs to be created. + /// The explicit PSHost implementation. /// /// The TypeTable to use while deserializing/serializing remote objects. /// TypeTable has the following information used by serializer: /// 1. SerializationMethod /// 2. SerializationDepth /// 3. SpecificSerializationProperties + /// /// TypeTable has the following information used by deserializer: /// 1. TargetTypeForDeserialization /// 2. TypeConverter @@ -536,19 +542,9 @@ public static Runspace CreateRunspace(RunspaceConnectionInfo connectionInfo, PSH /// Application arguments the server can see in /// /// Name for remote runspace. - /// + /// A remote Runspace. public static Runspace CreateRunspace(RunspaceConnectionInfo connectionInfo, PSHost host, TypeTable typeTable, PSPrimitiveDictionary applicationArguments, string name) { - if (connectionInfo is not WSManConnectionInfo && - connectionInfo is not NewProcessConnectionInfo && - connectionInfo is not NamedPipeConnectionInfo && - connectionInfo is not SSHConnectionInfo && - connectionInfo is not VMConnectionInfo && - connectionInfo is not ContainerConnectionInfo) - { - throw new NotSupportedException(); - } - if (connectionInfo is WSManConnectionInfo) { RemotingCommandUtil.CheckHostRemotingPrerequisites(); @@ -558,19 +554,21 @@ connectionInfo is not VMConnectionInfo && } /// + /// Creates a remote Runspace. /// - /// - /// - /// + /// The explicit PSHost implementation. + /// It defines connection path to a remote runspace that needs to be created. + /// A remote Runspace. public static Runspace CreateRunspace(PSHost host, RunspaceConnectionInfo connectionInfo) { return CreateRunspace(connectionInfo, host, null); } /// + /// Creates a remote Runspace. /// - /// - /// + /// It defines connection path to a remote runspace that needs to be created. + /// A remote Runspace. public static Runspace CreateRunspace(RunspaceConnectionInfo connectionInfo) { return CreateRunspace(null, connectionInfo); @@ -581,9 +579,20 @@ public static Runspace CreateRunspace(RunspaceConnectionInfo connectionInfo) #region V3 Extensions /// + /// Creates an out-of-process remote Runspace. /// - /// - /// + /// + /// The TypeTable to use while deserializing/serializing remote objects. + /// TypeTable has the following information used by serializer: + /// 1. SerializationMethod + /// 2. SerializationDepth + /// 3. SpecificSerializationProperties + /// + /// TypeTable has the following information used by deserializer: + /// 1. TargetTypeForDeserialization + /// 2. TypeConverter + /// + /// An out-of-process remote Runspace. public static Runspace CreateOutOfProcessRunspace(TypeTable typeTable) { NewProcessConnectionInfo connectionInfo = new NewProcessConnectionInfo(null); @@ -592,10 +601,21 @@ public static Runspace CreateOutOfProcessRunspace(TypeTable typeTable) } /// + /// Creates an out-of-process remote Runspace. /// - /// - /// - /// + /// + /// The TypeTable to use while deserializing/serializing remote objects. + /// TypeTable has the following information used by serializer: + /// 1. SerializationMethod + /// 2. SerializationDepth + /// 3. SpecificSerializationProperties + /// + /// TypeTable has the following information used by deserializer: + /// 1. TargetTypeForDeserialization + /// 2. TypeConverter + /// + /// It represents a PowerShell process that is used for an out-of-process remote Runspace + /// An out-of-process remote Runspace. public static Runspace CreateOutOfProcessRunspace(TypeTable typeTable, PowerShellProcessInstance processInstance) { NewProcessConnectionInfo connectionInfo = new NewProcessConnectionInfo(null) { Process = processInstance }; diff --git a/src/System.Management.Automation/engine/hostifaces/FieldDescription.cs b/src/System.Management.Automation/engine/hostifaces/FieldDescription.cs index 75987cc463a..541b1607df0 100644 --- a/src/System.Management.Automation/engine/hostifaces/FieldDescription.cs +++ b/src/System.Management.Automation/engine/hostifaces/FieldDescription.cs @@ -16,7 +16,7 @@ namespace System.Management.Automation.Host { /// /// Provides a description of a field for use by . - /// + /// /// /// /// It is permitted to subclass @@ -165,7 +165,7 @@ public string Name /// /// A short, human-presentable message to describe and identify the field. If supplied, a typical implementation of - /// will use this value instead of + /// will use this value instead of /// the field name to identify the field to the user. /// /// @@ -174,9 +174,9 @@ public string Name /// /// Note that the special character & (ampersand) may be embedded in the label string to identify the next /// character in the label as a "hot key" (aka "keyboard accelerator") that the - /// implementation may use + /// implementation may use /// to allow the user to quickly set input focus to this field. The implementation of - /// is responsible for parsing + /// is responsible for parsing /// the label string for this special character and rendering it accordingly. /// /// For example, a field named "SSN" might have "&Social Security Number" as it's label. @@ -256,15 +256,15 @@ public string Name } /// - /// Gets and sets the default value, if any, for the implementation of + /// Gets and sets the default value, if any, for the implementation of /// to pre-populate its UI with. This is a PSObject instance so that the value can be serialized, converted, /// manipulated like any pipeline object. /// - /// - /// It is up to the implementer of to decide if it + /// + /// It is up to the implementer of to decide if it /// can make use of the object in its presentation of the fields prompt. /// - /// + /// public PSObject DefaultValue @@ -283,8 +283,8 @@ public string Name } /// - /// Gets the Attribute classes that apply to the field. In the case that - /// is being called from the MSH engine, this will contain the set of prompting attributes that are attached to a + /// Gets the Attribute classes that apply to the field. In the case that + /// is being called from the engine, this will contain the set of prompting attributes that are attached to a /// cmdlet parameter declaration. /// public diff --git a/src/System.Management.Automation/engine/hostifaces/History.cs b/src/System.Management.Automation/engine/hostifaces/History.cs index e1100b97487..3b503f2fade 100644 --- a/src/System.Management.Automation/engine/hostifaces/History.cs +++ b/src/System.Management.Automation/engine/hostifaces/History.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; using System.Management.Automation; using System.Management.Automation.Host; @@ -509,7 +510,7 @@ internal HistoryInfo[] GetEntries(WildcardPattern wildcardpattern, long count, S long id = _countEntriesAdded; for (long i = 0; i <= count - 1;) { - // if buffersize is changed,we have to loop from max entry to min entry thats not cleared + // if buffersize is changed,we have to loop from max entry to min entry that's not cleared if (_capacity != DefaultHistorySize) { if (_countEntriesAdded > _capacity) @@ -580,10 +581,10 @@ internal void ClearEntry(long id) } } - /// + /// /// gets the total number of entries added - /// - ///count of total entries added. + /// + /// count of total entries added. internal int Buffercapacity() { return _capacity; @@ -858,7 +859,7 @@ public class GetHistoryCommand : PSCmdlet /// /// [Parameter(Position = 0, ValueFromPipeline = true)] - [ValidateRangeAttribute((long)1, long.MaxValue)] + [ValidateRange((long)1, long.MaxValue)] public long[] Id { get @@ -886,7 +887,7 @@ public long[] Id /// No of History Entries (starting from last) that are to be displayed. /// [Parameter(Position = 1)] - [ValidateRangeAttribute(0, (int)Int16.MaxValue)] + [ValidateRange(0, (int)Int16.MaxValue)] public int Count { get @@ -1345,7 +1346,7 @@ public class AddHistoryCommand : PSCmdlet /// A Boolean that indicates whether history objects should be /// passed to the next element in the pipeline. /// - [Parameter()] + [Parameter] public SwitchParameter Passthru { get { return _passthru; } @@ -1449,14 +1450,14 @@ void ProcessRecord() // Read StartExecutionTime property object temp = GetPropertyValue(mshObject, "StartExecutionTime"); - if (temp == null || !LanguagePrimitives.TryConvertTo(temp, out DateTime startExecutionTime)) + if (temp == null || !LanguagePrimitives.TryConvertTo(temp, CultureInfo.CurrentCulture, out DateTime startExecutionTime)) { break; } // Read EndExecutionTime property temp = GetPropertyValue(mshObject, "EndExecutionTime"); - if (temp == null || !LanguagePrimitives.TryConvertTo(temp, out DateTime endExecutionTime)) + if (temp == null || !LanguagePrimitives.TryConvertTo(temp, CultureInfo.CurrentCulture, out DateTime endExecutionTime)) { break; } @@ -1470,7 +1471,7 @@ void ProcessRecord() ); } while (false); - // If we are here, an error has occured. + // If we are here, an error has occurred. Exception ex = new InvalidDataException ( @@ -1503,9 +1504,9 @@ private static } } - /// + /// /// This Class implements the Clear History cmdlet - /// + /// [Cmdlet(VerbsCommon.Clear, "History", SupportsShouldProcess = true, DefaultParameterSetName = "IDParameter", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096691")] public class ClearHistoryCommand : PSCmdlet { @@ -1517,7 +1518,7 @@ public class ClearHistoryCommand : PSCmdlet /// [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 { @@ -1541,7 +1542,7 @@ public int[] Id /// Command line name of an entry in the session history. /// [Parameter(ParameterSetName = "CommandLineParameter", HelpMessage = "Specifies the name of a command in the session history")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] CommandLine { @@ -1561,11 +1562,11 @@ public string[] CommandLine /// private string[] _commandline = null; - /// + /// /// Clears the specified number of history entries - /// + /// [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 @@ -1650,8 +1651,8 @@ protected override void ProcessRecord() /// /// Clears the session history based on the id parameter /// takes no parameters - /// Nothing. /// + /// Nothing. private void ClearHistoryByID() { if (_countParameterSpecified && Count < 0) @@ -1758,8 +1759,8 @@ private void ClearHistoryByID() /// /// Clears the session history based on the Commandline parameter /// takes no parameters - /// Nothing. /// + /// Nothing. private void ClearHistoryByCmdLine() { // throw an exception for invalid count values @@ -1821,12 +1822,12 @@ private void ClearHistoryByCmdLine() /// /// Clears the session history based on the input parameter + /// + /// Nothing. /// Id of the entry to be cleared. /// Count of entries to be cleared. /// Cmdline string to be cleared. /// Order of the entries. - /// Nothing. - /// private void ClearHistoryEntries(long id, int count, string cmdline, SwitchParameter newest) { // if cmdline is null,use default parameter set notion. diff --git a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs index b3e29423a83..003625791b1 100644 --- a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs +++ b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs @@ -8,10 +8,9 @@ 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.Security; using System.Text; using System.Text.RegularExpressions; @@ -42,6 +41,8 @@ public static class HostUtilities { #region Internal Access + private static readonly char s_actionIndicator = HostSupportUnicode() ? '\u27a4' : '>'; + private static readonly string s_checkForCommandInCurrentDirectoryScript = @" [System.Diagnostics.DebuggerHidden()] param() @@ -65,56 +66,40 @@ public static class HostUtilities $formatString -f $lastError.TargetObject,"".\$($lastError.TargetObject)"" "; - private static readonly string s_getFuzzyMatchedCommands = @" - [System.Diagnostics.DebuggerHidden()] - param([string] $formatString) + private static readonly List s_suggestions = InitializeSuggestions(); - $formatString -f [string]::Join(', ', (Get-Command $lastError.TargetObject -UseFuzzyMatch | Select-Object -First 10 -Unique -ExpandProperty Name)) - "; + private static bool HostSupportUnicode() + { + // Reference: https://github.com/zkat/supports-unicode/blob/main/src/lib.rs + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Environment.GetEnvironmentVariable("WT_SESSION") is not null || + Environment.GetEnvironmentVariable("TERM_PROGRAM") is "vscode" || + Environment.GetEnvironmentVariable("ConEmuTask") is "{cmd:Cmder}" || + Environment.GetEnvironmentVariable("TERM") is "xterm-256color" or "alacritty"; + } - private static readonly List s_suggestions = InitializeSuggestions(); + string ctype = Environment.GetEnvironmentVariable("LC_ALL") ?? + Environment.GetEnvironmentVariable("LC_CTYPE") ?? + Environment.GetEnvironmentVariable("LANG") ?? + string.Empty; + + return ctype.EndsWith("UTF8") || ctype.EndsWith("UTF-8"); + } private static List InitializeSuggestions() { - var suggestions = new List( - new Hashtable[] - { - NewSuggestion( - id: 1, - category: "Transactions", - matchType: SuggestionMatchType.Command, - rule: "^Start-Transaction", - suggestion: SuggestionStrings.Suggestion_StartTransaction, - enabled: true), - NewSuggestion( - id: 2, - category: "Transactions", - matchType: SuggestionMatchType.Command, - rule: "^Use-Transaction", - suggestion: SuggestionStrings.Suggestion_UseTransaction, - enabled: true), - 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) - }); - - if (ExperimentalFeature.IsEnabled("PSCommandNotFoundSuggestion")) - { - 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)); - } + var suggestions = new List() + { + NewSuggestion( + id: 3, + category: "General", + matchType: SuggestionMatchType.Dynamic, + rule: ScriptBlock.CreateDelayParsedScriptBlock(s_checkForCommandInCurrentDirectoryScript, isProductCode: true), + suggestion: ScriptBlock.CreateDelayParsedScriptBlock(s_createCommandExistsInCurrentDirectoryScript, isProductCode: true), + suggestionArgs: new object[] { SuggestionStrings.Suggestion_CommandExistsInCurrentDirectory_Legacy }, + enabled: true) + }; return suggestions; } @@ -138,16 +123,6 @@ internal static PSObject GetDollarProfile(string allUsersAllHosts, string allUse return returnValue; } - /// - /// Gets an array of commands that can be run sequentially to set $profile and run the profile commands. - /// - /// The id identifying the host or shell used in profile file names. - /// - internal static PSCommand[] GetProfileCommands(string shellId) - { - return HostUtilities.GetProfileCommands(shellId, false); - } - /// /// Gets the object that serves as a value to $profile and the paths on it. /// @@ -309,7 +284,10 @@ internal static string GetMaxLines(string source, int maxLines) internal static List GetSuggestion(Runspace runspace) { - if (!(runspace is LocalRunspace localRunspace)) { return new List(); } + if (!(runspace is LocalRunspace localRunspace)) + { + return new List(); + } // Get the last value of $? bool questionMarkVariableValue = localRunspace.ExecutionContext.QuestionMarkVariableValue; @@ -519,72 +497,6 @@ internal static List GetSuggestion(HistoryInfo lastHistory, object lastE return returnSuggestions; } - /// - /// Remove the GUID from the message if the message is in the pre-defined format. - /// - /// - /// - /// - internal static string RemoveGuidFromMessage(string message, out bool matchPattern) - { - matchPattern = false; - if (string.IsNullOrEmpty(message)) - return message; - - const string pattern = @"^([\d\w]{8}\-[\d\w]{4}\-[\d\w]{4}\-[\d\w]{4}\-[\d\w]{12}:).*"; - Match matchResult = Regex.Match(message, pattern); - if (matchResult.Success) - { - string partToRemove = matchResult.Groups[1].Captures[0].Value; - message = message.Remove(0, partToRemove.Length); - matchPattern = true; - } - - return message; - } - - internal static string RemoveIdentifierInfoFromMessage(string message, out bool matchPattern) - { - matchPattern = false; - if (string.IsNullOrEmpty(message)) - return message; - - const string pattern = @"^([\d\w]{8}\-[\d\w]{4}\-[\d\w]{4}\-[\d\w]{4}\-[\d\w]{12}:\[.*\]:).*"; - Match matchResult = Regex.Match(message, pattern); - if (matchResult.Success) - { - string partToRemove = matchResult.Groups[1].Captures[0].Value; - message = message.Remove(0, partToRemove.Length); - matchPattern = true; - } - - return message; - } - - /// - /// Create suggestion with string rule and suggestion. - /// - /// Identifier for the suggestion. - /// Category for the suggestion. - /// Suggestion match type. - /// Rule to match. - /// Suggestion to return. - /// True if the suggestion is enabled. - /// Hashtable representing the suggestion. - private static Hashtable NewSuggestion(int id, string category, SuggestionMatchType matchType, string rule, string 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; - } - /// /// Create suggestion with string rule and scriptblock suggestion. /// @@ -639,15 +551,6 @@ private static Hashtable NewSuggestion(int id, string category, SuggestionMatchT return result; } - /// - /// Get suggestion text from suggestion scriptblock. - /// - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Need to keep this for legacy reflection based use")] - private static string GetSuggestionText(object suggestion, PSModuleInfo invocationModule) - { - return GetSuggestionText(suggestion, null, invocationModule); - } - /// /// Get suggestion text from suggestion scriptblock with arguments. /// @@ -696,47 +599,19 @@ runspace.ConnectionInfo is VMConnectionInfo || !string.IsNullOrEmpty(sshConnectionInfo.UserName) && !System.Environment.UserName.Equals(sshConnectionInfo.UserName, StringComparison.Ordinal)) { - return string.Format(CultureInfo.InvariantCulture, "[{0}@{1}]: {2}", sshConnectionInfo.UserName, sshConnectionInfo.ComputerName, basePrompt); + return string.Format( + CultureInfo.InvariantCulture, + "[{0}@{1}]: {2}", + sshConnectionInfo.UserName, + sshConnectionInfo.ComputerName, + basePrompt); } - return string.Format(CultureInfo.InvariantCulture, "[{0}]: {1}", runspace.ConnectionInfo.ComputerName, basePrompt); - } - - internal static bool IsProcessInteractive(InvocationInfo invocationInfo) - { -#if CORECLR - return false; -#else - // CommandOrigin != Runspace means it is in a script - if (invocationInfo.CommandOrigin != CommandOrigin.Runspace) - return false; - - // If we don't own the window handle, we've been invoked - // from another process that just calls "PowerShell -Command" - if (System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle == IntPtr.Zero) - return false; - - // If the window has been idle for less than two seconds, - // they're probably still calling "PowerShell -Command" - // but from Start-Process, or the StartProcess API - try - { - System.Diagnostics.Process currentProcess = System.Diagnostics.Process.GetCurrentProcess(); - TimeSpan timeSinceStart = DateTime.Now - currentProcess.StartTime; - TimeSpan idleTime = timeSinceStart - currentProcess.TotalProcessorTime; - - // Making it 2 seconds because of things like delayed prompt - if (idleTime.TotalSeconds > 2) - return true; - } - catch (System.ComponentModel.Win32Exception) - { - // Don't have access to the properties - return false; - } - - return false; -#endif + return string.Format( + CultureInfo.InvariantCulture, + "[{0}]: {1}", + runspace.ConnectionInfo.ComputerName, + basePrompt); } /// @@ -896,6 +771,189 @@ public static Collection InvokeOnRunspace(PSCommand command, Runspace #endregion + #region Feedback Rendering + + /// + /// Render the feedbacks to the specified host. + /// + /// The feedback results. + /// The host to render to. + public static void RenderFeedback(List feedbacks, PSHostUserInterface ui) + { + // Caption style is dimmed bright white with italic effect, used for fixed captions, such as '[' and ']'. + string captionStyle = "\x1b[97;2;3m"; + string italics = "\x1b[3m"; + string nameStyle = PSStyle.Instance.Formatting.FeedbackName; + string textStyle = PSStyle.Instance.Formatting.FeedbackText; + string actionStyle = PSStyle.Instance.Formatting.FeedbackAction; + string ansiReset = PSStyle.Instance.Reset; + + if (!ui.SupportsVirtualTerminal) + { + captionStyle = string.Empty; + italics = string.Empty; + nameStyle = string.Empty; + textStyle = string.Empty; + actionStyle = string.Empty; + ansiReset = string.Empty; + } + + var output = new StringBuilder(); + var chkset = new HashSet(); + + foreach (FeedbackResult entry in feedbacks) + { + output.AppendLine(); + output.Append($"{captionStyle}[{ansiReset}") + .Append($"{nameStyle}{italics}{entry.Name}{ansiReset}") + .Append($"{captionStyle}]{ansiReset}"); + + FeedbackItem item = entry.Item; + chkset.Add(item); + + do + { + RenderText(output, item.Header, textStyle, ansiReset, indent: 2, startOnNewLine: true); + RenderActions(output, item, textStyle, actionStyle, ansiReset); + RenderText(output, item.Footer, textStyle, ansiReset, indent: 2, startOnNewLine: true); + + // A feedback provider may return multiple feedback items, though that may be rare. + item = item.Next; + } + while (item is not null && chkset.Add(item)); + + ui.Write(output.ToString()); + output.Clear(); + chkset.Clear(); + } + + // Feedback section ends with a new line. + ui.WriteLine(); + } + + /// + /// Helper function to render feedback message. + /// + /// The output string builder to write to. + /// The text to be rendered. + /// The style to be used. + /// The ANSI code to reset. + /// The number of spaces for indentation. + /// Indicates whether to start writing from a new line. + internal static void RenderText(StringBuilder output, string text, string style, string ansiReset, int indent, bool startOnNewLine) + { + if (text is null) + { + return; + } + + if (startOnNewLine) + { + // Start writing the text on the next line. + output.AppendLine(); + } + + // Apply the style. + output.Append(style); + + int count = 0; + var trimChars = "\r\n".AsSpan(); + var span = text.AsSpan().Trim(trimChars); + + // This loop renders the text with minimal allocation. + while (true) + { + int index = span.IndexOf('\n'); + var line = index is -1 ? span : span.Slice(0, index); + + if (startOnNewLine || count > 0) + { + output.Append(' ', indent); + } + + output.Append(line.TrimEnd('\r')).AppendLine(); + + // Break out the loop if we are done with the last line. + if (index is -1) + { + break; + } + + // Point to the rest of feedback text. + span = span.Slice(index + 1); + count++; + } + + output.Append(ansiReset); + } + + /// + /// Helper function to render feedback actions. + /// + /// The output string builder to write to. + /// The feedback item to be rendered. + /// The style used for feedback messages. + /// The style used for feedback actions. + /// The ANSI code to reset. + internal static void RenderActions(StringBuilder output, FeedbackItem item, string textStyle, string actionStyle, string ansiReset) + { + if (item.RecommendedActions is null || item.RecommendedActions.Count is 0) + { + return; + } + + List actions = item.RecommendedActions; + if (item.Layout is FeedbackDisplayLayout.Landscape) + { + // Add 4-space indentation and write the indicator. + output.Append($" {textStyle}{s_actionIndicator}{ansiReset} "); + + // Then concatenate the action texts. + for (int i = 0; i < actions.Count; i++) + { + string action = actions[i]; + if (i > 0) + { + output.Append(", "); + } + + output.Append(actionStyle).Append(action).Append(ansiReset); + } + + output.AppendLine(); + } + else + { + int lastIndex = actions.Count - 1; + for (int i = 0; i < actions.Count; i++) + { + string action = actions[i]; + + // Add 4-space indentation and write the indicator, then write the action. + output.Append($" {textStyle}{s_actionIndicator}{ansiReset} "); + + if (action.Contains('\n')) + { + // If the action is a code snippet, properly render it with the right indentation. + RenderText(output, action, actionStyle, ansiReset, indent: 6, startOnNewLine: false); + + // Append an extra line unless it's the last action. + if (i != lastIndex) + { + output.AppendLine(); + } + } + else + { + output.Append(actionStyle).Append(action).Append(ansiReset) + .AppendLine(); + } + } + } + } + + #endregion + #endregion } diff --git a/src/System.Management.Automation/engine/hostifaces/InformationalRecord.cs b/src/System.Management.Automation/engine/hostifaces/InformationalRecord.cs index 3fdba5569c3..f8196167c32 100644 --- a/src/System.Management.Automation/engine/hostifaces/InformationalRecord.cs +++ b/src/System.Management.Automation/engine/hostifaces/InformationalRecord.cs @@ -13,7 +13,7 @@ namespace System.Management.Automation /// A PSInformationalRecord consists of a string Message and the InvocationInfo and pipeline state corresponding /// to the command that created the record. /// - [DataContract()] + [DataContract] public abstract class InformationalRecord { /// @@ -159,7 +159,7 @@ internal virtual void ToPSObjectForRemoting(PSObject psObject) } } - [DataMember()] + [DataMember] private string _message; private InvocationInfo _invocationInfo; @@ -170,7 +170,7 @@ internal virtual void ToPSObjectForRemoting(PSObject psObject) /// /// A warning record in the PSInformationalBuffers. /// - [DataContract()] + [DataContract] public class WarningRecord : InformationalRecord { /// @@ -226,7 +226,7 @@ public string FullyQualifiedWarningId /// /// A debug record in the PSInformationalBuffers. /// - [DataContract()] + [DataContract] public class DebugRecord : InformationalRecord { /// @@ -247,7 +247,7 @@ public DebugRecord(PSObject record) /// /// A verbose record in the PSInformationalBuffers. /// - [DataContract()] + [DataContract] public class VerboseRecord : InformationalRecord { /// diff --git a/src/System.Management.Automation/engine/hostifaces/InternalHost.cs b/src/System.Management.Automation/engine/hostifaces/InternalHost.cs index e9a4b0e86e3..450941b4b63 100644 --- a/src/System.Management.Automation/engine/hostifaces/InternalHost.cs +++ b/src/System.Management.Automation/engine/hostifaces/InternalHost.cs @@ -537,7 +537,10 @@ internal void SetHostRef(PSHost psHost) internal void RevertHostRef() { // nothing to revert if Host reference is not set. - if (!IsHostRefSet) { return; } + if (!IsHostRefSet) + { + return; + } _externalHostRef.Revert(); _internalUIRef.Revert(); diff --git a/src/System.Management.Automation/engine/hostifaces/InternalHostUserInterface.cs b/src/System.Management.Automation/engine/hostifaces/InternalHostUserInterface.cs index 57f3f675ffc..94a25726f7b 100644 --- a/src/System.Management.Automation/engine/hostifaces/InternalHostUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/InternalHostUserInterface.cs @@ -338,13 +338,7 @@ internal void WriteDebugRecord(DebugRecord record) /// Writes the DebugRecord to informational buffers. /// /// DebugRecord. - internal void WriteDebugInfoBuffers(DebugRecord record) - { - if (_informationalBuffers != null) - { - _informationalBuffers.AddDebug(record); - } - } + internal void WriteDebugInfoBuffers(DebugRecord record) => _informationalBuffers?.AddDebug(record); /// /// Helper function for WriteDebugLine. @@ -501,7 +495,7 @@ internal PSInformationalBuffers GetInformationalMessageBuffers() break; case 3: - // No to All means that we want to stop everytime WriteDebug is called. Since No throws an error, I + // No to All means that we want to stop every time WriteDebug is called. Since No throws an error, I // think that ordinarily, the caller will terminate. So I don't think the caller will ever get back // calling WriteDebug again, and thus "No to All" might not be a useful option to have. @@ -538,10 +532,7 @@ public override } // Write to Information Buffers - if (_informationalBuffers != null) - { - _informationalBuffers.AddProgress(record); - } + _informationalBuffers?.AddProgress(record); if (_externalUI == null) { @@ -588,13 +579,7 @@ internal void WriteVerboseRecord(VerboseRecord record) /// Writes the VerboseRecord to informational buffers. /// /// VerboseRecord. - internal void WriteVerboseInfoBuffers(VerboseRecord record) - { - if (_informationalBuffers != null) - { - _informationalBuffers.AddVerbose(record); - } - } + internal void WriteVerboseInfoBuffers(VerboseRecord record) => _informationalBuffers?.AddVerbose(record); /// /// See base class. @@ -631,13 +616,7 @@ internal void WriteWarningRecord(WarningRecord record) /// Writes the WarningRecord to informational buffers. /// /// WarningRecord. - internal void WriteWarningInfoBuffers(WarningRecord record) - { - if (_informationalBuffers != null) - { - _informationalBuffers.AddWarning(record); - } - } + internal void WriteWarningInfoBuffers(WarningRecord record) => _informationalBuffers?.AddWarning(record); /// /// @@ -657,13 +636,7 @@ internal void WriteInformationRecord(InformationRecord record) /// Writes the InformationRecord to informational buffers. /// /// WarningRecord. - internal void WriteInformationInfoBuffers(InformationRecord record) - { - if (_informationalBuffers != null) - { - _informationalBuffers.AddInformation(record); - } - } + internal void WriteInformationInfoBuffers(InformationRecord record) => _informationalBuffers?.AddInformation(record); internal static Type GetFieldType(FieldDescription field) { @@ -954,8 +927,7 @@ private Collection EmulatePromptForMultipleChoice(string caption, defaultStr = hotkeysAndPlainLabels[1, defaultChoice]; } - defaultChoicesBuilder.Append(string.Format(Globalization.CultureInfo.InvariantCulture, - "{0}{1}", prepend, defaultStr)); + defaultChoicesBuilder.Append(Globalization.CultureInfo.InvariantCulture, $"{prepend}{defaultStr}"); prepend = ","; } @@ -963,8 +935,7 @@ private Collection EmulatePromptForMultipleChoice(string caption, if (defaultChoiceKeys.Count == 1) { - defaultPrompt = StringUtil.Format(InternalHostUserInterfaceStrings.DefaultChoice, - defaultChoicesStr); + defaultPrompt = StringUtil.Format(InternalHostUserInterfaceStrings.DefaultChoice, defaultChoicesStr); } else { diff --git a/src/System.Management.Automation/engine/hostifaces/ListModifier.cs b/src/System.Management.Automation/engine/hostifaces/ListModifier.cs index 4723db9a1d7..db089b68333 100644 --- a/src/System.Management.Automation/engine/hostifaces/ListModifier.cs +++ b/src/System.Management.Automation/engine/hostifaces/ListModifier.cs @@ -212,10 +212,7 @@ public void ApplyTo(IList collectionToUpdate) /// The collection to update. public void ApplyTo(object collectionToUpdate) { - if (collectionToUpdate == null) - { - throw new ArgumentNullException(nameof(collectionToUpdate)); - } + ArgumentNullException.ThrowIfNull(collectionToUpdate); collectionToUpdate = PSObject.Base(collectionToUpdate); diff --git a/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs b/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs index 7f10b75525c..5dcc43ce2a7 100644 --- a/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs +++ b/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs @@ -84,10 +84,7 @@ public override PSPrimitiveDictionary GetApplicationPrivateData() { lock (this.SyncRoot) { - if (_applicationPrivateData == null) - { - _applicationPrivateData = new PSPrimitiveDictionary(); - } + _applicationPrivateData ??= new PSPrimitiveDictionary(); } } @@ -240,7 +237,7 @@ protected override Pipeline CoreCreatePipeline(string command, bool addToHistory /// /// Gets the execution context. /// - internal override System.Management.Automation.ExecutionContext GetExecutionContext + internal override ExecutionContext GetExecutionContext { get { @@ -363,7 +360,7 @@ public override Debugger Debugger } } - private static readonly string s_debugPreferenceCachePath = Path.Combine(Path.Combine(Platform.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WindowsPowerShell"), "DebugPreference.clixml"); + private static readonly string s_debugPreferenceCachePath = Path.Combine(Platform.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WindowsPowerShell", "DebugPreference.clixml"); private static readonly object s_debugPreferenceLockObject = new object(); /// @@ -768,10 +765,7 @@ internal void LogEngineHealthEvent(Exception exception, /// internal PipelineThread GetPipelineThread() { - if (_pipelineThread == null) - { - _pipelineThread = new PipelineThread(this.ApartmentState); - } + _pipelineThread ??= new PipelineThread(this.ApartmentState); return _pipelineThread; } @@ -840,18 +834,14 @@ private void DoCloseHelper() if (executionContext != null) { PSHostUserInterface hostUI = executionContext.EngineHostInterface.UI; - if (hostUI != null) - { - hostUI.StopAllTranscribing(); - } + hostUI?.StopAllTranscribing(); } AmsiUtils.Uninitialize(); } // Generate the shutdown event - if (Events != null) - Events.GenerateEvent(PSEngineEvent.Exiting, null, Array.Empty(), null, true, false); + Events?.GenerateEvent(PSEngineEvent.Exiting, null, Array.Empty(), null, true, false); // Stop all running pipelines // Note:Do not perform the Cancel in lock. Reason is @@ -884,7 +874,7 @@ private void DoCloseHelper() return runspaces; }); - // Notify Engine components that that runspace is closing. + // Notify Engine components that runspace is closing. _engine.Context.RunspaceClosingNotification(); // Log engine lifecycle event. @@ -944,7 +934,10 @@ private void DoCloseHelper() private static void CloseOrDisconnectAllRemoteRunspaces(Func> getRunspaces) { List runspaces = getRunspaces(); - if (runspaces.Count == 0) { return; } + if (runspaces.Count == 0) + { + return; + } // whether the close of all remoterunspaces completed using (ManualResetEvent remoteRunspaceCloseCompleted = new ManualResetEvent(false)) @@ -969,7 +962,10 @@ private static void CloseOrDisconnectAllRemoteRunspaces(Func private void StopOrDisconnectAllJobs() { - if (JobRepository.Jobs.Count == 0) { return; } + if (JobRepository.Jobs.Count == 0) + { + return; + } List disconnectRunspaces = new List(); @@ -1242,8 +1238,6 @@ protected override void Dispose(bool disposing) RunspaceOpening = null; } - Platform.RemoveTemporaryDirectory(); - // Dispose the event manager if (this.ExecutionContext != null && this.ExecutionContext.Events != null) { @@ -1274,10 +1268,7 @@ public override void Close() base.Close(); // call base.Close() first to make it stop the pipeline - if (_pipelineThread != null) - { - _pipelineThread.Close(); - } + _pipelineThread?.Close(); } #endregion IDisposable Members @@ -1500,7 +1491,6 @@ private void RaiseOperationCompleteEvent() /// Defines the exception thrown an error loading modules occurs while opening the runspace. It /// contains a list of all of the module errors that have occurred. /// - [Serializable] public class RunspaceOpenModuleLoadException : RuntimeException { #region ctor @@ -1527,7 +1517,7 @@ public RunspaceOpenModuleLoadException(string message) /// Initializes a new instance of ScriptBlockToPowerShellNotSupportedException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. + /// The exception's inner exception. public RunspaceOpenModuleLoadException(string message, Exception innerException) : base(message, innerException) { @@ -1567,27 +1557,11 @@ public PSDataCollection ErrorRecords /// /// Serialization information. /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected RunspaceOpenModuleLoadException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - - /// - /// Populates a with the - /// data needed to serialize the RunspaceOpenModuleLoadException object. - /// - /// The to populate with data. - /// The destination for this serialization. - public override void GetObjectData(SerializationInfo info, StreamingContext context) { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, 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 ab430decb50..93cb65f3f07 100644 --- a/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs +++ b/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs @@ -12,9 +12,6 @@ #endif using System.Threading; using Microsoft.PowerShell.Commands; -using Microsoft.Win32; - -using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Runspaces { @@ -192,7 +189,7 @@ protected override void StartPipelineExecution() } #if !UNIX - if (apartmentState != ApartmentState.Unknown && !Platform.IsNanoServer && !Platform.IsIoT) + if (apartmentState != ApartmentState.Unknown && Platform.IsStaSupported) { invokeThread.SetApartmentState(apartmentState); } @@ -272,61 +269,60 @@ private void SetupInvokeThread(Thread invokeThread, bool changeName) } } - /// + /// /// Helper method for asynchronous invoke - ///Unhandled FlowControl exception if InvocationSettings.ExposeFlowControlExceptions is true. - /// + /// + /// Unhandled FlowControl exception if InvocationSettings.ExposeFlowControlExceptions is true. private FlowControlException InvokeHelper() { FlowControlException flowControlException = null; - PipelineProcessor pipelineProcessor = null; + try { -#if TRANSACTIONS_SUPPORTED - // 2004/11/08-JeffJon - // Transactions will not be supported for the Exchange release - - // Add the transaction to this thread - System.Transactions.Transaction.Current = this.LocalRunspace.ExecutionContext.CurrentTransaction; -#endif // Raise the event for Pipeline.Running RaisePipelineStateEvents(); // Add this pipeline to history RecordPipelineStartTime(); - // Add automatic transcription, but don't transcribe nested commands - if (this.AddToHistory || !IsNested) + // Add automatic transcription when it's NOT a pulse pipeline, but don't transcribe nested commands. + if (!IsPulsePipeline && (AddToHistory || !IsNested)) { - bool needToAddOutDefault = true; - CommandInfo outDefaultCommandInfo = new CmdletInfo("Out-Default", typeof(Microsoft.PowerShell.Commands.OutDefaultCommand), null, null, null); - - foreach (Command command in this.Commands) + foreach (Command command in Commands) { - if (command.IsScript && (!this.IsPulsePipeline)) + if (command.IsScript) { // Transcribe scripts, unless they are the pulse pipeline. - this.Runspace.GetExecutionContext.EngineHostInterface.UI.TranscribeCommand(command.CommandText, null); + Runspace.GetExecutionContext.EngineHostInterface.UI.TranscribeCommand(command.CommandText, invocation: null); } + } + + if (Runspace.GetExecutionContext.EngineHostInterface.UI.IsTranscribing) + { + bool needToAddOutDefault = true; + Command lastCommand = Commands[Commands.Count - 1]; // Don't need to add Out-Default if the pipeline already has it, or we've got a pipeline evaluating - // the PSConsoleHostReadLine command. - if ( - string.Equals(outDefaultCommandInfo.Name, command.CommandText, StringComparison.OrdinalIgnoreCase) || - string.Equals("PSConsoleHostReadLine", command.CommandText, StringComparison.OrdinalIgnoreCase) || - string.Equals("TabExpansion2", command.CommandText, StringComparison.OrdinalIgnoreCase) || - this.IsPulsePipeline) + // the PSConsoleHostReadLine or the TabExpansion2 commands. + if (string.Equals("Out-Default", lastCommand.CommandText, StringComparison.OrdinalIgnoreCase) || + string.Equals("PSConsoleHostReadLine", lastCommand.CommandText, StringComparison.OrdinalIgnoreCase) || + string.Equals("TabExpansion2", lastCommand.CommandText, StringComparison.OrdinalIgnoreCase) || + (lastCommand.CommandInfo is CmdletInfo cmdlet && cmdlet.ImplementingType == typeof(OutDefaultCommand))) { needToAddOutDefault = false; } - } - if (this.Runspace.GetExecutionContext.EngineHostInterface.UI.IsTranscribing) - { if (needToAddOutDefault) { - Command outDefaultCommand = new Command(outDefaultCommandInfo); + var outDefaultCommand = new Command( + new CmdletInfo( + "Out-Default", + typeof(OutDefaultCommand), + helpFile: null, + PSSnapin: null, + context: null)); + outDefaultCommand.Parameters.Add(new CommandParameter("Transcript", true)); outDefaultCommand.Parameters.Add(new CommandParameter("OutVariable", null)); @@ -491,10 +487,7 @@ private FlowControlException InvokeHelper() } PSLocalEventManager eventManager = LocalRunspace.Events as PSLocalEventManager; - if (eventManager != null) - { - eventManager.ProcessPendingActions(); - } + eventManager?.ProcessPendingActions(); // restore the trap state... this.LocalRunspace.ExecutionContext.PropagateExceptionsToEnclosingStatementBlock = oldTrapState; @@ -737,7 +730,7 @@ private void InvokeThreadProc() /// /// Stop the running pipeline. /// - /// If true pipeline is stoped synchronously + /// If true pipeline is stopped synchronously /// else asynchronously. protected override void ImplementStop(bool syncCall) { @@ -976,7 +969,7 @@ private void InitStreams() } /// - /// This method sets streams to their orignal states from execution context. + /// This method sets streams to their original states from execution context. /// This is done when Pipeline is completed/failed/stopped ie., termination state. /// private void ClearStreams() @@ -1165,7 +1158,7 @@ internal PipelineThread(ApartmentState apartmentState) _closed = false; #if !UNIX - if (apartmentState != ApartmentState.Unknown && !Platform.IsNanoServer && !Platform.IsIoT) + if (apartmentState != ApartmentState.Unknown && Platform.IsStaSupported) { _worker.SetApartmentState(apartmentState); } diff --git a/src/System.Management.Automation/engine/hostifaces/MshHost.cs b/src/System.Management.Automation/engine/hostifaces/MshHost.cs index 7ee89d14b12..bfa79e89f13 100644 --- a/src/System.Management.Automation/engine/hostifaces/MshHost.cs +++ b/src/System.Management.Automation/engine/hostifaces/MshHost.cs @@ -7,7 +7,7 @@ namespace System.Management.Automation.Host { /// - /// Defines the properties and facilities providing by an application hosting an MSH . /// /// @@ -15,7 +15,7 @@ namespace System.Management.Automation.Host /// overrides the abstract methods and properties. The hosting application creates an instance of its derived class and /// passes it to the CreateRunspace method. /// - /// From the moment that the instance of the derived class (the "host class") is passed to CreateRunspace, the MSH runtime + /// From the moment that the instance of the derived class (the "host class") is passed to CreateRunspace, the PowerShell runtime /// can call any of the methods of that class. The instance must not be destroyed until after the Runspace is closed. /// /// There is a 1:1 relationship between the instance of the host class and the Runspace instance to which it is passed. In @@ -64,9 +64,9 @@ protected PSHost() /// The name identifier of the hosting application. /// /// - /// + /// /// if ($Host.Name -ieq "ConsoleHost") { write-host "I'm running in the Console Host" } - /// + /// /// public abstract string Name { @@ -79,7 +79,7 @@ public abstract string Name /// /// /// When implementing this member, it should return the product version number for the product - /// that is hosting the Monad engine. + /// that is hosting the PowerShell engine. /// /// /// The version number of the hosting application. @@ -296,6 +296,11 @@ public interface IHostSupportsInteractiveSession /// /// Called by the engine to notify the host that a runspace push has been requested. /// + /// + /// The runspace to push. This runspace must be a remote runspace and + /// not a locally created runspace. + /// + /// The specified runspace is not a remote runspace. /// [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 f4c3aedfb69..201caf3c827 100644 --- a/src/System.Management.Automation/engine/hostifaces/MshHostRawUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/MshHostRawUserInterface.cs @@ -71,7 +71,7 @@ public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "{0},{1}", X, Y); + return string.Create(CultureInfo.InvariantCulture, $"{X},{Y}"); } /// @@ -257,7 +257,7 @@ public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "{0},{1}", Width, Height); + return string.Create(CultureInfo.InvariantCulture, $"{Width},{Height}"); } /// @@ -566,7 +566,7 @@ public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "{0},{1},{2},{3}", VirtualKeyCode, Character, ControlKeyState, KeyDown); + return string.Create(CultureInfo.InvariantCulture, $"{VirtualKeyCode},{Character},{ControlKeyState},{KeyDown}"); } /// /// Overrides @@ -796,7 +796,7 @@ public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "{0},{1} ; {2},{3}", Left, Top, Right, Bottom); + return string.Create(CultureInfo.InvariantCulture, $"{Left},{Top} ; {Right},{Bottom}"); } /// @@ -1024,7 +1024,7 @@ public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "'{0}' {1} {2} {3}", Character, ForegroundColor, BackgroundColor, BufferCellType); + return string.Create(CultureInfo.InvariantCulture, $"'{Character}' {ForegroundColor} {BackgroundColor} {BufferCellType}"); } /// @@ -1059,7 +1059,7 @@ public override /// /// Hash code for this instance. /// - /// + /// public override int GetHashCode() @@ -1150,7 +1150,7 @@ public enum #endregion Ancillary types /// - /// Defines the lowest-level user interface functions that an interactive application hosting an MSH + /// Defines the lowest-level user interface functions that an interactive application hosting PowerShell /// can choose to implement if it wants to /// support any cmdlet that does character-mode interaction with the user. /// @@ -1340,9 +1340,9 @@ public abstract /// Key stroke when a key is pressed. /// /// - /// + /// /// $Host.UI.RawUI.ReadKey() - /// + /// /// /// /// @@ -1370,10 +1370,10 @@ public abstract /// Neither ReadKeyOptions.IncludeKeyDown nor ReadKeyOptions.IncludeKeyUp is specified. /// /// - /// + /// /// $option = [System.Management.Automation.Host.ReadKeyOptions]"IncludeKeyDown"; /// $host.UI.RawUI.ReadKey($option) - /// + /// /// /// /// @@ -1458,11 +1458,11 @@ public abstract /// Provided for clearing regions -- less chatty than passing an array of cells. /// /// - /// + /// /// using System; /// using System.Management.Automation; /// using System.Management.Automation.Host; - /// namespace Microsoft.Samples.MSH.Cmdlet + /// namespace Microsoft.Samples.Cmdlet /// { /// [Cmdlet("Clear","Screen")] /// public class ClearScreen : PSCmdlet @@ -1474,7 +1474,7 @@ public abstract /// } /// } /// } - /// + /// /// /// /// @@ -1687,7 +1687,7 @@ char source /// is null; /// Any string in is null or empty /// - /// + /// /// If a character C takes one BufferCell to display as determined by LengthInBufferCells, /// one BufferCell is allocated with its Character set to C and BufferCellType to BufferCell.Complete. /// On the other hand, if C takes two BufferCell, two adjacent BufferCells on a row in @@ -1701,7 +1701,7 @@ char source /// and , respectively. /// The resulting array is suitable for use with /// and . - /// + /// /// /// /// @@ -1784,7 +1784,7 @@ char source /// /// Creates a 2D array of BufferCells by examining .Character. - /// + /// /// /// /// The number of columns of the resulting array diff --git a/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs b/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs index e6d40c72419..29fc5fa1f7f 100644 --- a/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs @@ -112,10 +112,10 @@ public abstract System.Management.Automation.Host.PSHostRawUserInterface RawUI /// /// The default implementation writes a carriage return to the screen buffer. - /// - /// - /// - /// + /// + /// + /// + /// /// public virtual void WriteLine() { @@ -170,10 +170,10 @@ public virtual void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgro /// /// Writes a line to the "error display" of the host, as opposed to the "output display," which is /// written to by the variants of - /// - /// - /// and - /// + /// + /// + /// and + /// /// /// /// The characters to be written. @@ -232,6 +232,147 @@ public virtual void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgro /// public virtual void WriteInformation(InformationRecord record) { } + private static bool ShouldOutputPlainText(bool isHost, bool? supportsVirtualTerminal) + { + var outputRendering = OutputRendering.PlainText; + + if (supportsVirtualTerminal != false) + { + switch (PSStyle.Instance.OutputRendering) + { + case OutputRendering.Host: + outputRendering = isHost ? OutputRendering.Ansi : OutputRendering.PlainText; + break; + default: + outputRendering = PSStyle.Instance.OutputRendering; + break; + } + } + + return outputRendering == OutputRendering.PlainText; + } + + /// + /// The format styles that are supported by the host. + /// + public enum FormatStyle + { + /// + /// Reset the formatting to the default. + /// + Reset, + + /// + /// Highlight text used in output formatting. + /// + FormatAccent, + + /// + /// Highlight for table headers. + /// + TableHeader, + + /// + /// Highlight for detailed error view. + /// + ErrorAccent, + + /// + /// Style for error messages. + /// + Error, + + /// + /// Style for warning messages. + /// + Warning, + + /// + /// Style for verbose messages. + /// + Verbose, + + /// + /// Style for debug messages. + /// + Debug, + } + + /// + /// Get the ANSI escape sequence for the given format style. + /// + /// + /// The format style to get the escape sequence for. + /// + /// + /// The ANSI escape sequence for the given format style. + /// + public static string GetFormatStyleString(FormatStyle formatStyle) + { + if (PSStyle.Instance.OutputRendering == OutputRendering.PlainText) + { + return string.Empty; + } + + PSStyle psstyle = PSStyle.Instance; + switch (formatStyle) + { + case FormatStyle.Reset: + return psstyle.Reset; + case FormatStyle.FormatAccent: + return psstyle.Formatting.FormatAccent; + case FormatStyle.TableHeader: + return psstyle.Formatting.TableHeader; + case FormatStyle.ErrorAccent: + return psstyle.Formatting.ErrorAccent; + case FormatStyle.Error: + return psstyle.Formatting.Error; + case FormatStyle.Warning: + return psstyle.Formatting.Warning; + case FormatStyle.Verbose: + return psstyle.Formatting.Verbose; + case FormatStyle.Debug: + return psstyle.Formatting.Debug; + default: + return string.Empty; + } + } + + /// + /// Get the appropriate output string based on different criteria. + /// + /// + /// The text to format. + /// + /// + /// True if the host supports virtual terminal. + /// + /// + /// The formatted text. + /// + public static string GetOutputString(string text, bool supportsVirtualTerminal) + { + return GetOutputString(text, isHost: true, supportsVirtualTerminal: supportsVirtualTerminal); + } + + internal static string GetOutputString(string text, bool isHost, bool? supportsVirtualTerminal = null) + { + var sd = new ValueStringDecorated(text); + + if (sd.IsDecorated) + { + var outputRendering = OutputRendering.Ansi; + if (ShouldOutputPlainText(isHost, supportsVirtualTerminal)) + { + outputRendering = OutputRendering.PlainText; + } + + text = sd.ToString(outputRendering); + } + + return text; + } + // Gets the state associated with PowerShell transcription. // // Ideally, this would be associated with the host instance, but remoting recycles host instances @@ -438,7 +579,7 @@ private void CheckSystemTranscript() { if (TranscriptionData.SystemTranscript == null) { - TranscriptionData.SystemTranscript = PSHostUserInterface.GetSystemTranscriptOption(TranscriptionData.SystemTranscript); + TranscriptionData.SystemTranscript = GetSystemTranscriptOption(TranscriptionData.SystemTranscript); if (TranscriptionData.SystemTranscript != null) { LogTranscriptHeader(null, TranscriptionData.SystemTranscript); @@ -620,13 +761,10 @@ internal void TranscribeResult(Runspace sourceRunspace, string resultText) resultText = resultText.TrimEnd(); - if (ExperimentalFeature.IsEnabled("PSAnsiRendering")) + var text = new ValueStringDecorated(resultText); + if (text.IsDecorated) { - var text = new ValueStringDecorated(resultText); - if (text.IsDecorated) - { - resultText = text.ToString(OutputRendering.PlainText); - } + resultText = text.ToString(OutputRendering.PlainText); } foreach (TranscriptionOption transcript in TranscriptionData.Transcripts.Prepend(TranscriptionData.SystemTranscript)) @@ -754,7 +892,7 @@ private void FlushPendingOutput() { // System transcripts can have high contention. Do exponential back-off on writing // if needed. - int delay = new Random().Next(10) + 1; + int delay = Random.Shared.Next(10) + 1; bool written = false; while (!written) @@ -952,10 +1090,7 @@ internal static TranscriptionOption GetSystemTranscriptOption(TranscriptionOptio // This way, multiple runspaces opened by the same process will share the same transcript. lock (s_systemTranscriptLock) { - if (systemTranscript == null) - { - systemTranscript = PSHostUserInterface.GetTranscriptOptionFromSettings(transcription, currentTranscript); - } + systemTranscript ??= PSHostUserInterface.GetTranscriptOptionFromSettings(transcription, currentTranscript); } } @@ -966,7 +1101,7 @@ internal static TranscriptionOption GetSystemTranscriptOption(TranscriptionOptio private static readonly object s_systemTranscriptLock = new object(); private static readonly Lazy s_transcriptionSettingCache = new Lazy( - () => Utils.GetPolicySetting(Utils.SystemWideThenCurrentUserConfig), + static () => Utils.GetPolicySetting(Utils.SystemWideThenCurrentUserConfig), isThreadSafe: true); private static TranscriptionOption GetTranscriptOptionFromSettings(Transcription transcriptConfig, TranscriptionOption currentTranscript) @@ -1032,8 +1167,8 @@ internal static string GetTranscriptPath(string baseDirectory, bool includeDate) // bytes of randomness (2^48 = 2.8e14) would take an attacker about 891 years to guess // a filename (assuming they knew the time the transcript was started). // (5 bytes = 3 years, 4 bytes = about a month) - byte[] randomBytes = new byte[6]; - System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(randomBytes); + Span randomBytes = stackalloc byte[6]; + System.Security.Cryptography.RandomNumberGenerator.Fill(randomBytes); string filename = string.Format( Globalization.CultureInfo.InvariantCulture, "PowerShell_transcript.{0}.{1}.{2:yyyyMMddHHmmss}.txt", @@ -1108,7 +1243,7 @@ internal void FlushContentToDisk() { static Encoding GetPathEncoding(string path) { - using StreamReader reader = new StreamReader(path, Utils.utf8NoBom, detectEncodingFromByteOrderMarks: true); + using StreamReader reader = new StreamReader(path, Encoding.Default, detectEncodingFromByteOrderMarks: true); _ = reader.Read(); return reader.CurrentEncoding; } @@ -1136,7 +1271,7 @@ static Encoding GetPathEncoding(string path) // file permissions. _contentWriter = new StreamWriter( new FileStream(this.Path, FileMode.Append, FileAccess.Write, FileShare.Read), - Utils.utf8NoBom); + Encoding.Default); } _contentWriter.AutoFlush = true; @@ -1159,7 +1294,10 @@ static Encoding GetPathEncoding(string path) /// public void Dispose() { - if (_disposed) { return; } + if (_disposed) + { + return; + } // Wait for any pending output to be flushed to disk so that Stop-Transcript // can be trusted to immediately have all content from that session in the file) @@ -1262,7 +1400,7 @@ internal static void BuildHotkeysAndPlainLabels(Collection ch Text.StringBuilder splitLabel = new Text.StringBuilder(choices[i].Label.Substring(0, andPos), choices[i].Label.Length); if (andPos + 1 < choices[i].Label.Length) { - splitLabel.Append(choices[i].Label.Substring(andPos + 1)); + splitLabel.Append(choices[i].Label.AsSpan(andPos + 1)); hotkeysAndPlainLabels[0, i] = CultureInfo.CurrentCulture.TextInfo.ToUpper(choices[i].Label.AsSpan(andPos + 1, 1).Trim().ToString()); } @@ -1278,7 +1416,7 @@ internal static void BuildHotkeysAndPlainLabels(Collection ch if (string.Equals(hotkeysAndPlainLabels[0, i], "?", StringComparison.Ordinal)) { Exception e = PSTraceSource.NewArgumentException( - string.Format(Globalization.CultureInfo.InvariantCulture, "choices[{0}].Label", i), + string.Create(Globalization.CultureInfo.InvariantCulture, $"choices[{i}].Label"), InternalHostUserInterfaceStrings.InvalidChoiceHotKeyError); throw e; } @@ -1295,7 +1433,7 @@ internal static void BuildHotkeysAndPlainLabels(Collection ch /// /// /// Returns the index into the choices array matching the response string, or -1 if there is no match. - /// + /// internal static int DetermineChoicePicked(string response, Collection choices, string[,] hotkeysAndPlainLabels) { Diagnostics.Assert(choices != null, "choices: expected a value"); diff --git a/src/System.Management.Automation/engine/hostifaces/NativeCultureResolver.cs b/src/System.Management.Automation/engine/hostifaces/NativeCultureResolver.cs deleted file mode 100644 index 74306b822df..00000000000 --- a/src/System.Management.Automation/engine/hostifaces/NativeCultureResolver.cs +++ /dev/null @@ -1,489 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -/********************************************************************++ - -Description: - -Windows Vista and later support non-traditional UI fallback ie., a -user on an Arabic machine can choose either French or English(US) as -UI fallback language. - -CLR does not support this (non-traditional) fallback mechanism. So -the static methods in this class calculate appropriate UI Culture -natively. ConsoleHot uses this API to set correct Thread UICulture. - -Dependent on: -GetThreadPreferredUILanguages -SetThreadPreferredUILanguages - -These methods are available on Windows Vista and later. - ---********************************************************************/ - -using System; -using System.Globalization; -using System.Runtime.InteropServices; -using System.Text; -using Dbg = System.Management.Automation.Diagnostics; -using WORD = System.UInt16; - -namespace Microsoft.PowerShell -{ - /// - /// Custom culture. - /// - internal class VistaCultureInfo : CultureInfo - { - private string[] _fallbacks; - // Cache the immediate parent and immediate fallback - private VistaCultureInfo _parentCI = null; - private object _syncObject = new object(); - - /// - /// Constructs a CultureInfo that keeps track of fallbacks. - /// - /// Name of the culture to construct. - /// - /// ordered,null-delimited list of fallbacks - /// - public VistaCultureInfo(string name, - string[] fallbacks) - : base(name) - { - _fallbacks = fallbacks; - } - - /// - /// Returns Parent culture for the current CultureInfo. - /// If Parent.Name is null or empty, then chooses the immediate fallback - /// If it is not empty, otherwise just returns Parent. - /// - public override CultureInfo Parent - { - get - { - // First traverse the parent hierarchy as established by CLR. - // This is required because there is difference in the parent hierarchy - // between CLR and Windows for Chinese. Ex: Native windows has - // zh-CN->zh-Hans->neutral whereas CLR has zh-CN->zh-CHS->zh-Hans->neutral - if ((base.Parent != null) && (!string.IsNullOrEmpty(base.Parent.Name))) - { - return ImmediateParent; - } - - // Check whether we have any fallback specified - // MUI_MERGE_SYSTEM_FALLBACK | MUI_MERGE_USER_FALLBACK - // returns fallback cultures (specified by the user) - // and also adds neutral culture where appropriate. - // Ex: ja-jp ja en-us en - while ((_fallbacks != null) && (_fallbacks.Length > 0)) - { - string fallback = _fallbacks[0]; - string[] fallbacksForParent = null; - - if (_fallbacks.Length > 1) - { - fallbacksForParent = new string[_fallbacks.Length - 1]; - Array.Copy(_fallbacks, 1, fallbacksForParent, 0, _fallbacks.Length - 1); - } - - try - { - return new VistaCultureInfo(fallback, fallbacksForParent); - } - // if there is any exception constructing the culture..catch..and go to - // the next culture in the list. - catch (ArgumentException) - { - _fallbacks = fallbacksForParent; - } - } - - // no fallbacks..just return base parent - return base.Parent; - } - } - - /// - /// This is called to create the parent culture (as defined by CLR) - /// of the current culture. - /// - private VistaCultureInfo ImmediateParent - { - get - { - if (_parentCI == null) - { - lock (_syncObject) - { - if (_parentCI == null) - { - string parentCulture = base.Parent.Name; - // remove the parentCulture from the m_fallbacks list. - // ie., remove duplicates from the parent hierarchy. - string[] fallbacksForTheParent = null; - if (_fallbacks != null) - { - fallbacksForTheParent = new string[_fallbacks.Length]; - int currentIndex = 0; - foreach (string culture in _fallbacks) - { - if (!parentCulture.Equals(culture, StringComparison.OrdinalIgnoreCase)) - { - fallbacksForTheParent[currentIndex] = culture; - currentIndex++; - } - } - - // There is atleast 1 duplicate in m_fallbacks which was not added to - // fallbacksForTheParent array. Resize the array to take care of this. - if (_fallbacks.Length != currentIndex) - { - Array.Resize(ref fallbacksForTheParent, currentIndex); - } - } - - _parentCI = new VistaCultureInfo(parentCulture, fallbacksForTheParent); - } - } - } - - return _parentCI; - } - } - - /// - /// Clones the custom CultureInfo retaining the fallbacks. - /// - /// Cloned custom CultureInfo. - public override object Clone() - { - return new VistaCultureInfo(base.Name, _fallbacks); - } - } - - /// - /// Static wrappers to get User chosen UICulture (for Vista and later) - /// - internal static class NativeCultureResolver - { - private static CultureInfo s_uiCulture = null; - private static CultureInfo s_culture = null; - private static object s_syncObject = new object(); - - /// - /// Gets the UICulture to be used by console host. - /// - internal static CultureInfo UICulture - { - get - { - if (s_uiCulture == null) - { - lock (s_syncObject) - { - if (s_uiCulture == null) - { - s_uiCulture = GetUICulture(); - } - } - } - - return (CultureInfo)s_uiCulture.Clone(); - } - } - - internal static CultureInfo Culture - { - get - { - if (s_culture == null) - { - lock (s_syncObject) - { - if (s_culture == null) - { - s_culture = GetCulture(); - } - } - } - - return s_culture; - } - } - - internal static CultureInfo GetUICulture() - { - return GetUICulture(true); - } - - internal static CultureInfo GetCulture() - { - return GetCulture(true); - } - - internal static CultureInfo GetUICulture(bool filterOutNonConsoleCultures) - { - if (!IsVistaAndLater()) - { - s_uiCulture = EmulateDownLevel(); - return s_uiCulture; - } - - // We are running on Vista - string langBuffer = GetUserPreferredUILangs(filterOutNonConsoleCultures); - if (!string.IsNullOrEmpty(langBuffer)) - { - try - { - string[] fallbacks = langBuffer.Split('\0', StringSplitOptions.RemoveEmptyEntries); - string fallback = fallbacks[0]; - string[] fallbacksForParent = null; - - if (fallbacks.Length > 1) - { - fallbacksForParent = new string[fallbacks.Length - 1]; - Array.Copy(fallbacks, 1, fallbacksForParent, 0, fallbacks.Length - 1); - } - - s_uiCulture = new VistaCultureInfo(fallback, fallbacksForParent); - return s_uiCulture; - } - catch (ArgumentException) - { - } - } - - s_uiCulture = EmulateDownLevel(); - return s_uiCulture; - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("GoldMan", "#pw17903:UseOfLCID", Justification = "In XP and below GetUserDefaultLocaleName is not available")] - internal static CultureInfo GetCulture(bool filterOutNonConsoleCultures) - { - CultureInfo returnValue; - try - { - if (!IsVistaAndLater()) - { - int lcid = GetUserDefaultLCID(); - returnValue = new CultureInfo(lcid); - } - else - { - // Vista and above - StringBuilder name = new StringBuilder(16); - if (0 == GetUserDefaultLocaleName(name, 16)) - { - // ther is an error retrieving the culture, - // just use the current thread's culture - returnValue = CultureInfo.CurrentCulture; - } - else - { - returnValue = new CultureInfo(name.ToString().Trim()); - } - } - - if (filterOutNonConsoleCultures) - { - // filter out languages that console cannot display.. - // Sometimes GetConsoleFallbackUICulture returns neutral cultures - // like "en" on "ar-SA". However neutral culture cannot be - // assigned as CurrentCulture. CreateSpecificCulture fixes - // this problem. - returnValue = CultureInfo.CreateSpecificCulture( - returnValue.GetConsoleFallbackUICulture().Name); - } - } - catch (ArgumentException) - { - // if there is any exception retrieving the - // culture, just use the current thread's culture. - returnValue = CultureInfo.CurrentCulture; - } - - return returnValue; - } - - [DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Unicode)] - internal static extern WORD GetUserDefaultUILanguage(); - - /// - /// Constructs CultureInfo object without considering any Vista and later - /// custom culture fallback logic. - /// - /// A CultureInfo object. - [System.Diagnostics.CodeAnalysis.SuppressMessage("GoldMan", "#pw17903:UseOfLCID", Justification = "This is only called In XP and below where GetUserDefaultLocaleName is not available, or as a fallback when GetThreadPreferredUILanguages fails")] - private static CultureInfo EmulateDownLevel() - { - // GetConsoleFallbackUICulture is not required. - // This is retained in order not to break existing code. - ushort langId = NativeCultureResolver.GetUserDefaultUILanguage(); - CultureInfo ci = new CultureInfo((int)langId); - return ci.GetConsoleFallbackUICulture(); - } - - /// - /// Checks if the current operating system is Vista or later. - /// - /// - /// true, if vista and above - /// false, otherwise. - /// - private static bool IsVistaAndLater() - { - // The version number is obtained from MSDN - // 4 - Windows NT 4.0, Windows Me, Windows 98, or Windows 95. - // 5 - Windows Server 2003 R2, Windows Server 2003, Windows XP, or Windows 2000. - // 6 - Windows Vista or Windows Server "Longhorn". - - if (Environment.OSVersion.Version.Major >= 6) - { - return true; - } - - return false; - } - - /// - /// This method is called on vista and above. - /// Using GetThreadPreferredUILanguages this method gets - /// the UI languages a user has chosen. - /// - /// - /// List of ThreadPreferredUILanguages. - /// - /// - /// This method will work only on Vista and later. - /// - private static string GetUserPreferredUILangs(bool filterOutNonConsoleCultures) - { - long numberLangs = 0; - int bufferSize = 0; - string returnval = string.Empty; - - if (filterOutNonConsoleCultures) - { - // Filter out languages that do not support console. - // The third parameter should be null otherwise this API will not - // set Console CodePage filter. - // The MSDN documentation does not call this out explicitly. Opened - // Bug 950 (Windows Developer Content) to track this. - if (!SetThreadPreferredUILanguages(s_MUI_CONSOLE_FILTER, null, IntPtr.Zero)) - { - return returnval; - } - } - - // calculate buffer size required - // MUI_MERGE_SYSTEM_FALLBACK | MUI_MERGE_USER_FALLBACK - // returns fallback cultures (specified by the user) - // and also adds neutral culture where appropriate. - // Ex: ja-jp ja en-us en - if (!GetThreadPreferredUILanguages( - s_MUI_LANGUAGE_NAME | s_MUI_MERGE_SYSTEM_FALLBACK | s_MUI_MERGE_USER_FALLBACK, - out numberLangs, - null, - out bufferSize)) - { - return returnval; - } - - // calculate space required to store output. - // StringBuilder will not work for this case as CLR - // does not copy the entire string if there are delimiter ('\0') - // in the middle of a string. - byte[] langBufferPtr = new byte[bufferSize * 2]; - - // Now get the actual value - if (!GetThreadPreferredUILanguages( - s_MUI_LANGUAGE_NAME | s_MUI_MERGE_SYSTEM_FALLBACK | s_MUI_MERGE_USER_FALLBACK, - out numberLangs, - langBufferPtr, // Pointer to a buffer in which this function retrieves an ordered, null-delimited list. - out bufferSize)) - { - return returnval; - } - - try - { - string langBuffer = Encoding.Unicode.GetString(langBufferPtr); - returnval = langBuffer.Trim().ToLowerInvariant(); - return returnval; - } - catch (ArgumentNullException) - { - } - catch (System.Text.DecoderFallbackException) - { - } - - return returnval; - } - - #region Dll Import data - - /// - /// Returns the locale identifier for the user default locale. - /// - /// - /// - /// This function can return data from custom locales. Locales are not - /// guaranteed to be the same from computer to computer or between runs - /// of an application. If your application must persist or transmit data, - /// see Using Persistent Locale Data. - /// Applications that are intended to run only on Windows Vista and later - /// should use GetUserDefaultLocaleName in preference to this function. - /// GetUserDefaultLocaleName provides good support for supplemental locales. - /// However, GetUserDefaultLocaleName is not supported for versions of Windows - /// prior to Windows Vista. - /// - [DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Unicode)] - private static extern int GetUserDefaultLCID(); - - /// - /// Retrieves the user default locale name. - /// - /// - /// - /// - /// Returns the size of the buffer containing the locale name, including - /// the terminating null character, if successful. The function returns 0 - /// if it does not succeed. To get extended error information, the application - /// can call GetLastError. Possible returns from GetLastError - /// include ERR_INSUFFICIENT_BUFFER. - /// - /// - /// - [DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Unicode)] - private static extern int GetUserDefaultLocaleName( - [MarshalAs(UnmanagedType.LPWStr)] - StringBuilder lpLocaleName, - int cchLocaleName); - - [DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Unicode)] - private static extern bool SetThreadPreferredUILanguages(int dwFlags, - StringBuilder pwszLanguagesBuffer, - IntPtr pulNumLanguages); - - [DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Unicode)] - private static extern bool GetThreadPreferredUILanguages(int dwFlags, - out long pulNumLanguages, - [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] - byte[] pwszLanguagesBuffer, - out int pcchLanguagesBuffer); - - [DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Unicode)] - internal static extern Int16 SetThreadUILanguage(Int16 langId); - - // private static int MUI_LANGUAGE_ID = 0x4; - private static int s_MUI_LANGUAGE_NAME = 0x8; - private static int s_MUI_CONSOLE_FILTER = 0x100; - private static int s_MUI_MERGE_USER_FALLBACK = 0x20; - private static int s_MUI_MERGE_SYSTEM_FALLBACK = 0x10; - - #endregion - } -} diff --git a/src/System.Management.Automation/engine/hostifaces/PSCommand.cs b/src/System.Management.Automation/engine/hostifaces/PSCommand.cs index 77bf704f383..6db40850381 100644 --- a/src/System.Management.Automation/engine/hostifaces/PSCommand.cs +++ b/src/System.Management.Automation/engine/hostifaces/PSCommand.cs @@ -77,7 +77,7 @@ internal PSCommand(Command command) /// current state. /// /// - /// A PSCommand instance with added. + /// A PSCommand instance with added. /// /// /// This method is not thread safe. @@ -89,13 +89,10 @@ public PSCommand AddCommand(string command) { if (command == null) { - throw PSTraceSource.NewArgumentNullException("cmdlet"); + throw PSTraceSource.NewArgumentNullException(nameof(command)); } - if (_owner != null) - { - _owner.AssertChangesAreAccepted(); - } + _owner?.AssertChangesAreAccepted(); _currentCommand = new Command(command, false); _commands.Add(_currentCommand); @@ -136,10 +133,7 @@ public PSCommand AddCommand(string cmdlet, bool useLocalScope) throw PSTraceSource.NewArgumentNullException(nameof(cmdlet)); } - if (_owner != null) - { - _owner.AssertChangesAreAccepted(); - } + _owner?.AssertChangesAreAccepted(); _currentCommand = new Command(cmdlet, false, useLocalScope); _commands.Add(_currentCommand); @@ -151,15 +145,15 @@ public PSCommand AddCommand(string cmdlet, bool useLocalScope) /// Add a piece of script to construct a command pipeline. /// For example, to construct a command string "get-process | foreach { $_.Name }" /// - /// PSCommand command = new PSCommand("get-process"). - /// AddCommand("foreach { $_.Name }", true); + /// PSCommand command = new PSCommand("get-process") + /// .AddScript("foreach { $_.Name }", true); /// /// /// /// A string representing the script. /// /// - /// A PSCommand instance with added. + /// A PSCommand instance with added. /// /// /// This method is not thread-safe. @@ -178,10 +172,7 @@ public PSCommand AddScript(string script) throw PSTraceSource.NewArgumentNullException(nameof(script)); } - if (_owner != null) - { - _owner.AssertChangesAreAccepted(); - } + _owner?.AssertChangesAreAccepted(); _currentCommand = new Command(script, true); _commands.Add(_currentCommand); @@ -193,8 +184,8 @@ public PSCommand AddScript(string script) /// Add a piece of script to construct a command pipeline. /// For example, to construct a command string "get-process | foreach { $_.Name }" /// - /// PSCommand command = new PSCommand("get-process"). - /// AddCommand("foreach { $_.Name }", true); + /// PSCommand command = new PSCommand("get-process") + /// .AddScript("foreach { $_.Name }", true); /// /// /// @@ -204,7 +195,7 @@ public PSCommand AddScript(string script) /// if true local scope is used to run the script command. /// /// - /// A PSCommand instance with added. + /// A PSCommand instance with added. /// /// /// This method is not thread-safe. @@ -223,10 +214,7 @@ public PSCommand AddScript(string script, bool useLocalScope) throw PSTraceSource.NewArgumentNullException(nameof(script)); } - if (_owner != null) - { - _owner.AssertChangesAreAccepted(); - } + _owner?.AssertChangesAreAccepted(); _currentCommand = new Command(script, true, useLocalScope); _commands.Add(_currentCommand); @@ -261,10 +249,7 @@ public PSCommand AddCommand(Command command) throw PSTraceSource.NewArgumentNullException(nameof(command)); } - if (_owner != null) - { - _owner.AssertChangesAreAccepted(); - } + _owner?.AssertChangesAreAccepted(); _currentCommand = command; _commands.Add(_currentCommand); @@ -276,8 +261,9 @@ public PSCommand AddCommand(Command command) /// Add a parameter to the last added command. /// For example, to construct a command string "get-process | select-object -property name" /// - /// PSCommand command = new PSCommand("get-process"). - /// AddCommand("select-object").AddParameter("property","name"); + /// PSCommand command = new PSCommand("get-process") + /// .AddCommand("select-object") + /// .AddParameter("property", "name"); /// /// /// @@ -308,10 +294,7 @@ public PSCommand AddParameter(string parameterName, object value) new object[] { "PSCommand" }); } - if (_owner != null) - { - _owner.AssertChangesAreAccepted(); - } + _owner?.AssertChangesAreAccepted(); _currentCommand.Parameters.Add(parameterName, value); return this; @@ -321,8 +304,9 @@ public PSCommand AddParameter(string parameterName, object value) /// Adds a switch parameter to the last added command. /// For example, to construct a command string "get-process | sort-object -descending" /// - /// PSCommand command = new PSCommand("get-process"). - /// AddCommand("sort-object").AddParameter("descending"); + /// PSCommand command = new PSCommand("get-process") + /// .AddCommand("sort-object") + /// .AddParameter("descending"); /// /// /// @@ -350,10 +334,7 @@ public PSCommand AddParameter(string parameterName) new object[] { "PSCommand" }); } - if (_owner != null) - { - _owner.AssertChangesAreAccepted(); - } + _owner?.AssertChangesAreAccepted(); _currentCommand.Parameters.Add(parameterName, true); return this; @@ -370,10 +351,7 @@ internal PSCommand AddParameter(CommandParameter parameter) new object[] { "PSCommand" }); } - if (_owner != null) - { - _owner.AssertChangesAreAccepted(); - } + _owner?.AssertChangesAreAccepted(); _currentCommand.Parameters.Add(parameter); return this; @@ -383,8 +361,9 @@ internal PSCommand AddParameter(CommandParameter parameter) /// Adds an argument to the last added command. /// For example, to construct a command string "get-process | select-object name" /// - /// PSCommand command = new PSCommand("get-process"). - /// AddCommand("select-object").AddParameter("name"); + /// PSCommand command = new PSCommand("get-process") + /// .AddCommand("select-object") + /// .AddArgument("name"); /// /// This will add the value "name" to the positional parameter list of "select-object" /// cmdlet. When the command is invoked, this value will get bound to positional parameter 0 @@ -412,10 +391,7 @@ public PSCommand AddArgument(object value) new object[] { "PSCommand" }); } - if (_owner != null) - { - _owner.AssertChangesAreAccepted(); - } + _owner?.AssertChangesAreAccepted(); _currentCommand.Parameters.Add(null, value); return this; diff --git a/src/System.Management.Automation/engine/hostifaces/PSDataCollection.cs b/src/System.Management.Automation/engine/hostifaces/PSDataCollection.cs index d51c96a33e0..eb1a796d0e7 100644 --- a/src/System.Management.Automation/engine/hostifaces/PSDataCollection.cs +++ b/src/System.Management.Automation/engine/hostifaces/PSDataCollection.cs @@ -116,7 +116,6 @@ internal DataAddingEventArgs(Guid psInstanceId, object itemAdded) /// build /// Thread Safe buffer used with PowerShell Hosting interfaces. /// - [Serializable] public class PSDataCollection : IList, ICollection, IEnumerable, IList, ICollection, IEnumerable, IDisposable, ISerializable { #region Private Data @@ -553,11 +552,8 @@ public void Complete() // raise the events outside of the lock. if (raiseEvents) { - if (_readWaitHandle != null) - { - // unblock any readers waiting on the handle - _readWaitHandle.Set(); - } + // unblock any readers waiting on the handle + _readWaitHandle?.Set(); // A temporary variable is used as the Completed may // reach null (because of -='s) after the null check @@ -798,10 +794,7 @@ public void Clear() { lock (SyncObject) { - if (_data != null) - { - _data.Clear(); - } + _data?.Clear(); } } @@ -1322,12 +1315,9 @@ internal WaitHandle WaitHandle { lock (SyncObject) { - if (_readWaitHandle == null) - { - // Create the handle signaled if there are objects in the buffer - // or the buffer has been closed. - _readWaitHandle = new ManualResetEvent(_data.Count > 0 || !_isOpen); - } + // Create the handle signaled if there are objects in the buffer + // or the buffer has been closed. + _readWaitHandle ??= new ManualResetEvent(_data.Count > 0 || !_isOpen); } } @@ -1522,7 +1512,7 @@ internal void InternalAddRange(Guid psInstanceId, ICollection collection) { InsertItem(psInstanceId, _data.Count, (T)o); - // set raise events if atleast one item is + // set raise events if at least one item is // added. raiseEvents = true; } @@ -1557,14 +1547,14 @@ internal void DecrementRef() Dbg.Assert(_refCount > 0, "RefCount cannot be <= 0"); _refCount--; - if (_refCount != 0 && (!_blockingEnumerator || _refCount != 1)) return; - - // release threads blocked on waithandle - if (_readWaitHandle != null) + if (_refCount != 0 && (!_blockingEnumerator || _refCount != 1)) { - _readWaitHandle.Set(); + return; } + // release threads blocked on waithandle + _readWaitHandle?.Set(); + // release any threads to notify refCount is 0. Enumerator // blocks on this syncObject and it needs to be notified // when the count becomes 0. @@ -1795,10 +1785,7 @@ protected void Dispose(bool disposing) _readWaitHandle = null; } - if (_data != null) - { - _data.Clear(); - } + _data?.Clear(); } } } @@ -2099,65 +2086,35 @@ internal PSDataCollection Debug /// The item is added to the buffer along with PowerShell InstanceId. /// /// - internal void AddProgress(ProgressRecord item) - { - if (progress != null) - { - progress.InternalAdd(_psInstanceId, item); - } - } + internal void AddProgress(ProgressRecord item) => progress?.InternalAdd(_psInstanceId, item); /// /// Adds item to the verbose buffer. /// The item is added to the buffer along with PowerShell InstanceId. /// /// - internal void AddVerbose(VerboseRecord item) - { - if (verbose != null) - { - verbose.InternalAdd(_psInstanceId, item); - } - } + internal void AddVerbose(VerboseRecord item) => verbose?.InternalAdd(_psInstanceId, item); /// /// Adds item to the debug buffer. /// The item is added to the buffer along with PowerShell InstanceId. /// /// - internal void AddDebug(DebugRecord item) - { - if (debug != null) - { - debug.InternalAdd(_psInstanceId, item); - } - } + internal void AddDebug(DebugRecord item) => debug?.InternalAdd(_psInstanceId, item); /// /// Adds item to the warning buffer. /// The item is added to the buffer along with PowerShell InstanceId. /// /// - internal void AddWarning(WarningRecord item) - { - if (Warning != null) - { - Warning.InternalAdd(_psInstanceId, item); - } - } + internal void AddWarning(WarningRecord item) => Warning?.InternalAdd(_psInstanceId, item); /// /// Adds item to the information buffer. /// The item is added to the buffer along with PowerShell InstanceId. /// /// - internal void AddInformation(InformationRecord item) - { - if (Information != null) - { - Information.InternalAdd(_psInstanceId, item); - } - } + internal void AddInformation(InformationRecord item) => Information?.InternalAdd(_psInstanceId, item); #endregion } diff --git a/src/System.Management.Automation/engine/hostifaces/PSTask.cs b/src/System.Management.Automation/engine/hostifaces/PSTask.cs index 5f05d267fcd..c56225f3bb7 100644 --- a/src/System.Management.Automation/engine/hostifaces/PSTask.cs +++ b/src/System.Management.Automation/engine/hostifaces/PSTask.cs @@ -68,6 +68,7 @@ protected override void InitializePowershell() _powershell.Streams.Warning.DataAdded += (sender, args) => HandleWarningData(); _powershell.Streams.Verbose.DataAdded += (sender, args) => HandleVerboseData(); _powershell.Streams.Debug.DataAdded += (sender, args) => HandleDebugData(); + _powershell.Streams.Progress.DataAdded += (sender, args) => HandleProgressData(); _powershell.Streams.Information.DataAdded += (sender, args) => HandleInformationData(); // State change handler @@ -132,6 +133,15 @@ private void HandleInformationData() } } + private void HandleProgressData() + { + foreach (var item in _powershell.Streams.Progress.ReadAll()) + { + _dataStreamWriter.Add( + new PSStreamObject(PSStreamObjectType.Progress, item)); + } + } + #endregion #region Event handlers @@ -486,13 +496,7 @@ public void Start(Runspace runspace) /// /// Signals the running task to stop. /// - public void SignalStop() - { - if (_powershell != null) - { - _powershell.BeginStop(null, null); - } - } + public void SignalStop() => _powershell?.BeginStop(null, null); #endregion } @@ -908,7 +912,7 @@ private void CheckForComplete() private Runspace GetRunspace(int taskId) { - var runspaceName = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", PSTask.RunspaceName, taskId); + var runspaceName = string.Create(CultureInfo.InvariantCulture, $"{PSTask.RunspaceName}:{taskId}"); if (_useRunspacePool && _runspacePool.TryDequeue(out Runspace runspace)) { @@ -932,8 +936,23 @@ private Runspace GetRunspace(int taskId) // Create and initialize a new Runspace var iss = InitialSessionState.CreateDefault2(); - iss.LanguageMode = (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) - ? PSLanguageMode.ConstrainedLanguage : PSLanguageMode.FullLanguage; + switch (SystemPolicy.GetSystemLockdownPolicy()) + { + case SystemEnforcementMode.Enforce: + iss.LanguageMode = PSLanguageMode.ConstrainedLanguage; + break; + + case SystemEnforcementMode.Audit: + // In audit mode, CL restrictions are not enforced and instead audit + // log entries are created. + iss.LanguageMode = PSLanguageMode.ConstrainedLanguage; + break; + + case SystemEnforcementMode.None: + iss.LanguageMode = PSLanguageMode.FullLanguage; + break; + } + runspace = RunspaceFactory.CreateRunspace(iss); runspace.Name = runspaceName; _activeRunspaces.TryAdd(runspace.Id, runspace); @@ -1526,12 +1545,9 @@ public Debugger Debugger { get { - if (_jobDebuggerWrapper == null) - { - _jobDebuggerWrapper = new PSTaskChildDebugger( - _task.Debugger, - this.Name); - } + _jobDebuggerWrapper ??= new PSTaskChildDebugger( + _task.Debugger, + this.Name); return _jobDebuggerWrapper; } diff --git a/src/System.Management.Automation/engine/hostifaces/Pipeline.cs b/src/System.Management.Automation/engine/hostifaces/Pipeline.cs index 874691852cb..66ebf7d0287 100644 --- a/src/System.Management.Automation/engine/hostifaces/Pipeline.cs +++ b/src/System.Management.Automation/engine/hostifaces/Pipeline.cs @@ -16,7 +16,6 @@ namespace System.Management.Automation.Runspaces /// Defines exception which is thrown when state of the pipeline is different /// from expected state. /// - [Serializable] public class InvalidPipelineStateException : SystemException { /// @@ -86,9 +85,10 @@ internal InvalidPipelineStateException(string message, PipelineState currentStat /// The that contains contextual information /// about the source or destination. /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] private InvalidPipelineStateException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion @@ -445,7 +445,7 @@ internal void SetHadErrors(bool status) /// /// This flag is used to force the redirection. By default it is false to maintain compatibility with /// V1, but the V2 hosting interface (PowerShell class) sets this flag to true to ensure the global - /// error output pipe is always set and $ErrorActionPreference when invoking the Pipeline. + /// error output pipe is always set and $ErrorActionPreference is checked when invoking the Pipeline. /// internal bool RedirectShellErrorOutputPipe { get; set; } = false; diff --git a/src/System.Management.Automation/engine/hostifaces/PowerShell.cs b/src/System.Management.Automation/engine/hostifaces/PowerShell.cs index 3829634c8c8..503d8e075b6 100644 --- a/src/System.Management.Automation/engine/hostifaces/PowerShell.cs +++ b/src/System.Management.Automation/engine/hostifaces/PowerShell.cs @@ -31,7 +31,6 @@ namespace System.Management.Automation /// Defines exception which is thrown when state of the PowerShell is different /// from the expected state. /// - [Serializable] public class InvalidPowerShellStateException : SystemException { /// @@ -97,10 +96,11 @@ internal InvalidPowerShellStateException(PSInvocationState currentState) /// The that contains contextual information /// about the source or destination. /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected InvalidPowerShellStateException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion @@ -762,10 +762,7 @@ internal void InitForRemotePipeline(CommandCollection command, ObjectStreamBase // create the client remote powershell for remoting // communications - if (RemotePowerShell == null) - { - RemotePowerShell = new ClientRemotePowerShell(this, ((RunspacePool)_rsConnection).RemoteRunspacePoolInternal); - } + RemotePowerShell ??= new ClientRemotePowerShell(this, ((RunspacePool)_rsConnection).RemoteRunspacePoolInternal); // If we get here, we don't call 'Invoke' or any of it's friends on 'this', instead we serialize 'this' in PowerShell.ToPSObjectForRemoting. // Without the following two steps, we'll be missing the 'ExtraCommands' on the serialized instance of 'this'. @@ -804,10 +801,7 @@ internal void InitForRemotePipelineConnect(ObjectStreamBase inputstream, ObjectS RedirectShellErrorOutputPipe = redirectShellErrorOutputPipe; - if (RemotePowerShell == null) - { - RemotePowerShell = new ClientRemotePowerShell(this, ((RunspacePool)_rsConnection).RemoteRunspacePoolInternal); - } + RemotePowerShell ??= new ClientRemotePowerShell(this, ((RunspacePool)_rsConnection).RemoteRunspacePoolInternal); if (!RemotePowerShell.Initialized) { @@ -952,9 +946,11 @@ private static PowerShell Create(bool isNested, PSCommand psCommand, Collection< /// /// Add a cmdlet to construct a command pipeline. - /// For example, to construct a command string "get-process | sort-object", + /// For example, to construct a command string "Get-Process | Sort-Object", /// - /// PowerShell shell = PowerShell.Create("get-process").AddCommand("sort-object"); + /// PowerShell shell = PowerShell.Create() + /// .AddCommand("Get-Process") + /// .AddCommand("Sort-Object"); /// /// /// @@ -990,9 +986,11 @@ public PowerShell AddCommand(string cmdlet) /// /// Add a cmdlet to construct a command pipeline. - /// For example, to construct a command string "get-process | sort-object", + /// For example, to construct a command string "Get-Process | Sort-Object", /// - /// PowerShell shell = PowerShell.Create("get-process").AddCommand("sort-object"); + /// PowerShell shell = PowerShell.Create() + /// .AddCommand("Get-Process", true) + /// .AddCommand("Sort-Object", true); /// /// /// @@ -1031,10 +1029,10 @@ public PowerShell AddCommand(string cmdlet, bool useLocalScope) /// /// Add a piece of script to construct a command pipeline. - /// For example, to construct a command string "get-process | foreach { $_.Name }" + /// For example, to construct a command string "Get-Process | ForEach-Object { $_.Name }" /// - /// PowerShell shell = PowerShell.Create("get-process"). - /// AddCommand("foreach { $_.Name }", true); + /// PowerShell shell = PowerShell.Create() + /// .AddScript("Get-Process | ForEach-Object { $_.Name }"); /// /// /// @@ -1070,10 +1068,10 @@ public PowerShell AddScript(string script) /// /// Add a piece of script to construct a command pipeline. - /// For example, to construct a command string "get-process | foreach { $_.Name }" + /// For example, to construct a command string "Get-Process | ForEach-Object { $_.Name }" /// - /// PowerShell shell = PowerShell.Create("get-process"). - /// AddCommand("foreach { $_.Name }", true); + /// PowerShell shell = PowerShell.Create() + /// .AddScript("Get-Process | ForEach-Object { $_.Name }", true); /// /// /// @@ -1179,10 +1177,11 @@ public PowerShell AddCommand(CommandInfo commandInfo) /// /// Add a parameter to the last added command. - /// For example, to construct a command string "get-process | select-object -property name" + /// For example, to construct a command string "Get-Process | Select-Object -Property Name" /// - /// PowerShell shell = PowerShell.Create("get-process"). - /// AddCommand("select-object").AddParameter("property","name"); + /// PowerShell shell = PowerShell.Create() + /// .AddCommand("Get-Process") + /// .AddCommand("Select-Object").AddParameter("Property", "Name"); /// /// /// @@ -1384,10 +1383,11 @@ public PowerShell AddParameters(IDictionary parameters) /// /// Adds an argument to the last added command. - /// For example, to construct a command string "get-process | select-object name" + /// For example, to construct a command string "Get-Process | Select-Object Name" /// - /// PowerShell shell = PowerShell.Create("get-process"). - /// AddCommand("select-object").AddParameter("name"); + /// PowerShell shell = PowerShell.Create() + /// .AddCommand("Get-Process") + /// .AddCommand("Select-Object").AddArgument("Name"); /// /// This will add the value "name" to the positional parameter list of "select-object" /// cmdlet. When the command is invoked, this value will get bound to positional parameter 0 @@ -2236,10 +2236,7 @@ internal void InvokeWithDebugger( { if (addToHistory) { - if (settings == null) - { - settings = new PSInvocationSettings(); - } + settings ??= new PSInvocationSettings(); settings.AddToHistory = true; } @@ -3079,6 +3076,10 @@ public IAsyncResult BeginInvoke(PSDataCollection input, /// /// Object is disposed. /// + /// + /// The running PowerShell pipeline was stopped. + /// This occurs when or is called. + /// public Task> InvokeAsync() => Task>.Factory.FromAsync(BeginInvoke(), _endInvokeMethod); @@ -3120,6 +3121,10 @@ public Task> InvokeAsync() /// /// Object is disposed. /// + /// + /// The running PowerShell pipeline was stopped. + /// This occurs when or is called. + /// public Task> InvokeAsync(PSDataCollection input) => Task>.Factory.FromAsync(BeginInvoke(input), _endInvokeMethod); @@ -3174,6 +3179,10 @@ public Task> InvokeAsync(PSDataCollection input /// /// Object is disposed. /// + /// + /// The running PowerShell pipeline was stopped. + /// This occurs when or is called. + /// public Task> InvokeAsync(PSDataCollection input, PSInvocationSettings settings, AsyncCallback callback, object state) => Task>.Factory.FromAsync(BeginInvoke(input, settings, callback, state), _endInvokeMethod); @@ -3222,6 +3231,14 @@ public Task> InvokeAsync(PSDataCollection input /// /// Object is disposed. /// + /// + /// The running PowerShell pipeline was stopped. + /// This occurs when or is called. + /// To collect partial output in this scenario, + /// supply a for the parameter, + /// and either add a handler for the event + /// or catch the exception and enumerate the object supplied for . + /// public Task> InvokeAsync(PSDataCollection input, PSDataCollection output) => Task>.Factory.FromAsync(BeginInvoke(input, output), _endInvokeMethod); @@ -3284,6 +3301,14 @@ public Task> InvokeAsync(PSDataColle /// /// Object is disposed. /// + /// + /// The running PowerShell pipeline was stopped. + /// This occurs when or is called. + /// To collect partial output in this scenario, + /// supply a for the parameter, + /// and either add a handler for the event + /// or catch the exception and use object supplied for . + /// public Task> InvokeAsync(PSDataCollection input, PSDataCollection output, PSInvocationSettings settings, AsyncCallback callback, object state) => Task>.Factory.FromAsync(BeginInvoke(input, output, settings, callback, state), _endInvokeMethod); @@ -3493,9 +3518,7 @@ private void BatchInvocationCallback(IAsyncResult result) ActionPreference preference; if (_batchInvocationSettings != null) { - preference = (_batchInvocationSettings.ErrorActionPreference.HasValue) ? - _batchInvocationSettings.ErrorActionPreference.Value - : ActionPreference.Continue; + preference = _batchInvocationSettings.ErrorActionPreference ?? ActionPreference.Continue; } else { @@ -3518,10 +3541,7 @@ private void BatchInvocationCallback(IAsyncResult result) break; } - if (objs == null) - { - objs = _batchAsyncResult.Output; - } + objs ??= _batchAsyncResult.Output; DoRemainingBatchCommands(objs); } @@ -3655,6 +3675,14 @@ private void AppendExceptionToErrorStream(Exception e) /// asyncResult object was not created by calling BeginInvoke /// on this PowerShell instance. /// + /// + /// The running PowerShell pipeline was stopped. + /// This occurs when or is called. + /// To collect partial output in this scenario, + /// supply a to for the parameter + /// and either add a handler for the event + /// or catch the exception and enumerate the object supplied. + /// public PSDataCollection EndInvoke(IAsyncResult asyncResult) { try @@ -3706,6 +3734,10 @@ public PSDataCollection EndInvoke(IAsyncResult asyncResult) /// /// Object is disposed. /// + /// + /// When used with , that call will return a partial result. + /// When used with , that call will throw a . + /// public void Stop() { try @@ -3762,6 +3794,10 @@ public IAsyncResult BeginStop(AsyncCallback callback, object state) /// asyncResult object was not created by calling BeginStop /// on this PowerShell instance. /// + /// + /// When used with , that call will return a partial result. + /// When used with , that call will throw a . + /// public void EndStop(IAsyncResult asyncResult) { if (asyncResult == null) @@ -3812,6 +3848,10 @@ public void EndStop(IAsyncResult asyncResult) /// /// Object is disposed. /// + /// + /// When used with , that call will return a partial result. + /// When used with , that call will throw a . + /// public Task StopAsync(AsyncCallback callback, object state) => Task.Factory.FromAsync(BeginStop(callback, state), _endStopMethod); @@ -4127,10 +4167,7 @@ private void Dispose(bool disposing) _runspace.Dispose(); } - if (RemotePowerShell != null) - { - RemotePowerShell.Dispose(); - } + RemotePowerShell?.Dispose(); _invokeAsyncResult = null; _stopAsyncResult = null; @@ -4144,10 +4181,7 @@ private void InternalClearSuppressExceptions() { lock (_syncObject) { - if (_worker != null) - { - _worker.InternalClearSuppressExceptions(); - } + _worker?.InternalClearSuppressExceptions(); } } @@ -4273,10 +4307,7 @@ internal void SetStateChanged(PSInvocationStateInfo stateInfo) { if (RunningExtraCommands) { - if (tempInvokeAsyncResult != null) - { - tempInvokeAsyncResult.SetAsCompleted(InvocationStateInfo.Reason); - } + tempInvokeAsyncResult?.SetAsCompleted(InvocationStateInfo.Reason); RaiseStateChangeEvent(InvocationStateInfo.Clone()); } @@ -4284,16 +4315,10 @@ internal void SetStateChanged(PSInvocationStateInfo stateInfo) { RaiseStateChangeEvent(InvocationStateInfo.Clone()); - if (tempInvokeAsyncResult != null) - { - tempInvokeAsyncResult.SetAsCompleted(InvocationStateInfo.Reason); - } + tempInvokeAsyncResult?.SetAsCompleted(InvocationStateInfo.Reason); } - if (tempStopAsyncResult != null) - { - tempStopAsyncResult.SetAsCompleted(null); - } + tempStopAsyncResult?.SetAsCompleted(null); } catch (Exception) { @@ -4305,7 +4330,7 @@ internal void SetStateChanged(PSInvocationStateInfo stateInfo) } finally { - // takes care exception occured with invokeAsyncResult + // takes care exception occurred with invokeAsyncResult if (isExceptionOccured && (tempStopAsyncResult != null)) { tempStopAsyncResult.Release(); @@ -4332,10 +4357,7 @@ internal void SetStateChanged(PSInvocationStateInfo stateInfo) // This object can be disconnected even if "BeginStop" was called if it is a remote object // and robust connections is retrying a failed network connection. // In this case release the stop wait handle to prevent not responding. - if (tempStopAsyncResult != null) - { - tempStopAsyncResult.SetAsCompleted(null); - } + tempStopAsyncResult?.SetAsCompleted(null); // Only raise the Disconnected state changed event if the PowerShell state // actually transitions to Disconnected from some other state. This condition @@ -4356,7 +4378,7 @@ internal void SetStateChanged(PSInvocationStateInfo stateInfo) } finally { - // takes care exception occured with invokeAsyncResult + // takes care exception occurred with invokeAsyncResult if (isExceptionOccured && (tempStopAsyncResult != null)) { tempStopAsyncResult.Release(); @@ -4401,10 +4423,7 @@ internal void ClearRemotePowerShell() { lock (_syncObject) { - if (RemotePowerShell != null) - { - RemotePowerShell.Clear(); - } + RemotePowerShell?.Clear(); } } @@ -5118,11 +5137,8 @@ private IAsyncResult CoreStop(bool isSyncCall, AsyncCallback callback, object st // cannot complete with this object. if (isDisconnected) { - if (_invokeAsyncResult != null) - { - // Since object is stopped, allow result wait to end. - _invokeAsyncResult.SetAsCompleted(null); - } + // Since object is stopped, allow result wait to end. + _invokeAsyncResult?.SetAsCompleted(null); _stopAsyncResult.SetAsCompleted(null); @@ -5183,10 +5199,7 @@ private IAsyncResult CoreStop(bool isSyncCall, AsyncCallback callback, object st private void ReleaseDebugger() { LocalRunspace localRunspace = _runspace as LocalRunspace; - if (localRunspace != null) - { - localRunspace.ReleaseDebugger(); - } + localRunspace?.ReleaseDebugger(); } /// @@ -5287,10 +5300,7 @@ private void AddToRemoteRunspaceRunningList() else { RemoteRunspacePoolInternal remoteRunspacePoolInternal = GetRemoteRunspacePoolInternal(); - if (remoteRunspacePoolInternal != null) - { - remoteRunspacePoolInternal.PushRunningPowerShell(this); - } + remoteRunspacePoolInternal?.PushRunningPowerShell(this); } } @@ -5306,10 +5316,7 @@ private void RemoveFromRemoteRunspaceRunningList() else { RemoteRunspacePoolInternal remoteRunspacePoolInternal = GetRemoteRunspacePoolInternal(); - if (remoteRunspacePoolInternal != null) - { - remoteRunspacePoolInternal.PopRunningPowerShell(); - } + remoteRunspacePoolInternal?.PopRunningPowerShell(); } } @@ -5678,10 +5685,7 @@ internal void InternalClearSuppressExceptions() else { RunspacePool pool = _shell._rsConnection as RunspacePool; - if (pool != null) - { - pool.ReleaseRunspace(CurrentlyRunningPipeline.Runspace); - } + pool?.ReleaseRunspace(CurrentlyRunningPipeline.Runspace); } CurrentlyRunningPipeline.Dispose(); @@ -5852,10 +5856,7 @@ internal void SuspendIncomingData() throw new PSNotSupportedException(); } - if (RemotePowerShell.DataStructureHandler != null) - { - RemotePowerShell.DataStructureHandler.TransportManager.SuspendQueue(true); - } + RemotePowerShell.DataStructureHandler?.TransportManager.SuspendQueue(true); } /// @@ -5868,10 +5869,7 @@ internal void ResumeIncomingData() throw new PSNotSupportedException(); } - if (RemotePowerShell.DataStructureHandler != null) - { - RemotePowerShell.DataStructureHandler.TransportManager.ResumeQueue(); - } + RemotePowerShell.DataStructureHandler?.TransportManager.ResumeQueue(); } /// @@ -6165,15 +6163,9 @@ internal class PowerShellStopper : IDisposable internal PowerShellStopper(ExecutionContext context, PowerShell powerShell) { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } + ArgumentNullException.ThrowIfNull(context); - if (powerShell == null) - { - throw new ArgumentNullException(nameof(powerShell)); - } + ArgumentNullException.ThrowIfNull(powerShell); _powerShell = powerShell; diff --git a/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs b/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs index 3f4dcf40744..5ab95041877 100644 --- a/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs +++ b/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs @@ -11,6 +11,7 @@ namespace System.Management.Automation.Runspaces { /// + /// This class represents a PowerShell process that is used for an out-of-process remote Runspace. /// public sealed class PowerShellProcessInstance : IDisposable { @@ -30,8 +31,6 @@ public sealed class PowerShellProcessInstance : IDisposable #region Constructors - /// - /// static PowerShellProcessInstance() { #if UNIX @@ -145,10 +144,10 @@ public PowerShellProcessInstance(Version powerShellVersion, PSCredential credent /// /// Initializes a new instance of the class. Initializes the underlying dotnet process class. /// - /// - /// - /// - /// + /// Specifies the version of powershell. + /// Specifies a user account credentials. + /// Specifies a script that will be executed when the powershell process is initialized. + /// Specifies if the powershell process will be 32-bit. public PowerShellProcessInstance(Version powerShellVersion, PSCredential credential, ScriptBlock initializationScript, bool useWow64) : this(powerShellVersion, credential, initializationScript, useWow64, workingDirectory: null) { } @@ -178,7 +177,9 @@ public bool HasExited #endregion Constructors #region Dispose + /// + /// Implementing the interface. /// public void Dispose() { @@ -186,15 +187,20 @@ public void Dispose() GC.SuppressFinalize(this); } - /// - /// - /// private void Dispose(bool disposing) { - if (_isDisposed) return; + if (_isDisposed) + { + return; + } + lock (_syncObject) { - if (_isDisposed) return; + if (_isDisposed) + { + return; + } + _isDisposed = true; } @@ -220,7 +226,9 @@ private void Dispose(bool disposing) #endregion Dispose #region Public Properties + /// + /// Gets the process object of the remote target. /// public Process Process { get; } diff --git a/src/System.Management.Automation/engine/hostifaces/RunspaceInvoke.cs b/src/System.Management.Automation/engine/hostifaces/RunspaceInvoke.cs index 25c611eba0e..5fa8370bcea 100644 --- a/src/System.Management.Automation/engine/hostifaces/RunspaceInvoke.cs +++ b/src/System.Management.Automation/engine/hostifaces/RunspaceInvoke.cs @@ -61,7 +61,7 @@ public RunspaceInvoke(Runspace runspace) /// /// Invoke the specified script. /// - /// Msh script to invoke. + /// PowerShell script to invoke. /// Output of invocation. public Collection Invoke(string script) { @@ -71,7 +71,7 @@ public Collection Invoke(string script) /// /// Invoke the specified script and passes specified input to the script. /// - /// Msh script to invoke. + /// PowerShell script to invoke. /// Input to script. /// Output of invocation. public Collection Invoke(string script, IEnumerable input) @@ -93,7 +93,7 @@ public Collection Invoke(string script, IEnumerable input) /// /// Invoke the specified script and passes specified input to the script. /// - /// Msh script to invoke. + /// PowerShell script to invoke. /// Input to script. /// This gets errors from script. /// Output of invocation. diff --git a/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs b/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs index 5f814e0428b..5b03ecffa58 100644 --- a/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs +++ b/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs @@ -16,7 +16,6 @@ namespace System.Management.Automation.Runspaces /// Exception thrown when state of the runspace pool is different from /// expected state of runspace pool. /// - [Serializable] public class InvalidRunspacePoolStateException : SystemException { /// @@ -91,10 +90,11 @@ RunspacePoolState expectedState /// The that contains /// contextual information about the source or destination. /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected InvalidRunspacePoolStateException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion @@ -1003,7 +1003,7 @@ public Collection CreateDisconnectedPowerShells() return _internalPool.CreateDisconnectedPowerShells(this); } - /// + /// /// Returns RunspacePool capabilities. /// /// RunspacePoolCapability. @@ -1203,9 +1203,7 @@ public void EndClose(IAsyncResult asyncResult) /// public void Dispose() { - _internalPool.Dispose(true); - - GC.SuppressFinalize(this); + _internalPool.Dispose(); } /// diff --git a/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs b/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs index aac3acfbdf8..6df0aa74695 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 /// /// Class which supports pooling local powerShell runspaces. /// - internal class RunspacePoolInternal + internal class RunspacePoolInternal : IDisposable { #region Private data @@ -231,10 +231,7 @@ internal virtual PSPrimitiveDictionary GetApplicationPrivateData() { lock (this.syncObject) { - if (_applicationPrivateData == null) - { - _applicationPrivateData = new PSPrimitiveDictionary(); - } + _applicationPrivateData ??= new PSPrimitiveDictionary(); } } @@ -815,6 +812,16 @@ public void ReleaseRunspace(Runspace runspace) EnqueueCheckAndStartRequestServicingThread(null, false); } } + + /// + /// Release all resources. + /// + public void Dispose() + { + Dispose(true); + + GC.SuppressFinalize(this); + } /// /// Dispose off the current runspace pool. @@ -822,7 +829,7 @@ public void ReleaseRunspace(Runspace runspace) /// /// true to release all the internal resources. /// - public virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) { if (!_isDisposed) { @@ -1306,7 +1313,7 @@ protected void DestroyRunspace(Runspace runspace) /// /// Cleans the pool closing the runspaces that are idle. /// This method is called as part of a timer callback. - /// This method will make sure atleast minPoolSz number + /// This method will make sure at least minPoolSz number /// of Runspaces are active. /// /// diff --git a/src/System.Management.Automation/engine/hostifaces/pipelinebase.cs b/src/System.Management.Automation/engine/hostifaces/pipelinebase.cs index 2d74430a40a..f144ff7f506 100644 --- a/src/System.Management.Automation/engine/hostifaces/pipelinebase.cs +++ b/src/System.Management.Automation/engine/hostifaces/pipelinebase.cs @@ -282,7 +282,7 @@ public override void StopAsync() /// /// Stop the running pipeline. /// - /// If true pipeline is stoped synchronously + /// If true pipeline is stopped synchronously /// else asynchronously. private void CoreStop(bool syncCall) { @@ -298,7 +298,7 @@ private void CoreStop(bool syncCall) break; // If pipeline execution has failed or completed or - // stoped, return silently. + // stopped, return silently. case PipelineState.Stopped: case PipelineState.Completed: case PipelineState.Failed: @@ -331,7 +331,7 @@ private void CoreStop(bool syncCall) // Raise the event outside the lock RaisePipelineStateEvents(); - // A pipeline can be stoped before it is started. See NotStarted + // A pipeline can be stopped before it is started. See NotStarted // case in above switch statement. This is done to allow stoping a pipeline // in another thread before it has been started. lock (SyncRoot) @@ -515,7 +515,7 @@ private void CoreInvoke(IEnumerable input, bool syncCall) SyncInvokeCall = syncCall; // Create event which will be signalled when pipeline execution - // is completed/failed/stoped. + // is completed/failed/stopped. // Note:Runspace.Close waits for all the running pipeline // to finish. This Event must be created before pipeline is // added to list of running pipelines. This avoids the race condition @@ -760,7 +760,7 @@ protected bool IsPipelineFinished() } /// - /// This is queue of all the state change event which have occured for + /// This is queue of all the state change event which have occurred for /// this pipeline. RaisePipelineStateEvents raises event for each /// item in this queue. We don't raise the event with in SetPipelineState /// because often SetPipelineState is called with in a lock. @@ -768,7 +768,7 @@ protected bool IsPipelineFinished() /// private Queue _executionEventQueue = new Queue(); - private class ExecutionEventQueueItem + private sealed class ExecutionEventQueueItem { public ExecutionEventQueueItem(PipelineStateInfo pipelineStateInfo, RunspaceAvailability currentAvailability, RunspaceAvailability newAvailability) { @@ -891,7 +891,7 @@ protected void RaisePipelineStateEvents() /// /// ManualResetEvent which is signaled when pipeline execution is - /// completed/failed/stoped. + /// completed/failed/stopped. /// internal ManualResetEvent PipelineFinishedEvent { get; private set; } diff --git a/src/System.Management.Automation/engine/interpreter/BranchLabel.cs b/src/System.Management.Automation/engine/interpreter/BranchLabel.cs index 08af974416c..1bae16783a9 100644 --- a/src/System.Management.Automation/engine/interpreter/BranchLabel.cs +++ b/src/System.Management.Automation/engine/interpreter/BranchLabel.cs @@ -110,10 +110,7 @@ internal void AddBranch(InstructionList instructions, int branchIndex) if (_targetIndex == UnknownIndex) { - if (_forwardBranchFixups == null) - { - _forwardBranchFixups = new List(); - } + _forwardBranchFixups ??= new List(); _forwardBranchFixups.Add(branchIndex); } diff --git a/src/System.Management.Automation/engine/interpreter/CallInstruction.cs b/src/System.Management.Automation/engine/interpreter/CallInstruction.cs index 88ff4bc1357..f7173c745ff 100644 --- a/src/System.Management.Automation/engine/interpreter/CallInstruction.cs +++ b/src/System.Management.Automation/engine/interpreter/CallInstruction.cs @@ -222,7 +222,11 @@ private static bool IndexIsNotReturnType(int index, MethodInfo target, Parameter private static CallInstruction SlowCreate(MethodInfo info, ParameterInfo[] pis) { List types = new List(); - if (!info.IsStatic) types.Add(info.DeclaringType); + if (!info.IsStatic) + { + types.Add(info.DeclaringType); + } + foreach (ParameterInfo pi in pis) { types.Add(pi.ParameterType); diff --git a/src/System.Management.Automation/engine/interpreter/ControlFlowInstructions.cs b/src/System.Management.Automation/engine/interpreter/ControlFlowInstructions.cs index 0fddd203346..5d994d934c5 100644 --- a/src/System.Management.Automation/engine/interpreter/ControlFlowInstructions.cs +++ b/src/System.Management.Automation/engine/interpreter/ControlFlowInstructions.cs @@ -157,10 +157,7 @@ public override Instruction[] Cache { get { - if (s_caches == null) - { - s_caches = new Instruction[2][][] { new Instruction[2][], new Instruction[2][] }; - } + s_caches ??= new Instruction[2][][] { new Instruction[2][], new Instruction[2][] }; return s_caches[ConsumedStack][ProducedStack] ?? (s_caches[ConsumedStack][ProducedStack] = new Instruction[CacheSize]); } @@ -509,7 +506,7 @@ public override int Run(InterpretedFrame frame) frame.PopPendingContinuation(); // If _pendingContinuation == -1 then we were getting into the finally block because an exception was thrown - // In this case we just return 1, and the the real instruction index will be calculated by GotoHandler later + // In this case we just return 1, and the real instruction index will be calculated by GotoHandler later if (!frame.IsJumpHappened()) { return 1; } // jump to goto target or to the next finally: return frame.YieldToPendingContinuation(); diff --git a/src/System.Management.Automation/engine/interpreter/DynamicInstructions.Generated.cs b/src/System.Management.Automation/engine/interpreter/DynamicInstructions.Generated.cs index ad10413f9d0..abe64c471af 100644 --- a/src/System.Management.Automation/engine/interpreter/DynamicInstructions.Generated.cs +++ b/src/System.Management.Automation/engine/interpreter/DynamicInstructions.Generated.cs @@ -22,7 +22,11 @@ namespace System.Management.Automation.Interpreter { internal partial class DynamicInstructionN { internal static Type GetDynamicInstructionType(Type delegateType) { Type[] argTypes = delegateType.GetGenericArguments(); - if (argTypes.Length == 0) return null; + if (argTypes.Length == 0) + { + return null; + } + Type genericType; Type[] newArgTypes = argTypes.Skip(1).ToArray(); switch (newArgTypes.Length) { diff --git a/src/System.Management.Automation/engine/interpreter/ILightCallSiteBinder.cs b/src/System.Management.Automation/engine/interpreter/ILightCallSiteBinder.cs index 49fbfdb0f8e..9f24d14fa14 100644 --- a/src/System.Management.Automation/engine/interpreter/ILightCallSiteBinder.cs +++ b/src/System.Management.Automation/engine/interpreter/ILightCallSiteBinder.cs @@ -13,6 +13,7 @@ * * ***************************************************************************/ +#nullable enable #if !CLR2 #else using Microsoft.Scripting.Ast; diff --git a/src/System.Management.Automation/engine/interpreter/InstructionFactory.cs b/src/System.Management.Automation/engine/interpreter/InstructionFactory.cs index 4a707201eb6..839ccd6f905 100644 --- a/src/System.Management.Automation/engine/interpreter/InstructionFactory.cs +++ b/src/System.Management.Automation/engine/interpreter/InstructionFactory.cs @@ -119,10 +119,7 @@ protected internal override Instruction NewArrayInit(int elementCount) { if (elementCount < MaxArrayInitElementCountCache) { - if (_newArrayInit == null) - { - _newArrayInit = new Instruction[MaxArrayInitElementCountCache]; - } + _newArrayInit ??= new Instruction[MaxArrayInitElementCountCache]; return _newArrayInit[elementCount] ?? (_newArrayInit[elementCount] = new NewArrayInitInstruction(elementCount)); } diff --git a/src/System.Management.Automation/engine/interpreter/InstructionList.cs b/src/System.Management.Automation/engine/interpreter/InstructionList.cs index 3c810d186e0..93946bfd2c3 100644 --- a/src/System.Management.Automation/engine/interpreter/InstructionList.cs +++ b/src/System.Management.Automation/engine/interpreter/InstructionList.cs @@ -95,7 +95,9 @@ internal sealed class InstructionList private List _labels; // list of (instruction index, cookie) sorted by instruction index: +#pragma warning disable IDE0044 // Add readonly modifier private List> _debugCookies = null; +#pragma warning restore IDE0044 // Variable is assigned when DEBUG is defined. #region Debug View @@ -231,10 +233,7 @@ private void UpdateStackDepth(Instruction instruction) public void SetDebugCookie(object cookie) { #if DEBUG - if (_debugCookies == null) - { - _debugCookies = new List>(); - } + _debugCookies ??= new List>(); Debug.Assert(Count > 0); _debugCookies.Add(new KeyValuePair(Count - 1, cookie)); @@ -273,7 +272,7 @@ internal Instruction GetInstruction(int index) static InstructionList() { AppDomain.CurrentDomain.ProcessExit += new EventHandler((_, __) => { PerfTrack.DumpHistogram(_executedInstructions); - Console.WriteLine("-- Total executed: {0}", _executedInstructions.Values.Aggregate(0, (sum, value) => sum + value)); + Console.WriteLine("-- Total executed: {0}", _executedInstructions.Values.Aggregate(0, static (sum, value) => sum + value)); Console.WriteLine("-----"); var referenced = new Dictionary(); @@ -370,10 +369,7 @@ public void EmitLoad(object value, Type type) int i = (int)value; if (i >= PushIntMinCachedValue && i <= PushIntMaxCachedValue) { - if (s_ints == null) - { - s_ints = new Instruction[PushIntMaxCachedValue - PushIntMinCachedValue + 1]; - } + s_ints ??= new Instruction[PushIntMaxCachedValue - PushIntMinCachedValue + 1]; i -= PushIntMinCachedValue; Emit(s_ints[i] ?? (s_ints[i] = new LoadObjectInstruction(value))); @@ -385,10 +381,7 @@ public void EmitLoad(object value, Type type) if (_objects == null) { _objects = new List(); - if (s_loadObjectCached == null) - { - s_loadObjectCached = new Instruction[CachedObjectCount]; - } + s_loadObjectCached ??= new Instruction[CachedObjectCount]; } if (_objects.Count < s_loadObjectCached.Length) @@ -449,10 +442,7 @@ internal void SwitchToBoxed(int index, int instructionIndex) public void EmitLoadLocal(int index) { - if (s_loadLocal == null) - { - s_loadLocal = new Instruction[LocalInstrCacheSize]; - } + s_loadLocal ??= new Instruction[LocalInstrCacheSize]; if (index < s_loadLocal.Length) { @@ -471,10 +461,7 @@ public void EmitLoadLocalBoxed(int index) internal static Instruction LoadLocalBoxed(int index) { - if (s_loadLocalBoxed == null) - { - s_loadLocalBoxed = new Instruction[LocalInstrCacheSize]; - } + s_loadLocalBoxed ??= new Instruction[LocalInstrCacheSize]; if (index < s_loadLocalBoxed.Length) { @@ -488,10 +475,7 @@ internal static Instruction LoadLocalBoxed(int index) public void EmitLoadLocalFromClosure(int index) { - if (s_loadLocalFromClosure == null) - { - s_loadLocalFromClosure = new Instruction[LocalInstrCacheSize]; - } + s_loadLocalFromClosure ??= new Instruction[LocalInstrCacheSize]; if (index < s_loadLocalFromClosure.Length) { @@ -505,10 +489,7 @@ public void EmitLoadLocalFromClosure(int index) public void EmitLoadLocalFromClosureBoxed(int index) { - if (s_loadLocalFromClosureBoxed == null) - { - s_loadLocalFromClosureBoxed = new Instruction[LocalInstrCacheSize]; - } + s_loadLocalFromClosureBoxed ??= new Instruction[LocalInstrCacheSize]; if (index < s_loadLocalFromClosureBoxed.Length) { @@ -522,10 +503,7 @@ public void EmitLoadLocalFromClosureBoxed(int index) public void EmitAssignLocal(int index) { - if (s_assignLocal == null) - { - s_assignLocal = new Instruction[LocalInstrCacheSize]; - } + s_assignLocal ??= new Instruction[LocalInstrCacheSize]; if (index < s_assignLocal.Length) { @@ -539,10 +517,7 @@ public void EmitAssignLocal(int index) public void EmitStoreLocal(int index) { - if (s_storeLocal == null) - { - s_storeLocal = new Instruction[LocalInstrCacheSize]; - } + s_storeLocal ??= new Instruction[LocalInstrCacheSize]; if (index < s_storeLocal.Length) { @@ -561,10 +536,7 @@ public void EmitAssignLocalBoxed(int index) internal static Instruction AssignLocalBoxed(int index) { - if (s_assignLocalBoxed == null) - { - s_assignLocalBoxed = new Instruction[LocalInstrCacheSize]; - } + s_assignLocalBoxed ??= new Instruction[LocalInstrCacheSize]; if (index < s_assignLocalBoxed.Length) { @@ -583,10 +555,7 @@ public void EmitStoreLocalBoxed(int index) internal static Instruction StoreLocalBoxed(int index) { - if (s_storeLocalBoxed == null) - { - s_storeLocalBoxed = new Instruction[LocalInstrCacheSize]; - } + s_storeLocalBoxed ??= new Instruction[LocalInstrCacheSize]; if (index < s_storeLocalBoxed.Length) { @@ -600,10 +569,7 @@ internal static Instruction StoreLocalBoxed(int index) public void EmitAssignLocalToClosure(int index) { - if (s_assignLocalToClosure == null) - { - s_assignLocalToClosure = new Instruction[LocalInstrCacheSize]; - } + s_assignLocalToClosure ??= new Instruction[LocalInstrCacheSize]; if (index < s_assignLocalToClosure.Length) { @@ -645,10 +611,7 @@ internal void EmitInitializeParameter(int index) internal static Instruction Parameter(int index) { - if (s_parameter == null) - { - s_parameter = new Instruction[LocalInstrCacheSize]; - } + s_parameter ??= new Instruction[LocalInstrCacheSize]; if (index < s_parameter.Length) { @@ -660,10 +623,7 @@ internal static Instruction Parameter(int index) internal static Instruction ParameterBox(int index) { - if (s_parameterBox == null) - { - s_parameterBox = new Instruction[LocalInstrCacheSize]; - } + s_parameterBox ??= new Instruction[LocalInstrCacheSize]; if (index < s_parameterBox.Length) { @@ -675,10 +635,7 @@ internal static Instruction ParameterBox(int index) internal static Instruction InitReference(int index) { - if (s_initReference == null) - { - s_initReference = new Instruction[LocalInstrCacheSize]; - } + s_initReference ??= new Instruction[LocalInstrCacheSize]; if (index < s_initReference.Length) { @@ -690,10 +647,7 @@ internal static Instruction InitReference(int index) internal static Instruction InitImmutableRefBox(int index) { - if (s_initImmutableRefBox == null) - { - s_initImmutableRefBox = new Instruction[LocalInstrCacheSize]; - } + s_initImmutableRefBox ??= new Instruction[LocalInstrCacheSize]; if (index < s_initImmutableRefBox.Length) { @@ -1125,10 +1079,7 @@ private RuntimeLabel[] BuildRuntimeLabels() public BranchLabel MakeLabel() { - if (_labels == null) - { - _labels = new List(); - } + _labels ??= new List(); var label = new BranchLabel(); _labels.Add(label); diff --git a/src/System.Management.Automation/engine/interpreter/LabelInfo.cs b/src/System.Management.Automation/engine/interpreter/LabelInfo.cs index a8518099824..8d501987c3d 100644 --- a/src/System.Management.Automation/engine/interpreter/LabelInfo.cs +++ b/src/System.Management.Automation/engine/interpreter/LabelInfo.cs @@ -80,7 +80,7 @@ internal void Define(LabelScopeInfo block) { if (j.ContainsTarget(_node)) { - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Label target already defined: {0}", _node.Name)); + throw new InvalidOperationException(string.Create(CultureInfo.InvariantCulture, $"Label target already defined: {_node.Name}")); } } @@ -132,12 +132,12 @@ private void ValidateJump(LabelScopeInfo reference) if (HasMultipleDefinitions) { - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Ambiguous jump {0}", _node.Name)); + throw new InvalidOperationException(string.Create(CultureInfo.InvariantCulture, $"Ambiguous jump {_node.Name}")); } // We didn't find an outward jump. Look for a jump across blocks LabelScopeInfo def = FirstDefinition(); - LabelScopeInfo common = CommonNode(def, reference, b => b.Parent); + LabelScopeInfo common = CommonNode(def, reference, static b => b.Parent); // Validate that we aren't jumping across a finally for (LabelScopeInfo j = reference; j != common; j = j.Parent) @@ -176,10 +176,7 @@ internal void ValidateFinish() private void EnsureLabel(LightCompiler compiler) { - if (_label == null) - { - _label = compiler.Instructions.MakeLabel(); - } + _label ??= compiler.Instructions.MakeLabel(); } private bool DefinedIn(LabelScopeInfo scope) @@ -359,10 +356,7 @@ internal void AddLabelInfo(LabelTarget target, LabelInfo info) { Debug.Assert(CanJumpInto); - if (_labels == null) - { - _labels = new HybridReferenceDictionary(); - } + _labels ??= new HybridReferenceDictionary(); _labels[target] = info; } diff --git a/src/System.Management.Automation/engine/interpreter/LightCompiler.cs b/src/System.Management.Automation/engine/interpreter/LightCompiler.cs index 85f97ee7d40..a006b7e0f4f 100644 --- a/src/System.Management.Automation/engine/interpreter/LightCompiler.cs +++ b/src/System.Management.Automation/engine/interpreter/LightCompiler.cs @@ -68,7 +68,10 @@ public bool Matches(Type exceptionType) public bool IsBetterThan(ExceptionHandler other) { - if (other == null) return true; + if (other == null) + { + return true; + } Debug.Assert(StartIndex == other.StartIndex && EndIndex == other.EndIndex, "we only need to compare handlers for the same try block"); return HandlerStartIndex < other.HandlerStartIndex; @@ -92,11 +95,14 @@ internal bool IsInsideFinallyBlock(int index) public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "{0} [{1}-{2}] [{3}->{4}]", - (IsFault ? "fault" : "catch(" + ExceptionType.Name + ")"), - StartIndex, EndIndex, - HandlerStartIndex, HandlerEndIndex - ); + return string.Format( + CultureInfo.InvariantCulture, + "{0} [{1}-{2}] [{3}->{4}]", + IsFault ? "fault" : "catch(" + ExceptionType.Name + ")", + StartIndex, + EndIndex, + HandlerStartIndex, + HandlerEndIndex); } } @@ -181,7 +187,6 @@ internal sealed class RethrowException : SystemException { } - [Serializable] internal class DebugInfo { // TODO: readonly @@ -192,7 +197,7 @@ internal class DebugInfo public bool IsClear; private static readonly DebugInfoComparer s_debugComparer = new DebugInfoComparer(); - private class DebugInfoComparer : IComparer + private sealed class DebugInfoComparer : IComparer { // We allow comparison between int and DebugInfo here int IComparer.Compare(DebugInfo d1, DebugInfo d2) @@ -242,7 +247,6 @@ public override string ToString() // TODO: [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")] - [Serializable] internal readonly struct InterpretedFrameInfo { public readonly string MethodName; @@ -1063,7 +1067,7 @@ private void CompileSwitchExpression(Expression expr) } // Test values must be constant - if (!node.Cases.All(c => c.TestValues.All(t => t is ConstantExpression))) + if (!node.Cases.All(static c => c.TestValues.All(t => t is ConstantExpression))) { throw new NotImplementedException(); } @@ -1132,10 +1136,7 @@ private void CompileLabelExpression(Expression expr) Debug.Assert(label != null); } - if (label == null) - { - label = DefineLabel(node.Target); - } + label ??= DefineLabel(node.Target); if (node.DefaultValue != null) { @@ -1356,7 +1357,7 @@ private void CompileThrowUnaryExpression(Expression expr, bool asVoid) } // TODO: remove (replace by true fault support) - private bool EndsWithRethrow(Expression expr) + private static bool EndsWithRethrow(Expression expr) { if (expr.NodeType == ExpressionType.Throw) { @@ -1573,7 +1574,7 @@ private void CompileMethodCallExpression(Expression expr) // also could be a mutable value type, Delegate.CreateDelegate and MethodInfo.Invoke both can't handle this, we // need to generate code. var declaringType = node.Method.DeclaringType; - if (!parameters.TrueForAll(p => !p.ParameterType.IsByRef) || + if (!parameters.TrueForAll(static p => !p.ParameterType.IsByRef) || (!node.Method.IsStatic && declaringType.IsValueType && !declaringType.IsPrimitive)) { _forceCompile = true; @@ -1600,7 +1601,7 @@ private void CompileNewExpression(Expression expr) if (node.Constructor != null) { var parameters = node.Constructor.GetParameters(); - if (!parameters.TrueForAll(p => !p.ParameterType.IsByRef)) + if (!parameters.TrueForAll(static p => !p.ParameterType.IsByRef)) { _forceCompile = true; } diff --git a/src/System.Management.Automation/engine/interpreter/LightDelegateCreator.cs b/src/System.Management.Automation/engine/interpreter/LightDelegateCreator.cs index 0df528b94bb..6f9497db987 100644 --- a/src/System.Management.Automation/engine/interpreter/LightDelegateCreator.cs +++ b/src/System.Management.Automation/engine/interpreter/LightDelegateCreator.cs @@ -196,7 +196,7 @@ private static Type GetFuncOrAction(LambdaExpression lambda) // lambda.Parameters[0].IsByRef && lambda.Parameters[1].IsByRef) { // return typeof(ActionRef<,>).MakeGenericType(lambda.Parameters.Map(p => p.Type)); // } else { - Type[] types = lambda.Parameters.Map(p => p.IsByRef ? p.Type.MakeByRefType() : p.Type); + Type[] types = lambda.Parameters.Map(static p => p.IsByRef ? p.Type.MakeByRefType() : p.Type); if (isVoid) { if (Expression.TryGetActionType(types, out delegateType)) diff --git a/src/System.Management.Automation/engine/interpreter/LocalVariables.cs b/src/System.Management.Automation/engine/interpreter/LocalVariables.cs index d52f1fc6840..0c1f6416741 100644 --- a/src/System.Management.Automation/engine/interpreter/LocalVariables.cs +++ b/src/System.Management.Automation/engine/interpreter/LocalVariables.cs @@ -163,10 +163,7 @@ public LocalDefinition DefineLocal(ParameterExpression variable, int start) if (_variables.TryGetValue(variable, out existing)) { newScope = new VariableScope(result, start, existing); - if (existing.ChildScopes == null) - { - existing.ChildScopes = new List(); - } + existing.ChildScopes ??= new List(); existing.ChildScopes.Add(newScope); } @@ -296,10 +293,7 @@ internal Dictionary ClosureVariables internal LocalVariable AddClosureVariable(ParameterExpression variable) { - if (_closureVariables == null) - { - _closureVariables = new Dictionary(); - } + _closureVariables ??= new Dictionary(); LocalVariable result = new LocalVariable(_closureVariables.Count, true, false); _closureVariables.Add(variable, result); diff --git a/src/System.Management.Automation/engine/interpreter/LoopCompiler.cs b/src/System.Management.Automation/engine/interpreter/LoopCompiler.cs index 6ff0e36f5c3..78c5534a132 100644 --- a/src/System.Management.Automation/engine/interpreter/LoopCompiler.cs +++ b/src/System.Management.Automation/engine/interpreter/LoopCompiler.cs @@ -374,10 +374,7 @@ private Expression VisitVariable(ParameterExpression node, ExpressionAccess acce private ParameterExpression AddTemp(ParameterExpression variable) { - if (_temps == null) - { - _temps = new List(); - } + _temps ??= new List(); _temps.Add(variable); return variable; diff --git a/src/System.Management.Automation/engine/interpreter/PowerShellInstructions.cs b/src/System.Management.Automation/engine/interpreter/PowerShellInstructions.cs index 926f558a72b..5592af6f526 100644 --- a/src/System.Management.Automation/engine/interpreter/PowerShellInstructions.cs +++ b/src/System.Management.Automation/engine/interpreter/PowerShellInstructions.cs @@ -15,7 +15,7 @@ namespace System.Management.Automation.Interpreter { - internal class UpdatePositionInstruction : Instruction + internal sealed class UpdatePositionInstruction : Instruction { private readonly int _sequencePoint; private readonly bool _checkBreakpoints; diff --git a/src/System.Management.Automation/engine/interpreter/Utilities.cs b/src/System.Management.Automation/engine/interpreter/Utilities.cs index be7bb1935c7..0eef4728e46 100644 --- a/src/System.Management.Automation/engine/interpreter/Utilities.cs +++ b/src/System.Management.Automation/engine/interpreter/Utilities.cs @@ -147,7 +147,7 @@ internal static Type MakeDelegate(Type[] types) // Can only used predefined delegates if we have no byref types and // the arity is small enough to fit in Func<...> or Action<...> - if (types.Length > MaximumArity || types.Any(t => t.IsByRef)) + if (types.Length > MaximumArity || types.Any(static t => t.IsByRef)) { throw Assert.Unreachable; // return MakeCustomDelegate(types); @@ -1078,7 +1078,10 @@ internal static bool TrueForAll(this IEnumerable collection, Predicate foreach (T item in collection) { - if (!predicate(item)) return false; + if (!predicate(item)) + { + return false; + } } return true; diff --git a/src/System.Management.Automation/engine/lang/interface/PSToken.cs b/src/System.Management.Automation/engine/lang/interface/PSToken.cs index 048de2facac..306a64a0b61 100644 --- a/src/System.Management.Automation/engine/lang/interface/PSToken.cs +++ b/src/System.Management.Automation/engine/lang/interface/PSToken.cs @@ -370,223 +370,275 @@ public enum PSTokenType /// /// Unknown token. /// - /// - /// Unknown, /// + /// /// Command. - /// - /// + /// + /// /// For example, 'get-process' in /// - /// get-process -name foo - /// + /// get-process -name foo + /// + /// Command, /// + /// /// Command Parameter. - /// - /// + /// + /// /// For example, '-name' in /// - /// get-process -name foo - /// + /// get-process -name foo + /// + /// CommandParameter, /// + /// /// Command Argument. - /// - /// + /// + /// /// For example, 'foo' in /// - /// get-process -name foo - /// + /// get-process -name foo + /// + /// CommandArgument, /// + /// /// Number. - /// - /// + /// + /// /// For example, 12 in /// - /// $a=12 - /// + /// $a=12 + /// + /// Number, /// + /// /// String. - /// - /// + /// + /// /// For example, "12" in /// - /// $a="12" - /// + /// $a="12" + /// + /// String, /// + /// /// Variable. - /// + /// + /// /// /// For example, $a in /// - /// $a="12" - /// + /// $a="12" + /// + /// Variable, /// + /// /// Property name or method name. - /// - /// + /// + /// /// For example, Name in /// - /// $a.Name - /// + /// $a.Name + /// + /// Member, /// + /// /// Loop label. - /// - /// + /// + /// /// For example, :loop in /// + /// /// :loop /// foreach($a in $b) /// { /// $a /// } - /// + /// + /// LoopLabel, /// + /// /// Attributes. - /// - /// + /// + /// /// For example, Mandatory in /// - /// param([Mandatory] $a) - /// + /// param([Mandatory] $a) + /// + /// Attribute, /// + /// /// Types. - /// - /// + /// + /// /// For example, [string] in /// - /// $a = [string] 12 - /// + /// $a = [string] 12 + /// + /// Type, /// + /// /// Operators. - /// - /// + /// + /// /// For example, + in /// - /// $a = 1 + 2 - /// + /// $a = 1 + 2 + /// + /// Operator, /// + /// /// Group Starter. - /// - /// + /// + /// /// For example, { in /// + /// /// if ($a -gt 4) /// { /// $a++; /// } - /// + /// + /// + /// GroupStart, /// + /// /// Group Ender. - /// - /// + /// + /// /// For example, } in /// + /// /// if ($a -gt 4) /// { /// $a++; /// } - /// + /// + /// + /// GroupEnd, /// + /// /// Keyword. - /// - /// + /// + /// /// For example, if in /// + /// /// if ($a -gt 4) /// { /// $a++; /// } - /// + /// + /// + /// Keyword, /// + /// /// Comment. - /// - /// + /// + /// /// For example, #here in /// + /// /// #here /// if ($a -gt 4) /// { /// $a++; /// } - /// + /// + /// + /// Comment, /// + /// /// Statement separator. This is ';' - /// - /// + /// + /// /// For example, ; in /// + /// /// #here /// if ($a -gt 4) /// { /// $a++; /// } - /// + /// + /// + /// StatementSeparator, /// + /// /// New line. This is '\n' - /// - /// + /// + /// /// For example, \n in /// + /// /// #here /// if ($a -gt 4) /// { /// $a++; /// } - /// + /// + /// + /// NewLine, /// + /// /// Line continuation. - /// - /// + /// + /// /// For example, ` in /// + /// /// get-command -name ` /// foo - /// + /// + /// + /// LineContinuation, /// + /// /// Position token. - /// - /// - /// Position token are bogus tokens generated for identifying a location + /// + /// + /// Position tokens are bogus tokens generated for identifying a location /// in the script. - /// + /// + /// Position } } diff --git a/src/System.Management.Automation/engine/lang/parserutils.cs b/src/System.Management.Automation/engine/lang/parserutils.cs index 52c64400ccd..eb438a8a057 100644 --- a/src/System.Management.Automation/engine/lang/parserutils.cs +++ b/src/System.Management.Automation/engine/lang/parserutils.cs @@ -28,11 +28,6 @@ namespace System.Management.Automation public abstract class FlowControlException : SystemException { internal FlowControlException() { } - - internal FlowControlException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } /// @@ -45,11 +40,6 @@ internal LoopFlowException(string label) this.Label = label ?? string.Empty; } - internal LoopFlowException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - internal LoopFlowException() { } /// @@ -95,11 +85,6 @@ internal BreakException(string label, Exception innerException) : base(label) { } - - private BreakException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } /// @@ -121,11 +106,6 @@ internal ContinueException(string label, Exception innerException) : base(label) { } - - private ContinueException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } internal class ReturnException : FlowControlException @@ -156,12 +136,6 @@ internal ExitException(object argument) [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "This exception should only be thrown from SMA.dll")] internal ExitException() { } - - [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "This exception should only be thrown from SMA.dll")] - private ExitException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } /// @@ -251,7 +225,7 @@ public enum SplitOptions internal delegate object PowerShellBinaryOperator(ExecutionContext context, IScriptExtent errorPosition, object lval, object rval); /// - /// A static class holding various operations specific to the msh interpreter such as + /// A static class holding various operations specific to the PowerShell interpreter such as /// various math operations, ToString() and a routine to extract the base object from an /// PSObject in a canonical fashion. /// @@ -969,15 +943,7 @@ internal static object ReplaceOperator(ExecutionContext context, IScriptExtent e IEnumerator list = LanguagePrimitives.GetEnumerator(lval); if (list == null) { - string lvalString; - if (ExperimentalFeature.IsEnabled("PSCultureInvariantReplaceOperator")) - { - lvalString = PSObject.ToStringParser(context, lval) ?? string.Empty; - } - else - { - lvalString = lval?.ToString() ?? string.Empty; - } + string lvalString = PSObject.ToStringParser(context, lval) ?? string.Empty; return replacer.Replace(lvalString); } @@ -1518,7 +1484,7 @@ internal static string GetTypeFullName(object obj) /// methods and ScriptBlock notes. Native methods currently take precedence over notes... /// /// The position to use for error reporting. - /// The object to call the method on. It shouldn't be an msh object. + /// The object to call the method on. It shouldn't be a PSObject. /// The name of the method to call. /// Invocation constraints. /// The arguments to pass to the method. @@ -1981,6 +1947,22 @@ internal static void UpdateExceptionErrorRecordPosition(Exception exception, ISc } } } + + internal static void UpdateExceptionErrorRecordHistoryId(RuntimeException exception, ExecutionContext context) + { + InvocationInfo invInfo = exception.ErrorRecord.InvocationInfo; + if (invInfo is not { HistoryId: -1 }) + { + return; + } + + if (context?.CurrentCommandProcessor is null) + { + return; + } + + invInfo.HistoryId = context.CurrentCommandProcessor.Command.MyInvocation.HistoryId; + } } #endregion InterpreterError diff --git a/src/System.Management.Automation/engine/lang/scriptblock.cs b/src/System.Management.Automation/engine/lang/scriptblock.cs index 765809be306..7bbd33c9cfd 100644 --- a/src/System.Management.Automation/engine/lang/scriptblock.cs +++ b/src/System.Management.Automation/engine/lang/scriptblock.cs @@ -545,7 +545,7 @@ internal T InvokeAsMemberFunctionT(object instance, object[] args) // is a pipeline that emits nothing then result.Count will // be zero so we catch that and "convert" it to null. Note that // the return statement is still required in the method, it - // just recieves nothing from it's argument. + // just receives nothing from it's argument. if (result.Count == 0) { return default(T); @@ -778,7 +778,7 @@ internal Delegate CreateDelegate(Type delegateType) CachedReflectionInfo.ScriptBlock_InvokeAsDelegateHelper, dollarUnderExpr, dollarThisExpr, - Expression.NewArrayInit(typeof(object), parameterExprs.Select(p => p.Cast(typeof(object))))); + Expression.NewArrayInit(typeof(object), parameterExprs.Select(static p => p.Cast(typeof(object))))); if (returnsSomething) { call = DynamicExpression.Dynamic( @@ -1035,10 +1035,7 @@ internal void InvokeWithPipe( processInCurrentThread: true, waitForCompletionInCurrentThread: true); - if (scriptBlockInvocationEventArgs.Exception != null) - { - scriptBlockInvocationEventArgs.Exception.Throw(); - } + scriptBlockInvocationEventArgs.Exception?.Throw(); } } @@ -1099,15 +1096,9 @@ public sealed class SteppablePipeline : IDisposable { internal SteppablePipeline(ExecutionContext context, PipelineProcessor pipeline) { - if (pipeline == null) - { - throw new ArgumentNullException(nameof(pipeline)); - } + ArgumentNullException.ThrowIfNull(pipeline); - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } + ArgumentNullException.ThrowIfNull(context); _pipeline = pipeline; _context = context; @@ -1131,10 +1122,7 @@ internal SteppablePipeline(ExecutionContext context, PipelineProcessor pipeline) /// Context used to figure out how to route the output and errors. public void Begin(bool expectInput, EngineIntrinsics contextToRedirectTo) { - if (contextToRedirectTo == null) - { - throw new ArgumentNullException(nameof(contextToRedirectTo)); - } + ArgumentNullException.ThrowIfNull(contextToRedirectTo); ExecutionContext executionContext = contextToRedirectTo.SessionState.Internal.ExecutionContext; CommandProcessorBase commandProcessor = executionContext.CurrentCommandProcessor; @@ -1150,7 +1138,7 @@ public void Begin(bool expectInput, EngineIntrinsics contextToRedirectTo) /// The command you're calling this from (i.e. instance of PSCmdlet or value of $PSCmdlet variable). public void Begin(InternalCommand command) { - if (command == null || command.MyInvocation == null) + if (command is null || command.MyInvocation is null) { throw new ArgumentNullException(nameof(command)); } @@ -1280,7 +1268,44 @@ public Array End() { // then pop this pipeline and dispose it... _context.PopPipelineProcessor(true); - _pipeline.Dispose(); + Dispose(); + } + } + + /// + /// Clean resources for script commands of this steppable pipeline. + /// + /// + /// + /// 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. + /// + /// 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. + /// + public void Clean() + { + if (_pipeline.Commands is null) + { + // The pipeline commands have been disposed. In this case, 'Clean' + // should have already been called on the pipeline processor. + return; + } + + try + { + _context.PushPipelineProcessor(_pipeline); + _pipeline.DoCleanup(); + } + finally + { + // then pop this pipeline and dispose it... + _context.PopPipelineProcessor(true); + Dispose(); } } @@ -1293,23 +1318,13 @@ public Array End() /// When this object is disposed, the contained pipeline should also be disposed. /// public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) { if (_disposed) { return; } - if (disposing) - { - _pipeline.Dispose(); - } - + _pipeline.Dispose(); _disposed = true; } @@ -1320,7 +1335,6 @@ private void Dispose(bool disposing) /// Defines the exception thrown when conversion from ScriptBlock to PowerShell is forbidden /// (i.e. when the script block has undeclared variables or more than one statement) /// - [Serializable] public class ScriptBlockToPowerShellNotSupportedException : RuntimeException { #region ctor @@ -1347,7 +1361,7 @@ public ScriptBlockToPowerShellNotSupportedException(string message) /// Initializes a new instance of ScriptBlockToPowerShellNotSupportedException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. + /// The exception's inner exception. public ScriptBlockToPowerShellNotSupportedException(string message, Exception innerException) : base(message, innerException) { @@ -1374,9 +1388,10 @@ internal ScriptBlockToPowerShellNotSupportedException( /// /// Serialization information. /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ScriptBlockToPowerShellNotSupportedException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion Serialization diff --git a/src/System.Management.Automation/engine/parser/AstVisitor.cs b/src/System.Management.Automation/engine/parser/AstVisitor.cs index 5c09ee78dc6..03b234cf41f 100644 --- a/src/System.Management.Automation/engine/parser/AstVisitor.cs +++ b/src/System.Management.Automation/engine/parser/AstVisitor.cs @@ -10,195 +10,197 @@ namespace System.Management.Automation.Language { /// /// +#nullable enable public interface ICustomAstVisitor { /// - object DefaultVisit(Ast ast) => null; + object? DefaultVisit(Ast ast) => null; /// - object VisitErrorStatement(ErrorStatementAst errorStatementAst) => DefaultVisit(errorStatementAst); + object? VisitErrorStatement(ErrorStatementAst errorStatementAst) => DefaultVisit(errorStatementAst); /// - object VisitErrorExpression(ErrorExpressionAst errorExpressionAst) => DefaultVisit(errorExpressionAst); + object? VisitErrorExpression(ErrorExpressionAst errorExpressionAst) => DefaultVisit(errorExpressionAst); #region Script Blocks /// - object VisitScriptBlock(ScriptBlockAst scriptBlockAst) => DefaultVisit(scriptBlockAst); + object? VisitScriptBlock(ScriptBlockAst scriptBlockAst) => DefaultVisit(scriptBlockAst); /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Param")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "param")] - object VisitParamBlock(ParamBlockAst paramBlockAst) => DefaultVisit(paramBlockAst); + object? VisitParamBlock(ParamBlockAst paramBlockAst) => DefaultVisit(paramBlockAst); /// - object VisitNamedBlock(NamedBlockAst namedBlockAst) => DefaultVisit(namedBlockAst); + object? VisitNamedBlock(NamedBlockAst namedBlockAst) => DefaultVisit(namedBlockAst); /// - object VisitTypeConstraint(TypeConstraintAst typeConstraintAst) => DefaultVisit(typeConstraintAst); + object? VisitTypeConstraint(TypeConstraintAst typeConstraintAst) => DefaultVisit(typeConstraintAst); /// - object VisitAttribute(AttributeAst attributeAst) => DefaultVisit(attributeAst); + object? VisitAttribute(AttributeAst attributeAst) => DefaultVisit(attributeAst); /// - object VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) => DefaultVisit(namedAttributeArgumentAst); + object? VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) => DefaultVisit(namedAttributeArgumentAst); /// - object VisitParameter(ParameterAst parameterAst) => DefaultVisit(parameterAst); + object? VisitParameter(ParameterAst parameterAst) => DefaultVisit(parameterAst); #endregion Script Blocks #region Statements /// - object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) => DefaultVisit(functionDefinitionAst); + object? VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) => DefaultVisit(functionDefinitionAst); /// - object VisitStatementBlock(StatementBlockAst statementBlockAst) => DefaultVisit(statementBlockAst); + object? VisitStatementBlock(StatementBlockAst statementBlockAst) => DefaultVisit(statementBlockAst); /// - object VisitIfStatement(IfStatementAst ifStmtAst) => DefaultVisit(ifStmtAst); + object? VisitIfStatement(IfStatementAst ifStmtAst) => DefaultVisit(ifStmtAst); /// - object VisitTrap(TrapStatementAst trapStatementAst) => DefaultVisit(trapStatementAst); + object? VisitTrap(TrapStatementAst trapStatementAst) => DefaultVisit(trapStatementAst); /// - object VisitSwitchStatement(SwitchStatementAst switchStatementAst) => DefaultVisit(switchStatementAst); + object? VisitSwitchStatement(SwitchStatementAst switchStatementAst) => DefaultVisit(switchStatementAst); /// - object VisitDataStatement(DataStatementAst dataStatementAst) => DefaultVisit(dataStatementAst); + object? VisitDataStatement(DataStatementAst dataStatementAst) => DefaultVisit(dataStatementAst); /// - object VisitForEachStatement(ForEachStatementAst forEachStatementAst) => DefaultVisit(forEachStatementAst); + object? VisitForEachStatement(ForEachStatementAst forEachStatementAst) => DefaultVisit(forEachStatementAst); /// - object VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) => DefaultVisit(doWhileStatementAst); + object? VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) => DefaultVisit(doWhileStatementAst); /// - object VisitForStatement(ForStatementAst forStatementAst) => DefaultVisit(forStatementAst); + object? VisitForStatement(ForStatementAst forStatementAst) => DefaultVisit(forStatementAst); /// - object VisitWhileStatement(WhileStatementAst whileStatementAst) => DefaultVisit(whileStatementAst); + object? VisitWhileStatement(WhileStatementAst whileStatementAst) => DefaultVisit(whileStatementAst); /// - object VisitCatchClause(CatchClauseAst catchClauseAst) => DefaultVisit(catchClauseAst); + object? VisitCatchClause(CatchClauseAst catchClauseAst) => DefaultVisit(catchClauseAst); /// - object VisitTryStatement(TryStatementAst tryStatementAst) => DefaultVisit(tryStatementAst); + object? VisitTryStatement(TryStatementAst tryStatementAst) => DefaultVisit(tryStatementAst); /// - object VisitBreakStatement(BreakStatementAst breakStatementAst) => DefaultVisit(breakStatementAst); + object? VisitBreakStatement(BreakStatementAst breakStatementAst) => DefaultVisit(breakStatementAst); /// - object VisitContinueStatement(ContinueStatementAst continueStatementAst) => DefaultVisit(continueStatementAst); + object? VisitContinueStatement(ContinueStatementAst continueStatementAst) => DefaultVisit(continueStatementAst); /// - object VisitReturnStatement(ReturnStatementAst returnStatementAst) => DefaultVisit(returnStatementAst); + object? VisitReturnStatement(ReturnStatementAst returnStatementAst) => DefaultVisit(returnStatementAst); /// - object VisitExitStatement(ExitStatementAst exitStatementAst) => DefaultVisit(exitStatementAst); + object? VisitExitStatement(ExitStatementAst exitStatementAst) => DefaultVisit(exitStatementAst); /// - object VisitThrowStatement(ThrowStatementAst throwStatementAst) => DefaultVisit(throwStatementAst); + object? VisitThrowStatement(ThrowStatementAst throwStatementAst) => DefaultVisit(throwStatementAst); /// - object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) => DefaultVisit(doUntilStatementAst); + object? VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) => DefaultVisit(doUntilStatementAst); /// - object VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) => DefaultVisit(assignmentStatementAst); + object? VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) => DefaultVisit(assignmentStatementAst); #endregion Statements #region Pipelines /// - object VisitPipeline(PipelineAst pipelineAst) => DefaultVisit(pipelineAst); + object? VisitPipeline(PipelineAst pipelineAst) => DefaultVisit(pipelineAst); /// - object VisitCommand(CommandAst commandAst) => DefaultVisit(commandAst); + object? VisitCommand(CommandAst commandAst) => DefaultVisit(commandAst); /// - object VisitCommandExpression(CommandExpressionAst commandExpressionAst) => DefaultVisit(commandExpressionAst); + object? VisitCommandExpression(CommandExpressionAst commandExpressionAst) => DefaultVisit(commandExpressionAst); /// - object VisitCommandParameter(CommandParameterAst commandParameterAst) => DefaultVisit(commandParameterAst); + object? VisitCommandParameter(CommandParameterAst commandParameterAst) => DefaultVisit(commandParameterAst); /// - object VisitFileRedirection(FileRedirectionAst fileRedirectionAst) => DefaultVisit(fileRedirectionAst); + object? VisitFileRedirection(FileRedirectionAst fileRedirectionAst) => DefaultVisit(fileRedirectionAst); /// - object VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst) => DefaultVisit(mergingRedirectionAst); + object? VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst) => DefaultVisit(mergingRedirectionAst); #endregion Pipelines #region Expressions /// - object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) => DefaultVisit(binaryExpressionAst); + object? VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) => DefaultVisit(binaryExpressionAst); /// - object VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst) => DefaultVisit(unaryExpressionAst); + object? VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst) => DefaultVisit(unaryExpressionAst); /// - object VisitConvertExpression(ConvertExpressionAst convertExpressionAst) => DefaultVisit(convertExpressionAst); + object? VisitConvertExpression(ConvertExpressionAst convertExpressionAst) => DefaultVisit(convertExpressionAst); /// - object VisitConstantExpression(ConstantExpressionAst constantExpressionAst) => DefaultVisit(constantExpressionAst); + object? VisitConstantExpression(ConstantExpressionAst constantExpressionAst) => DefaultVisit(constantExpressionAst); /// - object VisitStringConstantExpression(StringConstantExpressionAst stringConstantExpressionAst) => DefaultVisit(stringConstantExpressionAst); + object? VisitStringConstantExpression(StringConstantExpressionAst stringConstantExpressionAst) => DefaultVisit(stringConstantExpressionAst); /// [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "SubExpression")] [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "subExpression")] - object VisitSubExpression(SubExpressionAst subExpressionAst) => DefaultVisit(subExpressionAst); + object? VisitSubExpression(SubExpressionAst subExpressionAst) => DefaultVisit(subExpressionAst); /// - object VisitUsingExpression(UsingExpressionAst usingExpressionAst) => DefaultVisit(usingExpressionAst); + object? VisitUsingExpression(UsingExpressionAst usingExpressionAst) => DefaultVisit(usingExpressionAst); /// - object VisitVariableExpression(VariableExpressionAst variableExpressionAst) => DefaultVisit(variableExpressionAst); + object? VisitVariableExpression(VariableExpressionAst variableExpressionAst) => DefaultVisit(variableExpressionAst); /// - object VisitTypeExpression(TypeExpressionAst typeExpressionAst) => DefaultVisit(typeExpressionAst); + object? VisitTypeExpression(TypeExpressionAst typeExpressionAst) => DefaultVisit(typeExpressionAst); /// - object VisitMemberExpression(MemberExpressionAst memberExpressionAst) => DefaultVisit(memberExpressionAst); + object? VisitMemberExpression(MemberExpressionAst memberExpressionAst) => DefaultVisit(memberExpressionAst); /// - object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) => DefaultVisit(invokeMemberExpressionAst); + object? VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) => DefaultVisit(invokeMemberExpressionAst); /// - object VisitArrayExpression(ArrayExpressionAst arrayExpressionAst) => DefaultVisit(arrayExpressionAst); + object? VisitArrayExpression(ArrayExpressionAst arrayExpressionAst) => DefaultVisit(arrayExpressionAst); /// - object VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst) => DefaultVisit(arrayLiteralAst); + object? VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst) => DefaultVisit(arrayLiteralAst); /// - object VisitHashtable(HashtableAst hashtableAst) => DefaultVisit(hashtableAst); + object? VisitHashtable(HashtableAst hashtableAst) => DefaultVisit(hashtableAst); /// - object VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst) => DefaultVisit(scriptBlockExpressionAst); + object? VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst) => DefaultVisit(scriptBlockExpressionAst); /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Paren")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "paren")] - object VisitParenExpression(ParenExpressionAst parenExpressionAst) => DefaultVisit(parenExpressionAst); + object? VisitParenExpression(ParenExpressionAst parenExpressionAst) => DefaultVisit(parenExpressionAst); /// - object VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst) => DefaultVisit(expandableStringExpressionAst); + object? VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst) => DefaultVisit(expandableStringExpressionAst); /// - object VisitIndexExpression(IndexExpressionAst indexExpressionAst) => DefaultVisit(indexExpressionAst); + object? VisitIndexExpression(IndexExpressionAst indexExpressionAst) => DefaultVisit(indexExpressionAst); /// - object VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst) => DefaultVisit(attributedExpressionAst); + object? VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst) => DefaultVisit(attributedExpressionAst); /// - object VisitBlockStatement(BlockStatementAst blockStatementAst) => DefaultVisit(blockStatementAst); + object? VisitBlockStatement(BlockStatementAst blockStatementAst) => DefaultVisit(blockStatementAst); #endregion Expressions } +#nullable restore /// #nullable enable diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 794b2834331..5ce6a6c6dc8 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -39,6 +39,9 @@ internal static class CachedReflectionInfo internal static readonly MethodInfo ObjectList_ToArray = typeof(List).GetMethod(nameof(List.ToArray), Type.EmptyTypes); + internal static readonly MethodInfo ArrayOps_AddObject = + typeof(ArrayOps).GetMethod(nameof(ArrayOps.AddObjectArray), StaticFlags); + internal static readonly MethodInfo ArrayOps_GetMDArrayValue = typeof(ArrayOps).GetMethod(nameof(ArrayOps.GetMDArrayValue), StaticFlags); @@ -252,6 +255,9 @@ internal static class CachedReflectionInfo new Type[] { typeof(int), typeof(IEqualityComparer) }, null); + internal static readonly MethodInfo ByRefOps_GetByRefPropertyValue = + typeof(ByRefOps).GetMethod(nameof(ByRefOps.GetByRefPropertyValue), StaticFlags); + internal static readonly MethodInfo HashtableOps_Add = typeof(HashtableOps).GetMethod(nameof(HashtableOps.Add), StaticFlags); @@ -432,8 +438,8 @@ internal static class CachedReflectionInfo internal static readonly MethodInfo PSInvokeMemberBinder_IsHeterogeneousArray = typeof(PSInvokeMemberBinder).GetMethod(nameof(PSInvokeMemberBinder.IsHeterogeneousArray), StaticFlags); - internal static readonly MethodInfo PSInvokeMemberBinder_IsHomogenousArray = - typeof(PSInvokeMemberBinder).GetMethod(nameof(PSInvokeMemberBinder.IsHomogenousArray), StaticFlags); + internal static readonly MethodInfo PSInvokeMemberBinder_IsHomogeneousArray = + typeof(PSInvokeMemberBinder).GetMethod(nameof(PSInvokeMemberBinder.IsHomogeneousArray), StaticFlags); internal static readonly MethodInfo PSInvokeMemberBinder_TryGetInstanceMethod = typeof(PSInvokeMemberBinder).GetMethod(nameof(PSInvokeMemberBinder.TryGetInstanceMethod), StaticFlags); @@ -471,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); @@ -639,7 +648,9 @@ internal static class CachedReflectionInfo internal static readonly MethodInfo ArgumentTransformationAttribute_Transform = typeof(ArgumentTransformationAttribute).GetMethod(nameof(ArgumentTransformationAttribute.Transform), InstancePublicFlags); - // ReSharper restore InconsistentNaming + + internal static readonly MethodInfo MemberInvocationLoggingOps_LogMemberInvocation = + typeof(MemberInvocationLoggingOps).GetMethod(nameof(MemberInvocationLoggingOps.LogMemberInvocation), StaticFlags); } internal static class ExpressionCache @@ -777,7 +788,7 @@ internal class FunctionContext internal ExecutionContext _executionContext; internal Pipe _outputPipe; internal BitArray _breakPoints; - internal List _boundBreakpoints; + internal Dictionary> _boundBreakpoints; internal int _currentSequencePointIndex; internal MutableTuple _localsTuple; internal List[], Type[]>> _traps = new List[], Type[]>>(); @@ -825,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"); @@ -1059,7 +1078,7 @@ internal static Expression CallSetVariable(Expression variablePath, Expression r internal Expression GetAutomaticVariable(VariableExpressionAst varAst) { - // Generate, in psuedo code: + // Generate, in pseudo code: // // return (localsTuple.IsValueSet(tupleIndex) // ? localsTuple.ItemXXX @@ -1069,7 +1088,7 @@ internal Expression GetAutomaticVariable(VariableExpressionAst varAst) // // * $PSCmdlet - always set if the script uses cmdletbinding. // * $_ - always set in process and end block, otherwise need dynamic checks. - // * $this - can never know if it's set, always need above psuedo code. + // * $this - can never know if it's set, always need above pseudo code. // * $input - also can never know - it's always set from a command process, but not necessarily set from ScriptBlock.Invoke. // // These optimizations are not yet performed. @@ -1105,10 +1124,7 @@ internal static Expression CallStringEquals(Expression left, Expression right, b internal static Expression IsStrictMode(int version, Expression executionContext = null) { - if (executionContext == null) - { - executionContext = ExpressionCache.NullExecutionContext; - } + executionContext ??= ExpressionCache.NullExecutionContext; return Expression.Call( CachedReflectionInfo.ExecutionContext_IsStrictVersion, @@ -1147,7 +1163,7 @@ private Expression UpdatePosition(Ast ast) internal ParameterExpression NewTemp(Type type, string name) { - return Expression.Variable(type, string.Format(CultureInfo.InvariantCulture, "{0}{1}", name, _tempCounter++)); + return Expression.Variable(type, string.Create(CultureInfo.InvariantCulture, $"{name}{_tempCounter++}")); } internal static Type GetTypeConstraintForMethodResolution(ExpressionAst expr) @@ -1174,7 +1190,7 @@ internal static Type GetTypeConstraintForMethodResolution(ExpressionAst expr) internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution(Type targetType, Type argType) { - if (targetType == null && argType == null) + if (targetType is null && argType is null) { return null; } @@ -1182,14 +1198,19 @@ internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodReso return new PSMethodInvocationConstraints(targetType, new[] { argType }); } - internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution(Type targetType, Type[] argTypes) + internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution( + Type targetType, + Type[] argTypes, + object[] genericArguments = null) { - if (targetType == null && (argTypes == null || argTypes.Length == 0)) + if (targetType is null + && (argTypes is null || argTypes.Length == 0) + && (genericArguments is null || genericArguments.Length == 0)) { return null; } - return new PSMethodInvocationConstraints(targetType, argTypes); + return new PSMethodInvocationConstraints(targetType, argTypes, genericArguments); } internal static Expression ConvertValue(TypeConstraintAst typeConstraint, Expression expr) @@ -1257,7 +1278,7 @@ internal static RuntimeDefinedParameterDictionary GetParameterMetaData(ReadOnlyC for (int index = 0; index < runtimeDefinedParamList.Count; index++) { var rdp = runtimeDefinedParamList[index]; - var paramAttribute = (ParameterAttribute)rdp.Attributes.First(attr => attr is ParameterAttribute); + var paramAttribute = (ParameterAttribute)rdp.Attributes.First(static attr => attr is ParameterAttribute); if (rdp.ParameterType != typeof(SwitchParameter)) { paramAttribute.Position = pos++; @@ -1558,7 +1579,15 @@ private static Attribute NewOutputTypeAttribute(AttributeAst ast) if (args[0] is Type) { - result = new OutputTypeAttribute(LanguagePrimitives.ConvertTo(args)); + // We avoid `ConvertTo(args)` here as CLM would throw due to `Type[]` + // being a "non-core" type. NOTE: This doesn't apply to `string[]`. + Type[] types = new Type[args.Length]; + for (int i = 0; i < args.Length; i++) + { + types[i] = LanguagePrimitives.ConvertTo(args[i]); + } + + result = new OutputTypeAttribute(types); } else { @@ -1719,7 +1748,7 @@ internal static Attribute GetAttribute(AttributeAst attributeAst) } var positionalArgCount = attributeAst.PositionalArguments.Count; - var argumentNames = attributeAst.NamedArguments.Select(name => name.ArgumentName).ToArray(); + var argumentNames = attributeAst.NamedArguments.Select(static name => name.ArgumentName).ToArray(); var totalArgCount = positionalArgCount + argumentNames.Length; var callInfo = new CallInfo(totalArgCount, argumentNames); @@ -1748,18 +1777,15 @@ internal static Attribute GetAttribute(AttributeAst attributeAst) // Unwrap the wrapped exception var innerException = tie.InnerException; var rte = innerException as RuntimeException; - if (rte == null) - { - rte = InterpreterError.NewInterpreterExceptionWithInnerException( - null, - typeof(RuntimeException), - attributeAst.Extent, - "ExceptionConstructingAttribute", - ExtendedTypeSystem.ExceptionConstructingAttribute, - innerException, - innerException.Message, - attributeAst.TypeName.FullName); - } + rte ??= InterpreterError.NewInterpreterExceptionWithInnerException( + null, + typeof(RuntimeException), + attributeAst.Extent, + "ExceptionConstructingAttribute", + ExtendedTypeSystem.ExceptionConstructingAttribute, + innerException, + innerException.Message, + attributeAst.TypeName.FullName); InterpreterError.UpdateExceptionErrorRecordPosition(rte, attributeAst.Extent); throw rte; @@ -2026,6 +2052,7 @@ internal void Compile(CompiledScriptBlockData scriptBlock, bool optimize) scriptBlock.BeginBlock = CompileTree(_beginBlockLambda, compileInterpretChoice); scriptBlock.ProcessBlock = CompileTree(_processBlockLambda, compileInterpretChoice); scriptBlock.EndBlock = CompileTree(_endBlockLambda, compileInterpretChoice); + scriptBlock.CleanBlock = CompileTree(_cleanBlockLambda, compileInterpretChoice); scriptBlock.LocalsMutableTupleType = LocalVariablesTupleType; scriptBlock.LocalsMutableTupleCreator = MutableTuple.TupleCreator(LocalVariablesTupleType); scriptBlock.NameToIndexMap = nameToIndexMap; @@ -2036,16 +2063,14 @@ internal void Compile(CompiledScriptBlockData scriptBlock, bool optimize) scriptBlock.UnoptimizedBeginBlock = CompileTree(_beginBlockLambda, compileInterpretChoice); scriptBlock.UnoptimizedProcessBlock = CompileTree(_processBlockLambda, compileInterpretChoice); scriptBlock.UnoptimizedEndBlock = CompileTree(_endBlockLambda, compileInterpretChoice); + scriptBlock.UnoptimizedCleanBlock = CompileTree(_cleanBlockLambda, compileInterpretChoice); scriptBlock.UnoptimizedLocalsMutableTupleType = LocalVariablesTupleType; scriptBlock.UnoptimizedLocalsMutableTupleCreator = MutableTuple.TupleCreator(LocalVariablesTupleType); } // The sequence points are identical optimized or not. Regardless, we want to ensure // that the list is unique no matter when the property is accessed, so make sure it is set just once. - if (scriptBlock.SequencePoints == null) - { - scriptBlock.SequencePoints = _sequencePoints.ToArray(); - } + scriptBlock.SequencePoints ??= _sequencePoints.ToArray(); } private static Action CompileTree(Expression> lambda, CompileInterpretChoice compileInterpretChoice) @@ -2114,10 +2139,7 @@ private static object GetExpressionValue( // Can't be exposed to untrusted input - invoking arbitrary code could result in remote code // execution. - if (lambda == null) - { - lambda = (new Compiler()).CompileSingleExpression(expressionAst, out sequencePoints, out localsTupleType); - } + lambda ??= (new Compiler()).CompileSingleExpression(expressionAst, out sequencePoints, out localsTupleType); SessionStateInternal oldSessionState = context.EngineSessionState; try @@ -2200,7 +2222,7 @@ private Func CompileSingleExpression(ExpressionAst expr return Expression.Lambda>(body, parameters).Compile(); } - private class LoopGotoTargets + private sealed class LoopGotoTargets { internal LoopGotoTargets(string label, LabelTarget breakLabel, LabelTarget continueLabel) { @@ -2221,6 +2243,7 @@ internal LoopGotoTargets(string label, LabelTarget breakLabel, LabelTarget conti private Expression> _beginBlockLambda; private Expression> _processBlockLambda; private Expression> _endBlockLambda; + private Expression> _cleanBlockLambda; private readonly List _loopTargets = new List(); private bool _generatingWhileOrDoLoop; @@ -2284,17 +2307,17 @@ private Expression CaptureAstResults( if (context == CaptureAstContext.AssignmentWithoutResultPreservation) { var catchExprs = new List - { - Expression.Call(CachedReflectionInfo.PipelineOps_ClearPipe, resultList), - Expression.Rethrow(), - Expression.Constant(null, typeof(object)) - }; + { + Expression.Call(CachedReflectionInfo.PipelineOps_ClearPipe, resultList), + Expression.Rethrow(), + Expression.Constant(null, typeof(object)) + }; catches.Add(Expression.Catch(typeof(RuntimeException), Expression.Block(typeof(object), catchExprs))); } - // PipelineResult might get skipped in some circumstances due to a FlowControlException thrown out, in which case - // we write to the oldPipe. This can happen in cases like: + // PipelineResult might get skipped in some circumstances due to an early return or a FlowControlException thrown out, + // in which case we write to the oldPipe. This can happen in cases like: // $(1;2;return 3) finallyExprs.Add(Expression.Call(CachedReflectionInfo.PipelineOps_FlushPipe, oldPipe, resultList)); break; @@ -2386,7 +2409,7 @@ private Expression CaptureStatementResults( // We do this after evaluating the condition so that you could do something like: // if ((dir file1,file2 -ea SilentlyContinue) -and $?) { <# files both exist, otherwise $? would be $false if 0 or 1 files existed #> } // - if (context == CaptureAstContext.Condition && AstSearcher.FindFirst(stmt, ast => ast is CommandAst, searchNestedScriptBlocks: false) != null) + if (context == CaptureAstContext.Condition && AstSearcher.FindFirst(stmt, static ast => ast is CommandAst, searchNestedScriptBlocks: false) != null) { var tmp = NewTemp(result.Type, "condTmp"); result = Expression.Block( @@ -2426,7 +2449,7 @@ public object VisitScriptBlock(ScriptBlockAst scriptBlockAst) var funcDefn = scriptBlockAst.Parent as FunctionDefinitionAst; var funcName = (funcDefn != null) ? funcDefn.Name : ""; - var rootForDefiningTypesAndUsings = scriptBlockAst.Find(ast => ast is TypeDefinitionAst || ast is UsingStatementAst, true) != null + var rootForDefiningTypesAndUsings = scriptBlockAst.Find(static ast => ast is TypeDefinitionAst || ast is UsingStatementAst, true) != null ? scriptBlockAst : null; @@ -2463,6 +2486,13 @@ public object VisitScriptBlock(ScriptBlockAst scriptBlockAst) } _endBlockLambda = CompileNamedBlock(scriptBlockAst.EndBlock, funcName, rootForDefiningTypesAndUsings); + rootForDefiningTypesAndUsings = null; + } + + if (scriptBlockAst.CleanBlock != null) + { + _cleanBlockLambda = CompileNamedBlock(scriptBlockAst.CleanBlock, funcName + "", rootForDefiningTypesAndUsings); + rootForDefiningTypesAndUsings = null; } return null; @@ -2566,7 +2596,7 @@ private Expression> CompileSingleLambda( // to the right place. We can avoid also avoid generating the catch if we know there aren't any traps. if (!_compilingTrap && ((traps != null && traps.Count > 0) - || statements.Any(stmt => AstSearcher.Contains(stmt, ast => ast is TrapStatementAst, searchNestedScriptBlocks: false)))) + || statements.Any(static stmt => AstSearcher.Contains(stmt, static ast => ast is TrapStatementAst, searchNestedScriptBlocks: false)))) { body = Expression.Block( new[] { s_executionContextParameter }, @@ -2605,12 +2635,12 @@ private static void GenerateTypesAndUsings(ScriptBlockAst rootForDefiningTypesAn { if (rootForDefiningTypesAndUsings.UsingStatements.Count > 0) { - bool allUsingsAreNamespaces = rootForDefiningTypesAndUsings.UsingStatements.All(us => us.UsingStatementKind == UsingStatementKind.Namespace); + bool allUsingsAreNamespaces = rootForDefiningTypesAndUsings.UsingStatements.All(static us => us.UsingStatementKind == UsingStatementKind.Namespace); GenerateLoadUsings(rootForDefiningTypesAndUsings.UsingStatements, allUsingsAreNamespaces, exprs); } TypeDefinitionAst[] typeAsts = - rootForDefiningTypesAndUsings.FindAll(ast => ast is TypeDefinitionAst, true) + rootForDefiningTypesAndUsings.FindAll(static ast => ast is TypeDefinitionAst, true) .Cast() .ToArray(); @@ -2628,9 +2658,9 @@ private static void GenerateTypesAndUsings(ScriptBlockAst rootForDefiningTypesAn } Dictionary typesToAddToScope = - rootForDefiningTypesAndUsings.FindAll(ast => ast is TypeDefinitionAst, false) + rootForDefiningTypesAndUsings.FindAll(static ast => ast is TypeDefinitionAst, false) .Cast() - .ToDictionary(type => type.Name); + .ToDictionary(static type => type.Name); if (typesToAddToScope.Count > 0) { exprs.Add( @@ -2794,15 +2824,9 @@ private static Assembly LoadAssembly(string assemblyName, string scriptFileName) { if (!string.IsNullOrEmpty(scriptFileName) && !Path.IsPathRooted(assemblyFileName)) { - assemblyFileName = Path.GetDirectoryName(scriptFileName) + "\\" + assemblyFileName; + assemblyFileName = Path.Combine(Path.GetDirectoryName(scriptFileName), assemblyFileName); } -#if !CORECLR - if (!File.Exists(assemblyFileName)) - { - Microsoft.CodeAnalysis.GlobalAssemblyCache.ResolvePartialName(assemblyName, out assemblyFileName); - } -#endif if (File.Exists(assemblyFileName)) { assembly = Assembly.LoadFrom(assemblyFileName); @@ -3279,9 +3303,9 @@ private bool ShouldSetExecutionStatusToSuccess(StatementAst statementAst) /// True is the compiler should add the success setting, false otherwise. private bool ShouldSetExecutionStatusToSuccess(PipelineAst pipelineAst) { - ExpressionAst expressionAst = pipelineAst.GetPureExpression(); + ExpressionAst expressionAst = GetSingleExpressionFromPipeline(pipelineAst); - // If the pipeline is not a simple expression, it will set $? + // If the pipeline is not a single expression, it will set $? if (expressionAst == null) { return false; @@ -3291,6 +3315,22 @@ private bool ShouldSetExecutionStatusToSuccess(PipelineAst pipelineAst) return ShouldSetExecutionStatusToSuccess(expressionAst); } + /// + /// If the pipeline contains a single expression, the expression is returned, otherwise null is returned. + /// This method is different from in that it allows the single + /// expression to have redirections. + /// + private static ExpressionAst GetSingleExpressionFromPipeline(PipelineAst pipelineAst) + { + var pipelineElements = pipelineAst.PipelineElements; + if (pipelineElements.Count == 1 && pipelineElements[0] is CommandExpressionAst expr) + { + return expr.Expression; + } + + return null; + } + /// /// Determines whether an assignment statement must have an explicit setting /// for $? = $true after it by the compiler. @@ -3584,7 +3624,7 @@ public object VisitPipelineChain(PipelineChainAst pipelineChainAst) var dispatchTargets = new List(); var tryBodyExprs = new List() { - null, // Add a slot for the inital switch/case that we'll come back to + null, // Add a slot for the initial switch/case that we'll come back to }; // L0: dispatchIndex = 1; pipeline1 @@ -3778,20 +3818,20 @@ public object VisitPipeline(PipelineAst pipelineAst) // one dimension because each command may have multiple redirections. Here we create the array for // each command in the pipe, either a compile time constant or created at runtime if necessary. Expression redirectionExpr; - if (commandRedirections.Any(r => r is Expression)) + if (commandRedirections.Any(static r => r is Expression)) { // If any command redirections are non-constant, commandRedirections will have a Linq.Expression in it, // in which case we must create the array at runtime redirectionExpr = Expression.NewArrayInit( typeof(CommandRedirection[]), - commandRedirections.Select(r => (r as Expression) ?? Expression.Constant(r, typeof(CommandRedirection[])))); + commandRedirections.Select(static r => (r as Expression) ?? Expression.Constant(r, typeof(CommandRedirection[])))); } - else if (commandRedirections.Any(r => r != null)) + else if (commandRedirections.Any(static r => r != null)) { // There were redirections, but all were compile time constant, so build the array at compile time. redirectionExpr = - Expression.Constant(commandRedirections.Map(r => r as CommandRedirection[])); + Expression.Constant(commandRedirections.Map(static r => r as CommandRedirection[])); } else { @@ -3839,15 +3879,15 @@ private object GetCommandRedirections(CommandBaseAst command) } // If there were any non-constant expressions, we must generate the array at runtime. - if (compiledRedirections.Any(r => r is Expression)) + if (compiledRedirections.Any(static r => r is Expression)) { return Expression.NewArrayInit( typeof(CommandRedirection), - compiledRedirections.Select(r => (r as Expression) ?? Expression.Constant(r))); + compiledRedirections.Select(static r => (r as Expression) ?? Expression.Constant(r))); } // Otherwise, we can use a compile time constant array. - return compiledRedirections.Map(r => (CommandRedirection)r); + return compiledRedirections.Map(static r => (CommandRedirection)r); } // A redirected expression requires extra work because there is no CommandProcessor or PipelineProcessor @@ -3871,7 +3911,7 @@ private Expression GetRedirectedExpression(CommandExpressionAst commandExpr, boo // funcContext.OutputPipe = oldPipe; // } // - // In the above psuedo-code, any of {outputFileRedirection, nonOutputFileRedirection, mergingRedirection} may + // In the above pseudo-code, any of {outputFileRedirection, nonOutputFileRedirection, mergingRedirection} may // not exist, but the order is preserved, so that file redirections go before merging redirections (so that // funcContext.OutputPipe has the correct value when setting up merging.) // @@ -3882,7 +3922,7 @@ private Expression GetRedirectedExpression(CommandExpressionAst commandExpr, boo // For the output stream, we change funcContext.OutputPipe so all output goes to the file. // Currently output can only be redirected to a file stream. bool outputRedirected = - commandExpr.Redirections.Any(r => r is FileRedirectionAst && + commandExpr.Redirections.Any(static r => r is FileRedirectionAst && (r.FromStream == RedirectionStream.Output || r.FromStream == RedirectionStream.All)); ParameterExpression resultList = null; @@ -3910,10 +3950,7 @@ private Expression GetRedirectedExpression(CommandExpressionAst commandExpr, boo // This will simply return a Linq.Expression representing the redirection. var compiledRedirection = VisitFileRedirection(fileRedirectionAst); - if (extraFileRedirectExprs == null) - { - extraFileRedirectExprs = new List(commandExpr.Redirections.Count); - } + extraFileRedirectExprs ??= new List(commandExpr.Redirections.Count); // Hold the current 'FileRedirection' instance for later use var redirectionExpr = NewTemp(typeof(FileRedirection), "fileRedirection"); @@ -4277,7 +4314,7 @@ internal static Expression ThrowRuntimeError(string errorID, string resourceStri internal static Expression ThrowRuntimeError(Type exceptionType, string errorID, string resourceString, Type throwResultType, params Expression[] exceptionArgs) { var exceptionArgArray = exceptionArgs != null - ? Expression.NewArrayInit(typeof(object), exceptionArgs.Select(e => e.Cast(typeof(object)))) + ? Expression.NewArrayInit(typeof(object), exceptionArgs.Select(static e => e.Cast(typeof(object)))) : ExpressionCache.NullConstant; Expression[] argExprs = new Expression[] { @@ -5142,7 +5179,7 @@ public object VisitCatchClause(CatchClauseAst catchClauseAst) // If the automatic var has no value in the current frame, then we set the variable's value to $null // after leaving the stmt. // - // The psuedo-code: + // The pseudo-code: // // try { // oldValue = (localSet.Get(automaticVar)) ? locals.ItemNNN : null; @@ -5154,7 +5191,7 @@ public object VisitCatchClause(CatchClauseAst catchClauseAst) // } // // This is a little convoluted because an automatic variable isn't necessarily set. - private class AutomaticVarSaver + private sealed class AutomaticVarSaver { private readonly Compiler _compiler; private readonly int _automaticVar; @@ -5581,7 +5618,9 @@ public object VisitReturnStatement(ReturnStatementAst returnStatementAst) return Expression.Block(returnValue, returnExpr); } - return returnExpr; + return Expression.Block( + UpdatePosition(returnStatementAst), + returnExpr); } public object VisitExitStatement(ExitStatementAst exitStatementAst) @@ -6097,9 +6136,7 @@ public object VisitConvertExpression(ConvertExpressionAst convertExpressionAst) { // We'll wrap the variable in a PSReference, but not the constant variables ($true, $false, $null) because those // can't be changed. - IEnumerable unused1; - bool unused2; - var varType = varExpr.GetVariableType(this, out unused1, out unused2); + var varType = varExpr.GetVariableType(this, out _, out _); return Expression.Call( CachedReflectionInfo.VariableOps_GetVariableAsRef, Expression.Constant(varExpr.VariablePath), @@ -6110,10 +6147,7 @@ public object VisitConvertExpression(ConvertExpressionAst convertExpressionAst) } } - if (childExpr == null) - { - childExpr = Compile(convertExpressionAst.Child); - } + childExpr ??= Compile(convertExpressionAst.Child); if (typeName.FullName.Equals("PSCustomObject", StringComparison.OrdinalIgnoreCase)) { @@ -6310,17 +6344,48 @@ public object VisitMemberExpression(MemberExpressionAst memberExpressionAst) internal static PSMethodInvocationConstraints GetInvokeMemberConstraints(InvokeMemberExpressionAst invokeMemberExpressionAst) { - var arguments = invokeMemberExpressionAst.Arguments; + ReadOnlyCollection arguments = invokeMemberExpressionAst.Arguments; + Type[] argumentTypes = null; + if (arguments is not null) + { + argumentTypes = new Type[arguments.Count]; + for (var i = 0; i < arguments.Count; i++) + { + argumentTypes[i] = GetTypeConstraintForMethodResolution(arguments[i]); + } + } + var targetTypeConstraint = GetTypeConstraintForMethodResolution(invokeMemberExpressionAst.Expression); - return CombineTypeConstraintForMethodResolution( - targetTypeConstraint, - arguments?.Select(Compiler.GetTypeConstraintForMethodResolution).ToArray()); + + ReadOnlyCollection genericArguments = invokeMemberExpressionAst.GenericTypeArguments; + object[] genericTypeArguments = null; + if (genericArguments is not null) + { + genericTypeArguments = new object[genericArguments.Count]; + for (var i = 0; i < genericArguments.Count; i++) + { + Type type = genericArguments[i].GetReflectionType(); + genericTypeArguments[i] = (object)type ?? genericArguments[i]; + } + } + + return CombineTypeConstraintForMethodResolution(targetTypeConstraint, argumentTypes, genericTypeArguments); } internal static PSMethodInvocationConstraints GetInvokeMemberConstraints(BaseCtorInvokeMemberExpressionAst invokeMemberExpressionAst) { Type targetTypeConstraint = null; - var arguments = invokeMemberExpressionAst.Arguments; + ReadOnlyCollection arguments = invokeMemberExpressionAst.Arguments; + Type[] argumentTypes = null; + if (arguments is not null) + { + argumentTypes = new Type[arguments.Count]; + for (var i = 0; i < arguments.Count; i++) + { + argumentTypes[i] = GetTypeConstraintForMethodResolution(arguments[i]); + } + } + TypeDefinitionAst typeDefinitionAst = Ast.GetAncestorTypeDefinitionAst(invokeMemberExpressionAst); if (typeDefinitionAst != null) { @@ -6331,9 +6396,7 @@ internal static PSMethodInvocationConstraints GetInvokeMemberConstraints(BaseCto Diagnostics.Assert(false, "BaseCtorInvokeMemberExpressionAst must be used only inside TypeDefinitionAst"); } - return CombineTypeConstraintForMethodResolution( - targetTypeConstraint, - arguments?.Select(Compiler.GetTypeConstraintForMethodResolution).ToArray()); + return CombineTypeConstraintForMethodResolution(targetTypeConstraint, argumentTypes, genericArguments: null); } internal Expression InvokeMember( @@ -6705,8 +6768,8 @@ public Expression GetValue(Compiler compiler, List exprs, List Expression.Variable(arg.Type)).ToArray(); - exprs.AddRange(args.Zip(_argExprTemps, (arg, temp) => Expression.Assign(temp, arg))); + _argExprTemps = args.Select(static arg => Expression.Variable(arg.Type)).ToArray(); + exprs.AddRange(args.Zip(_argExprTemps, static (arg, temp) => Expression.Assign(temp, arg))); temps.Add(_targetExprTemp); int tempsIndex = temps.Count; @@ -6918,10 +6981,7 @@ public void AddInstructions(LightCompiler compiler) compiler.PopLabelBlock(LabelScopeKind.Statement); // If enterLoop is null, we will never JIT compile the loop. - if (enterLoop != null) - { - enterLoop.FinishLoop(compiler.Instructions.Count); - } + enterLoop?.FinishLoop(compiler.Instructions.Count); } } diff --git a/src/System.Management.Automation/engine/parser/ConstantValues.cs b/src/System.Management.Automation/engine/parser/ConstantValues.cs index a8ada5f96d5..d580148ddd2 100644 --- a/src/System.Management.Automation/engine/parser/ConstantValues.cs +++ b/src/System.Management.Automation/engine/parser/ConstantValues.cs @@ -148,8 +148,16 @@ public static bool IsConstant(Ast ast, out object constantValue, bool forAttribu public object VisitStatementBlock(StatementBlockAst statementBlockAst) { - if (statementBlockAst.Traps != null) return false; - if (statementBlockAst.Statements.Count > 1) return false; + if (statementBlockAst.Traps != null) + { + return false; + } + + if (statementBlockAst.Statements.Count > 1) + { + return false; + } + var pipeline = statementBlockAst.Statements.FirstOrDefault(); return pipeline != null && (bool)pipeline.Accept(this); } diff --git a/src/System.Management.Automation/engine/parser/DebugViewWriter.cs b/src/System.Management.Automation/engine/parser/DebugViewWriter.cs index 69e8aae634b..cb280bdaf61 100644 --- a/src/System.Management.Automation/engine/parser/DebugViewWriter.cs +++ b/src/System.Management.Automation/engine/parser/DebugViewWriter.cs @@ -985,7 +985,7 @@ protected override Expression VisitBlock(BlockExpression node) { // Display if the type of the BlockExpression is different from the // last expression's type in the block. if (node.Type != node.Expressions[node.Expressions.Count - 1].Type) { - Out(string.Format(CultureInfo.CurrentCulture, "<{0}>", node.Type.ToString())); + Out(string.Create(CultureInfo.CurrentCulture, $"<{node.Type}>")); } VisitDeclarations(node.Variables); @@ -1124,7 +1124,7 @@ protected override Expression VisitIndex(IndexExpression node) { } protected override Expression VisitExtension(Expression node) { - Out(string.Format(CultureInfo.CurrentCulture, ".Extension<{0}>", node.GetType().ToString())); + Out(string.Create(CultureInfo.CurrentCulture, $".Extension<{node.GetType()}>")); if (node.CanReduce) { Out(Flow.Space, "{", Flow.NewLine); @@ -1151,13 +1151,13 @@ protected override Expression VisitDebugInfo(DebugInfoExpression node) { } private void DumpLabel(LabelTarget target) { - Out(string.Format(CultureInfo.CurrentCulture, ".LabelTarget {0}:", GetLabelTargetName(target))); + Out(string.Create(CultureInfo.CurrentCulture, $".LabelTarget {GetLabelTargetName(target)}:")); } private string GetLabelTargetName(LabelTarget target) { if (string.IsNullOrEmpty(target.Name)) { // Create the label target name as #Label1, #Label2, etc. - return string.Format(CultureInfo.CurrentCulture, "#Label{0}", GetLabelTargetId(target)); + return string.Create(CultureInfo.CurrentCulture, $"#Label{GetLabelTargetId(target)}"); } else { return GetDisplayName(target.Name); } @@ -1165,11 +1165,7 @@ private string GetLabelTargetName(LabelTarget target) { private void WriteLambda(LambdaExpression lambda) { Out( - string.Format( - CultureInfo.CurrentCulture, - ".Lambda {0}<{1}>", - GetLambdaName(lambda), - lambda.Type.ToString()) + string.Create(CultureInfo.CurrentCulture, $".Lambda {GetLambdaName(lambda)}<{lambda.Type}>") ); VisitDeclarations(lambda.Parameters); @@ -1205,7 +1201,7 @@ private static bool ContainsWhiteSpace(string name) { } private static string QuoteName(string name) { - return string.Format(CultureInfo.CurrentCulture, "'{0}'", name); + return string.Create(CultureInfo.CurrentCulture, $"'{name}'"); } private static string GetDisplayName(string name) { diff --git a/src/System.Management.Automation/engine/parser/FusionAssemblyIdentity.cs b/src/System.Management.Automation/engine/parser/FusionAssemblyIdentity.cs deleted file mode 100644 index 79df05d09f4..00000000000 --- a/src/System.Management.Automation/engine/parser/FusionAssemblyIdentity.cs +++ /dev/null @@ -1,316 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -// Code in this file was copied from https://github.com/dotnet/roslyn - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Reflection; -using System.Runtime.InteropServices; - -#if !CORECLR -namespace Microsoft.CodeAnalysis -{ - internal sealed class FusionAssemblyIdentity - { - [Flags] - internal enum ASM_DISPLAYF - { - VERSION = 0x01, - CULTURE = 0x02, - PUBLIC_KEY_TOKEN = 0x04, - PUBLIC_KEY = 0x08, - CUSTOM = 0x10, - PROCESSORARCHITECTURE = 0x20, - LANGUAGEID = 0x40, - RETARGET = 0x80, - CONFIG_MASK = 0x100, - MVID = 0x200, - CONTENT_TYPE = 0x400, - FULL = VERSION | CULTURE | PUBLIC_KEY_TOKEN | RETARGET | PROCESSORARCHITECTURE | CONTENT_TYPE - } - - internal enum PropertyId - { - PUBLIC_KEY = 0, // 0 - PUBLIC_KEY_TOKEN, // 1 - HASH_VALUE, // 2 - NAME, // 3 - MAJOR_VERSION, // 4 - MINOR_VERSION, // 5 - BUILD_NUMBER, // 6 - REVISION_NUMBER, // 7 - CULTURE, // 8 - PROCESSOR_ID_ARRAY, // 9 - OSINFO_ARRAY, // 10 - HASH_ALGID, // 11 - ALIAS, // 12 - CODEBASE_URL, // 13 - CODEBASE_LASTMOD, // 14 - NULL_PUBLIC_KEY, // 15 - NULL_PUBLIC_KEY_TOKEN, // 16 - CUSTOM, // 17 - NULL_CUSTOM, // 18 - MVID, // 19 - FILE_MAJOR_VERSION, // 20 - FILE_MINOR_VERSION, // 21 - FILE_BUILD_NUMBER, // 22 - FILE_REVISION_NUMBER, // 23 - RETARGET, // 24 - SIGNATURE_BLOB, // 25 - CONFIG_MASK, // 26 - ARCHITECTURE, // 27 - CONTENT_TYPE, // 28 - MAX_PARAMS // 29 - } - - private static class CANOF - { - public const uint PARSE_DISPLAY_NAME = 0x1; - public const uint SET_DEFAULT_VALUES = 0x2; - } - - [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("CD193BC0-B4BC-11d2-9833-00C04FC31D2E")] - internal unsafe interface IAssemblyName - { - void SetProperty(PropertyId id, void* data, uint size); - - [PreserveSig] - int GetProperty(PropertyId id, void* data, ref uint size); - - [PreserveSig] - int Finalize(); - - [PreserveSig] - int GetDisplayName(byte* buffer, ref uint characterCount, ASM_DISPLAYF dwDisplayFlags); - - [PreserveSig] - int __BindToObject(/*...*/); - - [PreserveSig] - int __GetName(/*...*/); - - [PreserveSig] - int GetVersion(out uint versionHi, out uint versionLow); - - [PreserveSig] - int IsEqual(IAssemblyName pName, uint dwCmpFlags); - - [PreserveSig] - int Clone(out IAssemblyName pName); - } - - [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("7c23ff90-33af-11d3-95da-00a024a85b51")] - internal interface IApplicationContext - { - } - - // NOTE: The CLR caches assembly identities, but doesn't do so in a threadsafe manner. - // Wrap all calls to this with a lock. - private static object s_assemblyIdentityGate = new object(); - private static int CreateAssemblyNameObject(out IAssemblyName ppEnum, string szAssemblyName, uint dwFlags, IntPtr pvReserved) - { - lock (s_assemblyIdentityGate) - { - return RealCreateAssemblyNameObject(out ppEnum, szAssemblyName, dwFlags, pvReserved); - } - } - - [DllImport("clr", EntryPoint = "CreateAssemblyNameObject", CharSet = CharSet.Unicode, PreserveSig = true)] - private static extern int RealCreateAssemblyNameObject(out IAssemblyName ppEnum, [MarshalAs(UnmanagedType.LPWStr)]string szAssemblyName, uint dwFlags, IntPtr pvReserved); - - private const int ERROR_INSUFFICIENT_BUFFER = unchecked((int)0x8007007A); - private const int FUSION_E_INVALID_NAME = unchecked((int)0x80131047); - - internal static unsafe string GetDisplayName(IAssemblyName nameObject, ASM_DISPLAYF displayFlags) - { - int hr; - uint characterCountIncludingTerminator = 0; - - hr = nameObject.GetDisplayName(null, ref characterCountIncludingTerminator, displayFlags); - if (hr == 0) - { - return string.Empty; - } - - if (hr != ERROR_INSUFFICIENT_BUFFER) - { - Marshal.ThrowExceptionForHR(hr); - } - - byte[] data = new byte[(int)characterCountIncludingTerminator * 2]; - fixed (byte* p = data) - { - hr = nameObject.GetDisplayName(p, ref characterCountIncludingTerminator, displayFlags); - Marshal.ThrowExceptionForHR(hr); - - return Marshal.PtrToStringUni((IntPtr)p, (int)characterCountIncludingTerminator - 1); - } - } - - internal static unsafe byte[] GetPropertyBytes(IAssemblyName nameObject, PropertyId propertyId) - { - int hr; - uint size = 0; - - hr = nameObject.GetProperty(propertyId, null, ref size); - if (hr == 0) - { - return null; - } - - if (hr != ERROR_INSUFFICIENT_BUFFER) - { - Marshal.ThrowExceptionForHR(hr); - } - - byte[] data = new byte[(int)size]; - fixed (byte* p = data) - { - hr = nameObject.GetProperty(propertyId, p, ref size); - Marshal.ThrowExceptionForHR(hr); - } - - return data; - } - - internal static unsafe string GetPropertyString(IAssemblyName nameObject, PropertyId propertyId) - { - byte[] data = GetPropertyBytes(nameObject, propertyId); - if (data == null) - { - return null; - } - - fixed (byte* p = data) - { - return Marshal.PtrToStringUni((IntPtr)p, (data.Length / 2) - 1); - } - } - - internal static unsafe Version GetVersion(IAssemblyName nameObject) - { - uint hi, lo; - int hr = nameObject.GetVersion(out hi, out lo); - if (hr != 0) - { - Debug.Assert(hr == FUSION_E_INVALID_NAME); - return null; - } - - return new Version((int)(hi >> 16), (int)(hi & 0xffff), (int)(lo >> 16), (int)(lo & 0xffff)); - } - - internal static unsafe uint? GetPropertyWord(IAssemblyName nameObject, PropertyId propertyId) - { - uint result; - uint size = sizeof(uint); - int hr = nameObject.GetProperty(propertyId, &result, ref size); - Marshal.ThrowExceptionForHR(hr); - - if (size == 0) - { - return null; - } - - return result; - } - - internal static string GetCulture(IAssemblyName nameObject) - { - return GetPropertyString(nameObject, PropertyId.CULTURE); - } - - internal static ProcessorArchitecture GetProcessorArchitecture(IAssemblyName nameObject) - { - return (ProcessorArchitecture)(GetPropertyWord(nameObject, PropertyId.ARCHITECTURE) ?? 0); - } - - /// - /// Creates object by parsing given display name. - /// - internal static IAssemblyName ToAssemblyNameObject(string displayName) - { - // CLR doesn't handle \0 in the display name well: - if (displayName.IndexOf('\0') >= 0) - { - return null; - } - - Debug.Assert(displayName != null); - IAssemblyName result; - int hr = CreateAssemblyNameObject(out result, displayName, CANOF.PARSE_DISPLAY_NAME, IntPtr.Zero); - if (hr != 0) - { - return null; - } - - Debug.Assert(result != null); - return result; - } - - /// - /// Selects the candidate assembly with the largest version number. Uses culture as a tie-breaker if it is provided. - /// All candidates are assumed to have the same name and must include versions and cultures. - /// - internal static IAssemblyName GetBestMatch(IEnumerable candidates, string preferredCultureOpt) - { - IAssemblyName bestCandidate = null; - Version bestVersion = null; - string bestCulture = null; - foreach (var candidate in candidates) - { - if (bestCandidate != null) - { - Version candidateVersion = GetVersion(candidate); - Debug.Assert(candidateVersion != null); - - if (bestVersion == null) - { - bestVersion = GetVersion(bestCandidate); - Debug.Assert(bestVersion != null); - } - - int cmp = bestVersion.CompareTo(candidateVersion); - if (cmp == 0) - { - if (preferredCultureOpt != null) - { - string candidateCulture = GetCulture(candidate); - Debug.Assert(candidateCulture != null); - - if (bestCulture == null) - { - bestCulture = GetCulture(candidate); - Debug.Assert(bestCulture != null); - } - - // we have exactly the preferred culture or - // we have neutral culture and the best candidate's culture isn't the preferred one: - if (StringComparer.OrdinalIgnoreCase.Equals(candidateCulture, preferredCultureOpt) || - candidateCulture.Length == 0 && !StringComparer.OrdinalIgnoreCase.Equals(bestCulture, preferredCultureOpt)) - { - bestCandidate = candidate; - bestVersion = candidateVersion; - bestCulture = candidateCulture; - } - } - } - else if (cmp < 0) - { - bestCandidate = candidate; - bestVersion = candidateVersion; - } - } - else - { - bestCandidate = candidate; - } - } - - return bestCandidate; - } - } -} -#endif // !CORECLR diff --git a/src/System.Management.Automation/engine/parser/GlobalAssemblyCache.cs b/src/System.Management.Automation/engine/parser/GlobalAssemblyCache.cs deleted file mode 100644 index 3761c55d3dc..00000000000 --- a/src/System.Management.Automation/engine/parser/GlobalAssemblyCache.cs +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -// Code in this file was copied from https://github.com/dotnet/roslyn - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; - -#if !CORECLR // Only enable/port what is needed by CORE CLR. -namespace Microsoft.CodeAnalysis -{ - /// - /// Provides APIs to enumerate and look up assemblies stored in the Global Assembly Cache. - /// - internal static class GlobalAssemblyCache - { - /// - /// Represents the current Processor architecture. - /// - public static readonly ProcessorArchitecture[] CurrentArchitectures = (IntPtr.Size == 4) - ? new[] { ProcessorArchitecture.None, ProcessorArchitecture.MSIL, ProcessorArchitecture.X86 } - - : new[] { ProcessorArchitecture.None, ProcessorArchitecture.MSIL, ProcessorArchitecture.Amd64 }; - -#region Interop - - private const int MAX_PATH = 260; - - [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("21b8916c-f28e-11d2-a473-00c04f8ef448")] - private interface IAssemblyEnum - { - [PreserveSig] - int GetNextAssembly(out FusionAssemblyIdentity.IApplicationContext ppAppCtx, out FusionAssemblyIdentity.IAssemblyName ppName, uint dwFlags); - - [PreserveSig] - int Reset(); - - [PreserveSig] - int Clone(out IAssemblyEnum ppEnum); - } - - [ComImport, Guid("e707dcde-d1cd-11d2-bab9-00c04f8eceae"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - private interface IAssemblyCache - { - void UninstallAssembly(); - - void QueryAssemblyInfo(uint dwFlags, [MarshalAs(UnmanagedType.LPWStr)] string pszAssemblyName, ref ASSEMBLY_INFO pAsmInfo); - - void CreateAssemblyCacheItem(); - void CreateAssemblyScavenger(); - void InstallAssembly(); - } - - [StructLayout(LayoutKind.Sequential)] - private unsafe struct ASSEMBLY_INFO - { - public uint cbAssemblyInfo; - public uint dwAssemblyFlags; - public ulong uliAssemblySizeInKB; - public char* pszCurrentAssemblyPathBuf; - public uint cchBuf; - } - - private enum ASM_CACHE - { - ZAP = 0x1, - GAC = 0x2, // C:\Windows\Assembly\GAC - DOWNLOAD = 0x4, - ROOT = 0x8, // C:\Windows\Assembly - GAC_MSIL = 0x10, - GAC_32 = 0x20, // C:\Windows\Assembly\GAC_32 - GAC_64 = 0x40, // C:\Windows\Assembly\GAC_64 - ROOT_EX = 0x80, // C:\Windows\Microsoft.NET\assembly - } - - [DllImport("clr", CharSet = CharSet.Auto, PreserveSig = true)] - private static extern int CreateAssemblyEnum(out IAssemblyEnum ppEnum, FusionAssemblyIdentity.IApplicationContext pAppCtx, FusionAssemblyIdentity.IAssemblyName pName, ASM_CACHE dwFlags, IntPtr pvReserved); - - [DllImport("clr", CharSet = CharSet.Auto, PreserveSig = false)] - private static extern void CreateAssemblyCache(out IAssemblyCache ppAsmCache, uint dwReserved); - -#endregion - - private const int S_OK = 0; - private const int S_FALSE = 1; - - // Internal for testing. - internal static IEnumerable GetAssemblyObjects( - FusionAssemblyIdentity.IAssemblyName partialNameFilter, - ProcessorArchitecture[] architectureFilter) - { - IAssemblyEnum enumerator; - - int hr = CreateAssemblyEnum(out enumerator, null, partialNameFilter, ASM_CACHE.GAC, IntPtr.Zero); - if (hr == S_FALSE) - { - // no assembly found - yield break; - } - - if (hr != S_OK) - { - Exception e = Marshal.GetExceptionForHR(hr); - if (e is FileNotFoundException) - { - // invalid assembly name: - yield break; - } - - if (e != null) - { - throw e; - } - // for some reason it might happen that CreateAssemblyEnum returns non-zero HR that doesn't correspond to any exception: - throw new ArgumentException("Invalid assembly name"); - } - - while (true) - { - FusionAssemblyIdentity.IAssemblyName nameObject; - - FusionAssemblyIdentity.IApplicationContext applicationContext; - hr = enumerator.GetNextAssembly(out applicationContext, out nameObject, 0); - if (hr != 0) - { - if (hr < 0) - { - Marshal.ThrowExceptionForHR(hr); - } - - break; - } - - if (architectureFilter != null) - { - var assemblyArchitecture = FusionAssemblyIdentity.GetProcessorArchitecture(nameObject); - if (!architectureFilter.Contains(assemblyArchitecture)) - { - continue; - } - } - - yield return nameObject; - } - } - - /// - /// Looks up specified partial assembly name in the GAC and returns the best matching full assembly name/>. - /// - /// The display name of an assembly. - /// Full path name of the resolved assembly. - /// The optional processor architecture. - /// The optional preferred culture information. - /// An assembly identity or null, if can't be resolved. - /// is null. - public static unsafe string ResolvePartialName( - string displayName, - out string location, - ProcessorArchitecture[] architectureFilter = null, - CultureInfo preferredCulture = null) - { - if (displayName == null) - { - throw new ArgumentNullException("displayName"); - } - - location = null; - FusionAssemblyIdentity.IAssemblyName nameObject = FusionAssemblyIdentity.ToAssemblyNameObject(displayName); - if (nameObject == null) - { - return null; - } - - var candidates = GetAssemblyObjects(nameObject, architectureFilter); - string cultureName = (preferredCulture != null && !preferredCulture.IsNeutralCulture) ? preferredCulture.Name : null; - - var bestMatch = FusionAssemblyIdentity.GetBestMatch(candidates, cultureName); - if (bestMatch == null) - { - return null; - } - - string fullName = FusionAssemblyIdentity.GetDisplayName(bestMatch, FusionAssemblyIdentity.ASM_DISPLAYF.FULL); - - fixed (char* p = new char[MAX_PATH]) - { - ASSEMBLY_INFO info = new ASSEMBLY_INFO - { - cbAssemblyInfo = (uint)Marshal.SizeOf(typeof(ASSEMBLY_INFO)), - pszCurrentAssemblyPathBuf = p, - cchBuf = (uint)MAX_PATH - }; - - IAssemblyCache assemblyCacheObject; - CreateAssemblyCache(out assemblyCacheObject, 0); - assemblyCacheObject.QueryAssemblyInfo(0, fullName, ref info); - Debug.Assert(info.pszCurrentAssemblyPathBuf != null); - Debug.Assert(info.pszCurrentAssemblyPathBuf[info.cchBuf - 1] == '\0'); - - var result = Marshal.PtrToStringUni((IntPtr)info.pszCurrentAssemblyPathBuf, (int)info.cchBuf - 1); - Debug.Assert(result.IndexOf('\0') == -1); - location = result; - } - - return fullName; - } - } -} -#endif // !CORECLR diff --git a/src/System.Management.Automation/engine/parser/PSType.cs b/src/System.Management.Automation/engine/parser/PSType.cs index 7a60d06a6be..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; @@ -265,7 +266,7 @@ internal static void DefineCustomAttributes(EnumBuilder member, ReadOnlyCollecti } } - private class DefineTypeHelper + private sealed class DefineTypeHelper { private readonly Parser _parser; internal readonly TypeDefinitionAst _typeDefinitionAst; @@ -276,7 +277,7 @@ private class DefineTypeHelper internal readonly TypeBuilder _staticHelpersTypeBuilder; private readonly Dictionary _definedProperties; private readonly Dictionary>> _definedMethods; - private HashSet> _interfaceProperties; + private Dictionary, PropertyInfo> _abstractProperties; internal readonly List<(string fieldName, IParameterMetadataProvider bodyAst, bool isStatic)> _fieldsToInitForMemberFunctions; private bool _baseClassHasDefaultCtor; @@ -296,7 +297,7 @@ public DefineTypeHelper(Parser parser, ModuleBuilder module, TypeDefinitionAst t var baseClass = this.GetBaseTypes(parser, typeDefinitionAst, out interfaces); _typeBuilder = module.DefineType(typeName, Reflection.TypeAttributes.Class | Reflection.TypeAttributes.Public, baseClass, interfaces.ToArray()); - _staticHelpersTypeBuilder = module.DefineType(string.Format(CultureInfo.InvariantCulture, "{0}_", typeName), Reflection.TypeAttributes.Class); + _staticHelpersTypeBuilder = module.DefineType(string.Create(CultureInfo.InvariantCulture, $"{typeName}_"), Reflection.TypeAttributes.Class); DefineCustomAttributes(_typeBuilder, typeDefinitionAst.Attributes, _parser, AttributeTargets.Class); _typeDefinitionAst.Type = _typeBuilder; @@ -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 (_interfaceProperties == null) + if (_abstractProperties == null) { - _interfaceProperties = new HashSet>(); + _abstractProperties = new Dictionary, PropertyInfo>(); var allInterfaces = new HashSet(); // TypeBuilder.GetInterfaces() returns only the interfaces that was explicitly passed to its constructor. @@ -467,12 +468,23 @@ private bool ShouldImplementProperty(string name, Type type) { foreach (var property in interfaceType.GetProperties()) { - _interfaceProperties.Add(Tuple.Create(property.Name, property.PropertyType)); + _abstractProperties.Add(Tuple.Create(property.Name, property.PropertyType), property); + } + } + + if (_typeBuilder.BaseType.IsAbstract) + { + foreach (var property in _typeBuilder.BaseType.GetProperties()) + { + if (property.GetAccessors().Any(m => m.IsAbstract)) + { + _abstractProperties.Add(Tuple.Create(property.Name, property.PropertyType), property); + } } } } - return _interfaceProperties.Contains(Tuple.Create(name, type)); + return _abstractProperties.TryGetValue(Tuple.Create(name, type), out interfaceProperty); } public void DefineMembers() @@ -618,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) @@ -629,7 +651,7 @@ private PropertyBuilder EmitPropertyIl(PropertyMemberAst propertyMemberAst, Type getSetAttributes |= Reflection.MethodAttributes.Static; } // C# naming convention for backing fields. - string backingFieldName = string.Format(CultureInfo.InvariantCulture, "<{0}>k__BackingField", propertyMemberAst.Name); + string backingFieldName = string.Create(CultureInfo.InvariantCulture, $"<{propertyMemberAst.Name}>k__BackingField"); var backingField = _typeBuilder.DefineField(backingFieldName, type, backingFieldAttributes); bool hasValidateAttributes = false; @@ -666,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(); @@ -699,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); @@ -937,7 +969,7 @@ private void DefineMethodBody( Type returnType, Action parameterNameSetter) { - var wrapperFieldName = string.Format(CultureInfo.InvariantCulture, "<{0}>", metadataToken); + var wrapperFieldName = string.Create(CultureInfo.InvariantCulture, $"<{metadataToken}>"); var scriptBlockWrapperField = _staticHelpersTypeBuilder.DefineField(wrapperFieldName, typeof(ScriptBlockMemberMethodWrapper), FieldAttributes.Assembly | FieldAttributes.Static); @@ -1007,7 +1039,7 @@ private void DefineMethodBody( } } - private class DefineEnumHelper + private sealed class DefineEnumHelper { private readonly Parser _parser; private readonly TypeDefinitionAst _enumDefinitionAst; @@ -1082,7 +1114,7 @@ internal static List Sort(List defineEnumHel } // The expression may have multiple member expressions, e.g. [E]::e1 + [E]::e2 - foreach (var memberExpr in initExpr.FindAll(ast => ast is MemberExpressionAst, false)) + foreach (var memberExpr in initExpr.FindAll(static ast => ast is MemberExpressionAst, false)) { var typeExpr = ((MemberExpressionAst)memberExpr).Expression as TypeExpressionAst; if (typeExpr != null) @@ -1321,9 +1353,8 @@ internal static Assembly DefineTypes(Parser parser, Ast rootAst, TypeDefinitionA foreach (var typeDefinitionAst in typeDefinitions) { var typeName = GetClassNameInAssembly(typeDefinitionAst); - if (!definedTypes.Contains(typeName)) + if (definedTypes.Add(typeName)) { - definedTypes.Add(typeName); if ((typeDefinitionAst.TypeAttributes & TypeAttributes.Class) == TypeAttributes.Class) { defineTypeHelpers.Add(new DefineTypeHelper(parser, module, typeDefinitionAst, typeName)); @@ -1434,7 +1465,7 @@ private static string GetClassNameInAssembly(TypeDefinitionAst typeDefinitionAst nameParts.Reverse(); nameParts.Add(typeDefinitionAst.Name); - return string.Join(".", nameParts); + return string.Join('.', nameParts); } private static readonly OpCode[] s_ldc = @@ -1472,4 +1503,18 @@ private static void EmitLdarg(ILGenerator emitter, int c) } } } + + /// + /// The attribute for a PowerShell class to not affiliate with a particular Runspace\SessionState. + /// + [AttributeUsage(AttributeTargets.Class)] + public sealed class NoRunspaceAffinityAttribute : ParsingBaseAttribute + { + /// + /// Initializes a new instance of the attribute. + /// + public NoRunspaceAffinityAttribute() + { + } + } } diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs index 5b8ca433454..1592d2e7e7d 100644 --- a/src/System.Management.Automation/engine/parser/Parser.cs +++ b/src/System.Management.Automation/engine/parser/Parser.cs @@ -9,6 +9,9 @@ using System.IO; using System.Linq; using System.Management.Automation.Runspaces; +using System.Management.Automation.Security; +using System.Management.Automation.Subsystem; +using System.Management.Automation.Subsystem.DSC; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -140,6 +143,8 @@ public static ScriptBlockAst ParseInput(string input, out Token[] tokens, out Pa /// The that represents the input script file. public static ScriptBlockAst ParseInput(string input, string fileName, out Token[] tokens, out ParseError[] errors) { + ArgumentNullException.ThrowIfNull(input); + Parser parser = new Parser(); List tokenList = new List(); ScriptBlockAst result; @@ -273,11 +278,10 @@ internal static ITypeName ScanType(string typename, bool ignoreErrors) var parser = new Parser(); var tokenizer = parser._tokenizer; tokenizer.Initialize(null, typename, null); - Token unused; - var result = parser.TypeNameRule(allowAssemblyQualifiedNames: true, firstTypeNameToken: out unused); + var result = parser.TypeNameRule(allowAssemblyQualifiedNames: true, firstTypeNameToken: out _); SemanticChecks.CheckArrayTypeNameDepth(result, PositionUtilities.EmptyExtent, parser); - if (!ignoreErrors && parser.ErrorList.Count > 0) + if (!ignoreErrors && result is not null && (parser.ErrorList.Count > 0 || !result.Extent.Text.Equals(typename, StringComparison.OrdinalIgnoreCase))) { result = null; } @@ -437,8 +441,7 @@ private Token NextToken() private Token PeekToken() { Token token = _ungotToken ?? _tokenizer.NextToken(); - if (_ungotToken == null) - _ungotToken = token; + _ungotToken ??= token; return token; } @@ -717,12 +720,13 @@ internal static bool TryParseAsConstantHashtable(string input, out Hashtable res ParseError[] parseErrors; var ast = Parser.ParseInput(input, out throwAwayTokens, out parseErrors); - if ((ast == null) || - parseErrors.Length > 0 || - ast.BeginBlock != null || - ast.ProcessBlock != null || - ast.DynamicParamBlock != null || - ast.EndBlock.Traps != null) + if (ast == null + || parseErrors.Length > 0 + || ast.BeginBlock != null + || ast.ProcessBlock != null + || ast.CleanBlock != null + || ast.DynamicParamBlock != null + || ast.EndBlock.Traps != null) { return false; } @@ -816,10 +820,7 @@ private List UsingStatementsRule() SkipToken(); var statement = UsingStatementRule(token); SkipNewlinesAndSemicolons(); - if (result == null) - { - result = new List(); - } + result ??= new List(); var usingStatement = statement as UsingStatementAst; // otherwise returned statement is ErrorStatementAst. @@ -1346,11 +1347,11 @@ private ITypeName FinishTypeNameRule(Token typeName, bool unBracketedGenericArg case TokenKind.RBracket: case TokenKind.Comma: var elementType = new TypeName(typeName.Extent, typeName.Text); - return CompleteArrayTypeName(elementType, elementType, token); + return CompleteArrayTypeName(elementType, elementType, token, unBracketedGenericArg); case TokenKind.LBracket: case TokenKind.Identifier: - return GenericTypeArgumentsRule(typeName, token, unBracketedGenericArg); + return GenericTypeNameRule(typeName, token, unBracketedGenericArg); default: // ErrorRecovery: sync to ']', and return non-null to avoid cascading errors. @@ -1430,7 +1431,7 @@ private ITypeName GetSingleGenericArgument(Token firstToken) return typeName; } - private ITypeName GenericTypeArgumentsRule(Token genericTypeName, Token firstToken, bool unBracketedGenericArg) + private List GenericTypeArgumentsRule(Token firstToken, out Token lastToken) { Diagnostics.Assert(firstToken.Kind == TokenKind.Identifier || firstToken.Kind == TokenKind.LBracket, "unexpected first token"); RuntimeHelpers.EnsureSufficientExecutionStack(); @@ -1439,20 +1440,18 @@ private ITypeName GenericTypeArgumentsRule(Token genericTypeName, Token firstTok ITypeName typeName = GetSingleGenericArgument(firstToken); genericArguments.Add(typeName); - Token commaOrRBracketToken; - Token token; while (true) { SkipNewlines(); - commaOrRBracketToken = NextToken(); - if (commaOrRBracketToken.Kind != TokenKind.Comma) + lastToken = NextToken(); + if (lastToken.Kind != TokenKind.Comma) { break; } SkipNewlines(); - token = PeekToken(); + Token token = PeekToken(); if (token.Kind == TokenKind.Identifier || token.Kind == TokenKind.LBracket) { SkipToken(); @@ -1460,43 +1459,55 @@ private ITypeName GenericTypeArgumentsRule(Token genericTypeName, Token firstTok } else { - ReportIncompleteInput(After(commaOrRBracketToken), + ReportIncompleteInput( + After(lastToken), nameof(ParserStrings.MissingTypename), ParserStrings.MissingTypename); - typeName = new TypeName(commaOrRBracketToken.Extent, ":ErrorTypeName:"); + typeName = new TypeName(lastToken.Extent, ":ErrorTypeName:"); } genericArguments.Add(typeName); } - if (commaOrRBracketToken.Kind != TokenKind.RBracket) + return genericArguments; + } + + private ITypeName GenericTypeNameRule(Token genericTypeName, Token firstToken, bool unbracketedGenericArg) + { + List genericArguments = GenericTypeArgumentsRule(firstToken, out Token rBracketToken); + + if (rBracketToken.Kind != TokenKind.RBracket) { // ErrorRecovery: pretend we had the closing bracket and just continue on. - - UngetToken(commaOrRBracketToken); - ReportIncompleteInput(Before(commaOrRBracketToken), + UngetToken(rBracketToken); + ReportIncompleteInput( + Before(rBracketToken), nameof(ParserStrings.EndSquareBracketExpectedAtEndOfAttribute), ParserStrings.EndSquareBracketExpectedAtEndOfAttribute); - commaOrRBracketToken = null; + rBracketToken = null; } - var openGenericType = new TypeName(genericTypeName.Extent, genericTypeName.Text); - var result = new GenericTypeName(ExtentOf(genericTypeName.Extent, ExtentFromFirstOf(commaOrRBracketToken, genericArguments.LastOrDefault(), firstToken)), - openGenericType, genericArguments); - token = PeekToken(); + var openGenericType = new TypeName(genericTypeName.Extent, genericTypeName.Text, genericArguments.Count); + var result = new GenericTypeName( + ExtentOf(genericTypeName.Extent, ExtentFromFirstOf(rBracketToken, genericArguments.LastOrDefault(), firstToken)), + openGenericType, + genericArguments); + + Token token = PeekToken(); if (token.Kind == TokenKind.LBracket) { SkipToken(); - return CompleteArrayTypeName(result, openGenericType, NextToken()); + return CompleteArrayTypeName(result, openGenericType, NextToken(), unbracketedGenericArg); } - if (token.Kind == TokenKind.Comma && !unBracketedGenericArg) + if (token.Kind == TokenKind.Comma && !unbracketedGenericArg) { SkipToken(); string assemblyNameSpec = _tokenizer.GetAssemblyNameSpec(); if (string.IsNullOrEmpty(assemblyNameSpec)) { - ReportError(After(token), + ReportError( + After(token), nameof(ParserStrings.MissingAssemblyNameSpecification), ParserStrings.MissingAssemblyNameSpecification); } @@ -1509,7 +1520,7 @@ private ITypeName GenericTypeArgumentsRule(Token genericTypeName, Token firstTok return result; } - private ITypeName CompleteArrayTypeName(ITypeName elementType, TypeName typeForAssemblyQualification, Token firstTokenAfterLBracket) + private ITypeName CompleteArrayTypeName(ITypeName elementType, TypeName typeForAssemblyQualification, Token firstTokenAfterLBracket, bool unBracketedGenericArg) { while (true) { @@ -1527,6 +1538,25 @@ private ITypeName CompleteArrayTypeName(ITypeName elementType, TypeName typeForA token = NextToken(); } while (token.Kind == TokenKind.Comma); + // The dimensions for an array must be less than or equal to 32. + // Search the doc for 'Type.MakeArrayType(int rank)' for more details. + if (dim > 32) + { + // If the next token is right bracket, we swallow it to make it easier to parse the rest of script. + // Otherwise, we unget the token for the subsequent parsing to consume. + if (token.Kind != TokenKind.RBracket) + { + UngetToken(token); + } + + ReportError( + ExtentOf(firstTokenAfterLBracket, lastComma), + nameof(ParserStrings.ArrayHasTooManyDimensions), + ParserStrings.ArrayHasTooManyDimensions, + arg: dim); + break; + } + if (token.Kind != TokenKind.RBracket) { // ErrorRecovery: just pretend we saw a ']'. @@ -1563,7 +1593,9 @@ private ITypeName CompleteArrayTypeName(ITypeName elementType, TypeName typeForA } token = PeekToken(); - if (token.Kind == TokenKind.Comma) + + // An array declared inside an unbracketed generic type argument cannot be assembly qualified + if (!unBracketedGenericArg && token.Kind == TokenKind.Comma) { SkipToken(); var assemblyName = _tokenizer.GetAssemblyNameSpec(); @@ -1706,9 +1738,9 @@ private ScriptBlockAst NamedBlockListRule(Token lCurly, List NamedBlockAst beginBlock = null; NamedBlockAst processBlock = null; NamedBlockAst endBlock = null; - IScriptExtent startExtent = lCurly != null - ? lCurly.Extent - : paramBlockAst?.Extent; + NamedBlockAst cleanBlock = null; + + IScriptExtent startExtent = lCurly?.Extent ?? paramBlockAst?.Extent; IScriptExtent endExtent = null; IScriptExtent extent = null; IScriptExtent scriptBlockExtent = null; @@ -1750,13 +1782,11 @@ private ScriptBlockAst NamedBlockListRule(Token lCurly, List case TokenKind.Begin: case TokenKind.Process: case TokenKind.End: + case TokenKind.Clean: break; } - if (startExtent == null) - { - startExtent = blockNameToken.Extent; - } + startExtent ??= blockNameToken.Extent; endExtent = blockNameToken.Extent; @@ -1790,6 +1820,10 @@ private ScriptBlockAst NamedBlockListRule(Token lCurly, List { endBlock = new NamedBlockAst(extent, TokenKind.End, statementBlock, false); } + else if (blockNameToken.Kind == TokenKind.Clean && cleanBlock == null) + { + cleanBlock = new NamedBlockAst(extent, TokenKind.Clean, statementBlock, false); + } else if (blockNameToken.Kind == TokenKind.Dynamicparam && dynamicParamBlock == null) { dynamicParamBlock = new NamedBlockAst(extent, TokenKind.Dynamicparam, statementBlock, false); @@ -1811,7 +1845,14 @@ private ScriptBlockAst NamedBlockListRule(Token lCurly, List CompleteScriptBlockBody(lCurly, ref extent, out scriptBlockExtent); return_script_block_ast: - return new ScriptBlockAst(scriptBlockExtent, usingStatements, paramBlockAst, beginBlock, processBlock, endBlock, + return new ScriptBlockAst( + scriptBlockExtent, + usingStatements, + paramBlockAst, + beginBlock, + processBlock, + endBlock, + cleanBlock, dynamicParamBlock); } @@ -1886,10 +1927,7 @@ private IScriptExtent StatementListRule(List statements, List attr is not AttributeAst)) + foreach (var attr in attributes.Where(static attr => attr is not AttributeAst)) { ReportError(attr.Extent, nameof(ParserStrings.TypeNotAllowedBeforeStatement), @@ -2884,7 +2922,7 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom return null; } - if (configurationNameToken.Kind == TokenKind.EndOfInput) + if (configurationNameToken.Kind is TokenKind.EndOfInput or TokenKind.Comma) { UngetToken(configurationNameToken); @@ -2933,7 +2971,6 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom // Runspaces.Runspace localRunspace = null; bool topLevel = false; - bool useCrossPlatformSchema = false; try { // At this point, we'll need a runspace to use to hold the metadata for the parse. If there is no @@ -2947,13 +2984,34 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom Runspaces.Runspace.DefaultRunspace = localRunspace; } - // Configuration is not supported on ARM or in ConstrainedLanguage - if (PsUtils.IsRunningOnProcessorArchitectureARM() || Runspace.DefaultRunspace.ExecutionContext.LanguageMode == PSLanguageMode.ConstrainedLanguage) + // Configuration is not supported in ConstrainedLanguage + if (Runspace.DefaultRunspace?.ExecutionContext?.LanguageMode == PSLanguageMode.ConstrainedLanguage) { - ReportError(configurationToken.Extent, - nameof(ParserStrings.ConfigurationNotAllowedInConstrainedLanguage), - ParserStrings.ConfigurationNotAllowedInConstrainedLanguage, - configurationToken.Kind.Text()); + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + ReportError(configurationToken.Extent, + nameof(ParserStrings.ConfigurationNotAllowedInConstrainedLanguage), + ParserStrings.ConfigurationNotAllowedInConstrainedLanguage, + configurationToken.Kind.Text()); + return null; + } + + SystemPolicy.LogWDACAuditMessage( + context: Runspace.DefaultRunspace?.ExecutionContext, + title: ParserStrings.WDACParserConfigKeywordLogTitle, + message: ParserStrings.WDACParserConfigKeywordLogMessage, + fqid: "ConfigurationLanguageKeywordNotAllowed", + dropIntoDebugger: true); + } + + // Configuration is not supported for ARM or ARM64 process architecture. + if (PsUtils.IsRunningOnProcessArchitectureARM()) + { + ReportError( + configurationToken.Extent, + nameof(ParserStrings.ConfigurationNotAllowedOnArm64), + ParserStrings.ConfigurationNotAllowedOnArm64, + configurationToken.Kind.Text()); return null; } @@ -2996,38 +3054,13 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom { // Load the default CIM keywords Collection CIMKeywordErrors = new Collection(); - if (ExperimentalFeature.IsEnabled(Dsc.CrossPlatform.DscClassCache.DscExperimentalFeatureName)) - { - // In addition to checking if experimental feature is enabled - // also check if PSDesiredStateConfiguration is already loaded - // if pre-v3 is already loaded then use old mof-based APIs - // otherwise use json-based APIs - - p.AddCommand(new CmdletInfo("Get-Module", typeof(Microsoft.PowerShell.Commands.GetModuleCommand))); - p.AddParameter("Name", "PSDesiredStateConfiguration"); - - bool prev3IsLoaded = false; - foreach (PSModuleInfo moduleInfo in p.Invoke()) - { - if (moduleInfo.Version.Major < 3) - { - prev3IsLoaded = true; - break; - } - } - - p.Commands.Clear(); - - useCrossPlatformSchema = !prev3IsLoaded; - if (useCrossPlatformSchema) - { - Dsc.CrossPlatform.DscClassCache.LoadDefaultCimKeywords(CIMKeywordErrors); - } - else - { - Dsc.DscClassCache.LoadDefaultCimKeywords(CIMKeywordErrors); - } + // DscSubsystem is auto-registered when PSDesiredStateConfiguration v3 module is loaded + // so if DscSubsystem is registered that means user intention to use v3 APIs. + ICrossPlatformDsc dscSubsystem = SubsystemManager.GetSubsystem(); + if (dscSubsystem != null) + { + dscSubsystem.LoadDefaultKeywords(CIMKeywordErrors); } else { @@ -3068,10 +3101,7 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom } finally { - if (p != null) - { - p.Dispose(); - } + p?.Dispose(); // // Put the parser back... @@ -3209,10 +3239,7 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom if (topLevel) { - if (_configurationKeywordsDefinedInThisFile == null) - { - _configurationKeywordsDefinedInThisFile = new Dictionary(); - } + _configurationKeywordsDefinedInThisFile ??= new Dictionary(); _configurationKeywordsDefinedInThisFile[keywordToAddForThisConfigurationStatement.Keyword] = keywordToAddForThisConfigurationStatement; } @@ -3274,9 +3301,10 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom // Clear out all of the cached classes and keywords. // They will need to be reloaded when the generated function is actually run. // - if (useCrossPlatformSchema) + ICrossPlatformDsc dscSubsystem = SubsystemManager.GetSubsystem(); + if (dscSubsystem != null) { - Dsc.CrossPlatform.DscClassCache.ClearCache(); + dscSubsystem.ClearCache(); } else { @@ -3566,10 +3594,7 @@ private StatementAst ForStatementRule(LabelToken labelToken, Token forToken) // ErrorRecovery: don't continue parsing the for statement. UngetToken(rParen); - if (endErrorStatement == null) - { - endErrorStatement = lParen.Extent; - } + endErrorStatement ??= lParen.Extent; ReportIncompleteInput(After(endErrorStatement), nameof(ParserStrings.MissingEndParenthesisAfterStatement), @@ -3878,10 +3903,7 @@ private StatementAst DynamicKeywordStatementRule(Token functionName, DynamicKeyw // we aren't expecting a name, we still do this so that the signature of the implementing function remains // the same. ExpressionAst originalInstanceName = instanceName; - if (instanceName == null) - { - instanceName = new StringConstantExpressionAst(nameToken.Extent, elementName, StringConstantType.BareWord); - } + instanceName ??= new StringConstantExpressionAst(nameToken.Extent, elementName, StringConstantType.BareWord); SkipNewlines(); @@ -4203,12 +4225,22 @@ private StatementAst ClassDefinitionRule(List customAttributes // PowerShell classes are not supported in ConstrainedLanguage if (Runspace.DefaultRunspace?.ExecutionContext?.LanguageMode == PSLanguageMode.ConstrainedLanguage) { - ReportError(classToken.Extent, - nameof(ParserStrings.ClassesNotAllowedInConstrainedLanguage), - ParserStrings.ClassesNotAllowedInConstrainedLanguage, - classToken.Kind.Text()); + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + ReportError(classToken.Extent, + nameof(ParserStrings.ClassesNotAllowedInConstrainedLanguage), + ParserStrings.ClassesNotAllowedInConstrainedLanguage, + classToken.Kind.Text()); - return null; + return null; + } + + SystemPolicy.LogWDACAuditMessage( + context: Runspace.DefaultRunspace?.ExecutionContext, + title: ParserStrings.WDACParserClassKeywordLogTitle, + message: ParserStrings.WDACParserClassKeywordLogMessage, + fqid: "ClassLanguageKeywordNotAllowed", + dropIntoDebugger: true); } SkipNewlines(); @@ -4241,11 +4273,10 @@ private StatementAst ClassDefinitionRule(List customAttributes this.SkipToken(); SkipNewlines(); ITypeName superClass; - Token unused; Token commaToken = null; while (true) { - superClass = this.TypeNameRule(allowAssemblyQualifiedNames: false, firstTypeNameToken: out unused); + superClass = this.TypeNameRule(allowAssemblyQualifiedNames: false, firstTypeNameToken: out _); if (superClass == null) { ReportIncompleteInput(After(ExtentFromFirstOf(commaToken, colonToken)), @@ -4299,10 +4330,7 @@ private StatementAst ClassDefinitionRule(List customAttributes if (astsOnError != null && astsOnError.Count > 0) { - if (nestedAsts == null) - { - nestedAsts = new List(); - } + nestedAsts ??= new List(); nestedAsts.AddRange(astsOnError); lastExtent = astsOnError.Last().Extent; @@ -4331,10 +4359,7 @@ private StatementAst ClassDefinitionRule(List customAttributes var classDefn = new TypeDefinitionAst(extent, name.Value, customAttributes?.OfType(), members, TypeAttributes.Class, superClassesList); if (customAttributes != null && customAttributes.OfType().Any()) { - if (nestedAsts == null) - { - nestedAsts = new List(); - } + nestedAsts ??= new List(); // no need to report error since the error is reported in method StatementRule nestedAsts.AddRange(customAttributes.OfType()); nestedAsts.Add(classDefn); @@ -4398,10 +4423,7 @@ private MemberAst ClassMemberRule(string className, out List astsOnError) if (attribute != null) { lastAttribute = attribute; - if (startExtent == null) - { - startExtent = attribute.Extent; - } + startExtent ??= attribute.Extent; var attributeAst = attribute as AttributeAst; if (attributeAst != null) @@ -4421,10 +4443,7 @@ private MemberAst ClassMemberRule(string className, out List astsOnError) } token = PeekToken(); - if (startExtent == null) - { - startExtent = token.Extent; - } + startExtent ??= token.Extent; switch (token.Kind) { @@ -4639,10 +4658,7 @@ private static void RecordErrorAsts(Ast errAst, ref List astsOnError) return; } - if (astsOnError == null) - { - astsOnError = new List(); - } + astsOnError ??= new List(); astsOnError.Add(errAst); } @@ -4654,10 +4670,7 @@ private static void RecordErrorAsts(IEnumerable errAsts, ref List asts return; } - if (astsOnError == null) - { - astsOnError = new List(); - } + astsOnError ??= new List(); astsOnError.AddRange(errAsts); } @@ -4687,19 +4700,19 @@ private Token NextTypeIdentifierToken() private StatementAst EnumDefinitionRule(List customAttributes, Token enumToken) { - //G enum-statement: - //G 'enum' new-lines:opt enum-name '{' enum-member-list '}' - //G 'enum' new-lines:opt enum-name ':' enum-underlying-type '{' enum-member-list '}' - //G - //G enum-name: - //G simple-name - //G - //G enum-underlying-type: - //G new-lines:opt valid-type-name new-lines:opt - //G - //G enum-member-list: - //G enum-member new-lines:opt - //G enum-member-list enum-member + // G enum-statement: + // G 'enum' new-lines:opt enum-name '{' enum-member-list '}' + // G 'enum' new-lines:opt enum-name ':' enum-underlying-type '{' enum-member-list '}' + // G + // G enum-name: + // G simple-name + // G + // G enum-underlying-type: + // G new-lines:opt valid-type-name new-lines:opt + // G + // G enum-member-list: + // G enum-member new-lines:opt + // G enum-member-list enum-member const TypeCode ValidUnderlyingTypeCodes = TypeCode.Byte | TypeCode.Int16 | TypeCode.Int32 | TypeCode.Int64 | TypeCode.SByte | TypeCode.UInt16 | TypeCode.UInt32 | TypeCode.UInt64; @@ -4726,8 +4739,7 @@ private StatementAst EnumDefinitionRule(List customAttributes, this.SkipToken(); SkipNewlines(); ITypeName underlyingType; - Token unused; - underlyingType = this.TypeNameRule(allowAssemblyQualifiedNames: false, firstTypeNameToken: out unused); + underlyingType = this.TypeNameRule(allowAssemblyQualifiedNames: false, firstTypeNameToken: out _); if (underlyingType == null) { ReportIncompleteInput( @@ -4996,7 +5008,7 @@ private StatementAst UsingStatementRule(Token usingToken) SkipToken(); var aliasToken = NextToken(); - if (aliasToken.Kind == TokenKind.EndOfInput) + if (aliasToken.Kind is TokenKind.EndOfInput or TokenKind.NewLine or TokenKind.Semi) { UngetToken(aliasToken); ReportIncompleteInput(After(equalsToken), @@ -5005,6 +5017,12 @@ private StatementAst UsingStatementRule(Token usingToken) return new ErrorStatementAst(ExtentOf(usingToken, equalsToken)); } + if (aliasToken.Kind == TokenKind.Comma) + { + ReportError(aliasToken.Extent, nameof(ParserStrings.UnexpectedUnaryOperator), ParserStrings.UnexpectedUnaryOperator, aliasToken.Text); + return new ErrorStatementAst(ExtentOf(usingToken, aliasToken)); + } + var aliasAst = GetCommandArgument(CommandArgumentContext.CommandArgument, aliasToken); if (kind == UsingStatementKind.Module && aliasAst is HashtableAst) { @@ -5012,7 +5030,19 @@ private StatementAst UsingStatementRule(Token usingToken) } else if (aliasAst is not StringConstantExpressionAst) { - return new ErrorStatementAst(ExtentOf(usingToken, aliasAst), new Ast[] { itemAst, aliasAst }); + var errorExtent = ExtentFromFirstOf(aliasAst, aliasToken); + Ast[] nestedAsts; + if (aliasAst is null) + { + nestedAsts = new Ast[] { itemAst }; + } + else + { + nestedAsts = new Ast[] { itemAst, aliasAst }; + } + + ReportError(errorExtent, nameof(ParserStrings.InvalidValueForUsingItemName), ParserStrings.InvalidValueForUsingItemName, errorExtent.Text); + return new ErrorStatementAst(ExtentOf(usingToken, errorExtent), nestedAsts); } RequireStatementTerminator(); @@ -5114,15 +5144,8 @@ private StringConstantExpressionAst ResolveUsingAssembly(StringConstantExpressio workingDirectory = Path.GetDirectoryName(scriptFileName); } - assemblyFileName = workingDirectory + @"\" + assemblyFileName; + assemblyFileName = Path.Combine(workingDirectory, assemblyFileName); } - -#if !CORECLR - if (!File.Exists(assemblyFileName)) - { - GlobalAssemblyCache.ResolvePartialName(assemblyName, out assemblyFileName); - } -#endif } catch { @@ -5202,7 +5225,7 @@ private StatementAst MethodDeclarationRule(Token functionNameToken, string class SkipToken(); // we don't allow syntax // : base{ script } - // as a short for for + // as a short for // : base( { script } ) baseCtorCallParams = InvokeParamParenListRule(lParen, out baseCallLastExtent); this.SkipNewlines(); @@ -5229,11 +5252,9 @@ private StatementAst MethodDeclarationRule(Token functionNameToken, string class SetTokenizerMode(oldTokenizerMode); } - if (baseCtorCallParams == null) - { + baseCtorCallParams ??= // Assuming implicit default ctor - baseCtorCallParams = new List(); - } + new List(); } Token lCurly = NextToken(); @@ -5522,10 +5543,7 @@ private CatchClauseAst CatchBlockRule(ref IScriptExtent endErrorStatement, ref L break; } - if (exceptionTypes == null) - { - exceptionTypes = new List(); - } + exceptionTypes ??= new List(); exceptionTypes.Add(typeConstraintAst); @@ -5766,7 +5784,7 @@ private PipelineBaseAst PipelineChainRule() // just look for pipelines as before. RuntimeHelpers.EnsureSufficientExecutionStack(); - // First look for assignment, since PipelineRule once handled that and this supercedes that. + // First look for assignment, since PipelineRule once handled that and this supersedes that. // We may end up with an expression here as a result, // in which case we hang on to it to pass it into the first pipeline rule call. Token assignToken = null; @@ -6028,10 +6046,7 @@ private PipelineBaseAst PipelineRule( { SkipToken(); - if (redirections == null) - { - redirections = new RedirectionAst[CommandBaseAst.MaxRedirections]; - } + redirections ??= new RedirectionAst[CommandBaseAst.MaxRedirections]; IScriptExtent unused = null; lastRedirection = RedirectionRule(redirectionToken, redirections, ref unused); @@ -6043,7 +6058,7 @@ private PipelineBaseAst PipelineRule( commandAst = new CommandExpressionAst( exprExtent, expr, - redirections?.Where(r => r != null)); + redirections?.Where(static r => r != null)); } else { @@ -6052,10 +6067,7 @@ private PipelineBaseAst PipelineRule( if (commandAst != null) { - if (startExtent == null) - { - startExtent = commandAst.Extent; - } + startExtent ??= commandAst.Extent; pipelineElements.Add(commandAst); } @@ -6408,10 +6420,7 @@ private ExpressionAst GetCommandArgument(CommandArgumentContext context, Token t } commaToken = token; - if (commandArgs == null) - { - commandArgs = new List(); - } + commandArgs ??= new List(); commandArgs.Add(exprAst); @@ -6579,10 +6588,7 @@ internal Ast CommandRule(bool forDynamicKeyword) case TokenKind.RedirectInStd: if ((context & CommandArgumentContext.CommandName) == 0) { - if (redirections == null) - { - redirections = new RedirectionAst[CommandBaseAst.MaxRedirections]; - } + redirections ??= new RedirectionAst[CommandBaseAst.MaxRedirections]; RedirectionRule((RedirectionToken)token, redirections, ref endExtent); } @@ -6674,7 +6680,7 @@ internal Ast CommandRule(bool forDynamicKeyword) return new CommandAst(ExtentOf(firstToken, endExtent), elements, dotSource || ampersand ? firstToken.Kind : TokenKind.Unknown, - redirections?.Where(r => r != null)); + redirections?.Where(static r => r != null)); } #endregion Pipelines @@ -6721,7 +6727,7 @@ private ExpressionAst ExpressionRule(bool endNumberOnTernaryOpChars = false) SkipToken(); SkipNewlines(); - // We have seen the ternary operator '?' and now expecting the 'IfFalse' expression. + // We have seen the ternary operator '?' and now expecting the 'IfTrue' expression. ExpressionAst ifTrue = ExpressionRule(endNumberOnTernaryOpChars: true); if (ifTrue == null) { @@ -7148,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); @@ -7184,10 +7190,7 @@ private ExpressionAst UnaryExpressionRule(bool endNumberOnTernaryOpChars = false } } - if (expr == null) - { - expr = new TypeExpressionAst(lastAttribute.Extent, lastAttribute.TypeName); - } + expr ??= new TypeExpressionAst(lastAttribute.Extent, lastAttribute.TypeName); } for (int i = attributes.Count - 2; i >= 0; --i) @@ -7733,34 +7736,128 @@ private ExpressionAst MemberAccessRule(ExpressionAst targetExpr, Token operatorT member = GetSingleCommandArgument(CommandArgumentContext.CommandArgument) ?? new ErrorExpressionAst(ExtentOf(targetExpr, operatorToken)); } - else + else if (_ungotToken == null) { + // Member name may be an incomplete token like `$a.$(Command-Name`, in which case, '_ungotToken != null'. + // We do not look for generic args or invocation token if the member name token is recognisably incomplete. + int resyncIndex = _tokenizer.GetRestorePoint(); + List genericTypeArguments = GenericMethodArgumentsRule(resyncIndex, out Token rBracket); Token lParen = NextInvokeMemberToken(); + if (lParen != null) { + // When we reach here, we either had a legit section of generic arguments (in which case, `rBracket` + // won't be null), or we saw `lParen` directly following the member token (in which case, `rBracket` + // will be null). + int endColumnNumber = rBracket is null ? member.Extent.EndColumnNumber : rBracket.Extent.EndColumnNumber; + Diagnostics.Assert(lParen.Kind == TokenKind.LParen || lParen.Kind == TokenKind.LCurly, "token kind incorrect"); - Diagnostics.Assert(member.Extent.EndColumnNumber == lParen.Extent.StartColumnNumber, - "member and paren must be adjacent"); - return MemberInvokeRule(targetExpr, lParen, operatorToken, member); + Diagnostics.Assert( + endColumnNumber == lParen.Extent.StartColumnNumber, + "member and paren must be adjacent when the method is not generic"); + return MemberInvokeRule(targetExpr, lParen, operatorToken, member, genericTypeArguments); + } + else if (rBracket != null) + { + // We had a legit section of generic arguments but no 'lParen' following that, so this is not a method + // invocation, but an invalid indexing operation. Resync the tokenizer back to before the generic arg + // parsing and then continue. + Resync(resyncIndex); } } return new MemberExpressionAst( - ExtentOf(targetExpr, member), - targetExpr, - member, - @static: operatorToken.Kind == TokenKind.ColonColon, - nullConditional: operatorToken.Kind == TokenKind.QuestionDot); + ExtentOf(targetExpr, member), + targetExpr, + member, + @static: operatorToken.Kind == TokenKind.ColonColon, + nullConditional: operatorToken.Kind == TokenKind.QuestionDot); } - private ExpressionAst MemberInvokeRule(ExpressionAst targetExpr, Token lBracket, Token operatorToken, CommandElementAst member) + private List GenericMethodArgumentsRule(int resyncIndex, out Token rBracketToken) + { + List genericTypes = null; + + Token lBracket = NextToken(); + rBracketToken = null; + + if (lBracket.Kind != TokenKind.LBracket) + { + // We cannot avoid this Resync(); if we use PeekToken() to try to avoid a Resync(), the method called + // after this [`NextInvokeMemberToken()` or `NextMemberAccessToken()`] will note that an _ungotToken + // is present and assume an error state. That will cause any property accesses or non-generic method + // calls to throw a parse error. + Resync(resyncIndex); + return null; + } + + // This is either a InvokeMember expression with generic type arguments, or some sort of collection index + // on a property. + TokenizerMode oldTokenizerMode = _tokenizer.Mode; + try + { + // Switch to typename mode to avoid aggressive argument tokenization. + SetTokenizerMode(TokenizerMode.TypeName); + + SkipNewlines(); + Token firstToken = NextToken(); + + // For method generic arguments, we only support the syntax `$var.Method[TypeName1 <, TypeName2 ...>]`, + // not the syntax `$var.Method[[TypeName1] <, [TypeName2] ...>]`. + // The latter syntax has been supported for type expression since the beginning, but it's ambiguous in + // this scenario because we could be looking at an indexing operation on a property like: + // `$var.Property[]` + // and the `` could start with a type expression like `[TypeName]::Method()`, or even just + // a single type expression acting as a key to a hashtable property. Such cases will cause ambiguities. + // + // It could be possible to write code that sorts out the ambiguity and continue to support the latter + // syntax for method generic arguments, and thus to allow assembly-qualified type names. But we choose + // not to do so because: + // 1. that will definitely increase the complexity of the parsing code and also make it fragile; + // 2. the latter syntax hurts readability a lot due to the number of opening/closing brackets. + // The downside is that the assembly-qualified type names won't be supported for method generic args, + // but that's likely not a problem in practice, and we can revisit if it turns out otherwise. + if (firstToken.Kind == TokenKind.Identifier) + { + resyncIndex = -1; + genericTypes = GenericTypeArgumentsRule(firstToken, out rBracketToken); + + if (rBracketToken.Kind != TokenKind.RBracket) + { + UngetToken(rBracketToken); + ReportIncompleteInput( + Before(rBracketToken), + nameof(ParserStrings.EndSquareBracketExpectedAtEndOfType), + ParserStrings.EndSquareBracketExpectedAtEndOfType); + rBracketToken = null; + } + } + } + finally + { + SetTokenizerMode(oldTokenizerMode); + + if (resyncIndex > 0) + { + Resync(resyncIndex); + } + } + + return genericTypes; + } + + private ExpressionAst MemberInvokeRule( + ExpressionAst targetExpr, + Token lBracket, + Token operatorToken, + CommandElementAst member, + IList genericTypes) { // G invocation-expression: target-expression passed as a parameter. lBracket can be '(' or '{'. // G target-expression member-name invoke-param-list // G invoke-param-list: // G '(' invoke-param-paren-list // G script-block - IScriptExtent lastExtent = null; List arguments; @@ -7771,6 +7868,7 @@ private ExpressionAst MemberInvokeRule(ExpressionAst targetExpr, Token lBracket, else { arguments = new List(); + // handle the construct $x.methodName{2+2} as through it had been written $x.methodName({2+2}) SkipNewlines(); ExpressionAst argument = ScriptBlockExpressionRule(lBracket); @@ -7784,7 +7882,8 @@ private ExpressionAst MemberInvokeRule(ExpressionAst targetExpr, Token lBracket, member, arguments, operatorToken.Kind == TokenKind.ColonColon, - operatorToken.Kind == TokenKind.QuestionDot); + operatorToken.Kind == TokenKind.QuestionDot, + genericTypes); } private List InvokeParamParenListRule(Token lParen, out IScriptExtent lastExtent) @@ -7874,7 +7973,18 @@ private ExpressionAst ElementAccessRule(ExpressionAst primaryExpression, Token l // G primary-expression '[' new-lines:opt expression new-lines:opt ']' SkipNewlines(); - ExpressionAst indexExpr = ExpressionRule(); + bool oldDisableCommaOperator = _disableCommaOperator; + _disableCommaOperator = false; + ExpressionAst indexExpr = null; + try + { + indexExpr = ExpressionRule(); + } + finally + { + _disableCommaOperator = oldDisableCommaOperator; + } + if (indexExpr == null) { // ErrorRecovery: hope we see a closing bracket. If we don't, we'll pretend we saw @@ -7979,7 +8089,7 @@ private static void AssertErrorIdCorrespondsToMsgString(string errorId, string e } } - Diagnostics.Assert(msgCorrespondsToString, string.Format("Parser error ID \"{0}\" must correspond to the error message \"{1}\"", errorId, errorMsg)); + Diagnostics.Assert(msgCorrespondsToString, $"Parser error ID \"{errorId}\" must correspond to the error message \"{errorMsg}\""); } private static object[] arrayOfOneArg diff --git a/src/System.Management.Automation/engine/parser/Position.cs b/src/System.Management.Automation/engine/parser/Position.cs index 54f4c3eccbe..9e1363e4a53 100644 --- a/src/System.Management.Automation/engine/parser/Position.cs +++ b/src/System.Management.Automation/engine/parser/Position.cs @@ -15,12 +15,13 @@ namespace System.Management.Automation.Language /// /// Represents a single point in a script. The script may come from a file or interactive input. /// +#nullable enable public interface IScriptPosition { /// /// The name of the file, or if the script did not come from a file, then null. /// - string File { get; } + string? File { get; } /// /// The line number of the position, with the value 1 being the first line. @@ -45,8 +46,9 @@ public interface IScriptPosition /// /// The complete script that this position is included in. /// - string GetFullScript(); + string? GetFullScript(); } +#nullable restore /// /// Represents the a span of text in a script. @@ -336,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) @@ -354,10 +353,18 @@ internal static bool ContainsLineAndColumn(this IScriptExtent extent, int line, { if (extent.StartLineNumber == line) { - if (column == 0) return true; + if (column == 0) + { + return true; + } + if (column >= extent.StartColumnNumber) { - if (extent.EndLineNumber != extent.StartLineNumber) return true; + if (extent.EndLineNumber != extent.StartLineNumber) + { + return true; + } + return (column < extent.EndColumnNumber); } @@ -764,9 +771,9 @@ public string Text _endPosition.ColumnNumber - _startPosition.ColumnNumber); } - return string.Format(CultureInfo.InvariantCulture, "{0}...{1}", - _startPosition.Line.Substring(_startPosition.ColumnNumber), - _endPosition.Line.Substring(0, _endPosition.ColumnNumber)); + var start = _startPosition.Line.AsSpan(_startPosition.ColumnNumber); + var end = _endPosition.Line.AsSpan(0, _endPosition.ColumnNumber); + return string.Create(CultureInfo.InvariantCulture, $"{start}...{end}"); } else { diff --git a/src/System.Management.Automation/engine/parser/PreOrderVisitor.cs b/src/System.Management.Automation/engine/parser/PreOrderVisitor.cs index 33b2fadec64..675e53394c6 100644 --- a/src/System.Management.Automation/engine/parser/PreOrderVisitor.cs +++ b/src/System.Management.Automation/engine/parser/PreOrderVisitor.cs @@ -6,7 +6,7 @@ namespace System.Management.Automation.Language { /// - /// Each Visit* method in returns one of these values to control + /// Each Visit* method in returns one of these values to control /// how visiting nodes in the AST should proceed. /// public enum AstVisitAction @@ -37,10 +37,7 @@ public abstract class AstVisitor internal AstVisitAction CheckForPostAction(Ast ast, AstVisitAction action) { var postActionHandler = this as IAstPostVisitHandler; - if (postActionHandler != null) - { - postActionHandler.PostVisit(ast); - } + postActionHandler?.PostVisit(ast); return action; } diff --git a/src/System.Management.Automation/engine/parser/SafeValues.cs b/src/System.Management.Automation/engine/parser/SafeValues.cs index a727ae1d287..05c87daab5b 100644 --- a/src/System.Management.Automation/engine/parser/SafeValues.cs +++ b/src/System.Management.Automation/engine/parser/SafeValues.cs @@ -356,7 +356,7 @@ public object VisitParenExpression(ParenExpressionAst parenExpressionAst) * except in the case of handling the unary operator * ExecutionContext is provided to ensure we can resolve variables */ - internal class GetSafeValueVisitor : ICustomAstVisitor2 + internal sealed class GetSafeValueVisitor : ICustomAstVisitor2 { internal enum SafeValueContext { @@ -530,7 +530,8 @@ public object VisitIndexExpression(IndexExpressionAst indexExpressionAst) // Get the value of the index and value and call the compiler var index = indexExpressionAst.Index.Accept(this); var target = indexExpressionAst.Target.Accept(this); - if (index == null || target == null) + + if (index is null || target is null) { throw new ArgumentNullException(nameof(indexExpressionAst)); } @@ -548,10 +549,7 @@ public object VisitExpandableStringExpression(ExpandableStringExpressionAst expa ofs = t_context.SessionState.PSVariable.GetValue("OFS") as string; } - if (ofs == null) - { - ofs = " "; - } + ofs ??= " "; for (int offset = 0; offset < safeValues.Length; offset++) { diff --git a/src/System.Management.Automation/engine/parser/SemanticChecks.cs b/src/System.Management.Automation/engine/parser/SemanticChecks.cs index 3afd10ff6e2..4a2a3bb626c 100644 --- a/src/System.Management.Automation/engine/parser/SemanticChecks.cs +++ b/src/System.Management.Automation/engine/parser/SemanticChecks.cs @@ -5,17 +5,20 @@ using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; +using System.Text.RegularExpressions; using Microsoft.PowerShell; +using System.Management.Automation.Security; +using System.Management.Automation.Subsystem; +using System.Management.Automation.Subsystem.DSC; using Microsoft.PowerShell.DesiredStateConfiguration.Internal; namespace System.Management.Automation.Language { - internal class SemanticChecks : AstVisitor2, IAstPostVisitHandler + internal sealed partial class SemanticChecks : AstVisitor2, IAstPostVisitHandler { private readonly Parser _parser; @@ -87,20 +90,16 @@ private void CheckForDuplicateParameters(ReadOnlyCollection parame foreach (var parameter in parameters) { string parameterName = parameter.Name.VariablePath.UserPath; - if (parametersSet.Contains(parameterName)) + if (!parametersSet.Add(parameterName)) { _parser.ReportError(parameter.Name.Extent, nameof(ParserStrings.DuplicateFormalParameter), ParserStrings.DuplicateFormalParameter, parameterName); } - else - { - parametersSet.Add(parameterName); - } var voidConstraint = - parameter.Attributes.OfType().FirstOrDefault(t => typeof(void) == t.TypeName.GetReflectionType()); + parameter.Attributes.OfType().FirstOrDefault(static t => typeof(void) == t.TypeName.GetReflectionType()); if (voidConstraint != null) { @@ -239,7 +238,7 @@ public override AstVisitAction VisitAttribute(AttributeAst attributeAst) foreach (var namedArg in attributeAst.NamedArguments) { string name = namedArg.ArgumentName; - if (names.Contains(name)) + if (!names.Add(name)) { _parser.ReportError(namedArg.Extent, nameof(ParserStrings.DuplicateNamedArgument), @@ -248,8 +247,6 @@ public override AstVisitAction VisitAttribute(AttributeAst attributeAst) } else { - names.Add(name); - if (!namedArg.ExpressionOmitted && !IsValidAttributeArgument(namedArg.Argument, constantValueVisitor)) { var error = GetNonConstantAttributeArgErrorExpr(constantValueVisitor); @@ -404,10 +401,11 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem ParserStrings.ParamBlockNotAllowedInMethod); } - if (body.BeginBlock != null || - body.ProcessBlock != null || - body.DynamicParamBlock != null || - !body.EndBlock.Unnamed) + if (body.BeginBlock != null + || body.ProcessBlock != null + || body.CleanBlock != null + || body.DynamicParamBlock != null + || !body.EndBlock.Unnamed) { _parser.ReportError(Parser.ExtentFromFirstOf(body.DynamicParamBlock, body.BeginBlock, body.ProcessBlock, body.EndBlock), nameof(ParserStrings.NamedBlockNotAllowedInMethod), @@ -530,7 +528,10 @@ public override AstVisitAction VisitForEachStatement(ForEachStatementAst forEach public override AstVisitAction VisitTryStatement(TryStatementAst tryStatementAst) { - if (tryStatementAst.CatchClauses.Count <= 1) return AstVisitAction.Continue; + if (tryStatementAst.CatchClauses.Count <= 1) + { + return AstVisitAction.Continue; + } for (int i = 0; i < tryStatementAst.CatchClauses.Count - 1; ++i) { @@ -547,7 +548,10 @@ public override AstVisitAction VisitTryStatement(TryStatementAst tryStatementAst break; } - if (block2.IsCatchAll) continue; + if (block2.IsCatchAll) + { + continue; + } foreach (TypeConstraintAst typeLiteral1 in block1.CatchTypes) { @@ -917,6 +921,13 @@ public override AstVisitAction VisitConvertExpression(ConvertExpressionAst conve ParserStrings.OrderedAttributeOnlyOnHashLiteralNode, convertExpressionAst.Type.TypeName.FullName); } + + // Currently, the type name '[ordered]' is handled specially in PowerShell. + // When used in a conversion expression, it's only allowed on a hashliteral node, and it's + // always interpreted as an initializer for a case-insensitive + // 'System.Collections.Specialized.OrderedDictionary' by the compiler. + // So, we can return early from here. + return AstVisitAction.Continue; } if (typeof(PSReference) == convertExpressionAst.Type.TypeName.GetReflectionType()) @@ -1017,7 +1028,7 @@ public override AstVisitAction VisitUsingExpression(UsingExpressionAst usingExpr return AstVisitAction.Continue; } - private ExpressionAst CheckUsingExpression(ExpressionAst exprAst) + private static ExpressionAst CheckUsingExpression(ExpressionAst exprAst) { RuntimeHelpers.EnsureSufficientExecutionStack(); if (exprAst is VariableExpressionAst) @@ -1107,7 +1118,7 @@ public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) if (keyStrAst != null) { var keyStr = keyStrAst.Value.ToString(); - if (keys.Contains(keyStr)) + if (!keys.Add(keyStr)) { string errorId; string errorMsg; @@ -1124,10 +1135,6 @@ public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) _parser.ReportError(entry.Item1.Extent, errorId, errorMsg, keyStr); } - else - { - keys.Add(keyStr); - } } } @@ -1303,20 +1310,50 @@ public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionA public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatementAst) { - bool usingKindSupported = usingStatementAst.UsingStatementKind == UsingStatementKind.Namespace || - usingStatementAst.UsingStatementKind == UsingStatementKind.Assembly || - usingStatementAst.UsingStatementKind == UsingStatementKind.Module; - if (!usingKindSupported || - usingStatementAst.Alias != null) + UsingStatementKind kind = usingStatementAst.UsingStatementKind; + bool usingKindSupported = kind is UsingStatementKind.Namespace or UsingStatementKind.Assembly or UsingStatementKind.Module; + if (!usingKindSupported || usingStatementAst.Alias != null) { - _parser.ReportError(usingStatementAst.Extent, + _parser.ReportError( + usingStatementAst.Extent, nameof(ParserStrings.UsingStatementNotSupported), ParserStrings.UsingStatementNotSupported); } + if (kind is UsingStatementKind.Namespace) + { + Regex nsPattern = NamespacePattern(); + if (!nsPattern.IsMatch(usingStatementAst.Name.Value)) + { + _parser.ReportError( + usingStatementAst.Name.Extent, + nameof(ParserStrings.InvalidNamespaceValue), + ParserStrings.InvalidNamespaceValue); + } + } + return AstVisitAction.Continue; } + /// + /// This regular expression is for validating if a namespace string is valid. + /// + /// In C#, a legit namespace is defined as `identifier ('.' identifier)*` [see https://learn.microsoft.com/dotnet/csharp/language-reference/language-specification/namespaces#143-namespace-declarations]. + /// And `identifier` is defined in https://learn.microsoft.com/dotnet/csharp/fundamentals/coding-style/identifier-names#naming-rules, summarized below: + /// - Identifiers must start with a letter or underscore (_). + /// - Identifiers can contain + /// * Unicode letter characters (categories: Lu, Ll, Lt, Lm, Lo or Nl); + /// * decimal digit characters (category: Nd); + /// * Unicode connecting characters (category: Pc); + /// * Unicode combining characters (categories: Mn, Mc); + /// * Unicode formatting characters (category: Cf). + /// + /// For details about how Unicode categories are represented in regular expression, see the "Unicode Categories" section in the following article: + /// - https://www.regular-expressions.info/unicode.html + /// + [GeneratedRegex(@"^[\p{L}\p{Nl}_][\p{L}\p{Nl}\p{Nd}\p{Pc}\p{Mn}\p{Mc}\p{Cf}_]*(?:\.[\p{L}\p{Nl}_][\p{L}\p{Nl}\p{Nd}\p{Pc}\p{Mn}\p{Mc}\p{Cf}_]*)*$")] + private static partial Regex NamespacePattern(); + public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { // @@ -1386,7 +1423,7 @@ public override AstVisitAction VisitDynamicKeywordStatement(DynamicKeywordStatem else if (!keyword.Properties.ContainsKey(propName.Value)) { IOrderedEnumerable tableKeys = keyword.Properties.Keys - .OrderBy(key => key, StringComparer.OrdinalIgnoreCase); + .Order(StringComparer.OrdinalIgnoreCase); _parser.ReportError(propName.Extent, nameof(ParserStrings.InvalidInstanceProperty), @@ -1405,7 +1442,10 @@ public override AstVisitAction VisitDynamicKeywordStatement(DynamicKeywordStatem { StringConstantExpressionAst nameAst = dynamicKeywordStatementAst.CommandElements[0] as StringConstantExpressionAst; Diagnostics.Assert(nameAst != null, "nameAst should never be null"); - if (!DscClassCache.SystemResourceNames.Contains(nameAst.Extent.Text.Trim())) + var extentText = nameAst.Extent.Text.Trim(); + ICrossPlatformDsc dscSubsystem = SubsystemManager.GetSubsystem(); + var extentTextIsASystemResourceName = (dscSubsystem != null) ? dscSubsystem.IsSystemResourceName(extentText) : DscClassCache.SystemResourceNames.Contains(extentText); + if (!extentTextIsASystemResourceName) { if (configAst.ConfigurationType == ConfigurationType.Meta && !dynamicKeywordStatementAst.Keyword.IsMetaDSCResource()) { @@ -1676,7 +1716,11 @@ private static void CheckGet(Parser parser, FunctionMemberAst functionMemberAst, /// True if it is a Test method with qualified return type and signature; otherwise, false. private static void CheckTest(FunctionMemberAst functionMemberAst, ref bool hasTest) { - if (hasTest) return; + if (hasTest) + { + return; + } + hasTest = (functionMemberAst.Name.Equals("Test", StringComparison.OrdinalIgnoreCase) && functionMemberAst.Parameters.Count == 0 && functionMemberAst.ReturnType != null && @@ -1689,7 +1733,11 @@ private static void CheckTest(FunctionMemberAst functionMemberAst, ref bool hasT /// True if it is a Set method with qualified return type and signature; otherwise, false. private static void CheckSet(FunctionMemberAst functionMemberAst, ref bool hasSet) { - if (hasSet) return; + if (hasSet) + { + return; + } + hasSet = (functionMemberAst.Name.Equals("Set", StringComparison.OrdinalIgnoreCase) && functionMemberAst.Parameters.Count == 0 && functionMemberAst.IsReturnTypeVoid()); @@ -1805,11 +1853,21 @@ internal static void CheckDataStatementLanguageModeAtRuntime(DataStatementAst da // we only need to check the language mode. if (executionContext.LanguageMode == PSLanguageMode.ConstrainedLanguage) { - var parser = new Parser(); - parser.ReportError(dataStatementAst.CommandsAllowed[0].Extent, - nameof(ParserStrings.DataSectionAllowedCommandDisallowed), - ParserStrings.DataSectionAllowedCommandDisallowed); - throw new ParseException(parser.ErrorList.ToArray()); + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + var parser = new Parser(); + parser.ReportError(dataStatementAst.CommandsAllowed[0].Extent, + nameof(ParserStrings.DataSectionAllowedCommandDisallowed), + ParserStrings.DataSectionAllowedCommandDisallowed); + throw new ParseException(parser.ErrorList.ToArray()); + } + + SystemPolicy.LogWDACAuditMessage( + context: executionContext, + title: ParserStrings.WDACParserDSSupportedCommandLogTitle, + message: ParserStrings.WDACParserDSSupportedCommandLogMessage, + fqid: "SupportedCommandInDataSectionNotSupported", + dropIntoDebugger: true); } } diff --git a/src/System.Management.Automation/engine/parser/SymbolResolver.cs b/src/System.Management.Automation/engine/parser/SymbolResolver.cs index 2da38a4ed06..097bcabac3b 100644 --- a/src/System.Management.Automation/engine/parser/SymbolResolver.cs +++ b/src/System.Management.Automation/engine/parser/SymbolResolver.cs @@ -111,11 +111,8 @@ internal void AddTypeFromUsingModule(Parser parser, TypeDefinitionAst typeDefini TypeLookupResult result; if (_typeTable.TryGetValue(typeDefinitionAst.Name, out result)) { - if (result.ExternalNamespaces != null) - { - // override external type by the type defined in the current namespace - result.ExternalNamespaces.Add(moduleInfo.Name); - } + // override external type by the type defined in the current namespace + result.ExternalNamespaces?.Add(moduleInfo.Name); } else { @@ -179,7 +176,7 @@ internal void AddTypesInScope(Ast ast) // class C1 { [C2]$x } // class C2 { [C1]$c1 } - var types = ast.FindAll(x => x is TypeDefinitionAst, searchNestedScriptBlocks: false); + var types = ast.FindAll(static x => x is TypeDefinitionAst, searchNestedScriptBlocks: false); foreach (var type in types) { AddType((TypeDefinitionAst)type); @@ -275,7 +272,7 @@ public bool IsInMethodScope() } } - internal class SymbolResolver : AstVisitor2, IAstPostVisitHandler + internal sealed class SymbolResolver : AstVisitor2, IAstPostVisitHandler { private readonly SymbolResolvePostActionVisitor _symbolResolvePostActionVisitor; internal readonly SymbolTable _symbolTable; @@ -302,7 +299,7 @@ private static PowerShell UsingStatementResolvePowerShell InitialSessionState iss = InitialSessionState.Create(); iss.Commands.Add(new SessionStateCmdletEntry("Get-Module", typeof(GetModuleCommand), null)); var sessionStateProviderEntry = new SessionStateProviderEntry(FileSystemProvider.ProviderName, typeof(FileSystemProvider), null); - var snapin = PSSnapInReader.ReadEnginePSSnapIns().FirstOrDefault(snapIn => snapIn.Name.Equals("Microsoft.PowerShell.Core", StringComparison.OrdinalIgnoreCase)); + var snapin = PSSnapInReader.ReadEnginePSSnapIns().FirstOrDefault(static snapIn => snapIn.Name.Equals("Microsoft.PowerShell.Core", StringComparison.OrdinalIgnoreCase)); sessionStateProviderEntry.SetPSSnapIn(snapin); iss.Providers.Add(sessionStateProviderEntry); t_usingStatementResolvePowerShell = PowerShell.Create(iss); @@ -407,7 +404,7 @@ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst a var typeAst = _symbolTable.GetCurrentTypeDefinitionAst(); Diagnostics.Assert(typeAst != null, "Method scopes can exist only inside type definitions."); - string typeString = string.Format(CultureInfo.InvariantCulture, "[{0}]::", typeAst.Name); + string typeString = string.Create(CultureInfo.InvariantCulture, $"[{typeAst.Name}]::"); _parser.ReportError(variableExpressionAst.Extent, nameof(ParserStrings.MissingTypeInStaticPropertyAssignment), ParserStrings.MissingTypeInStaticPropertyAssignment, diff --git a/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs b/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs index 45f278b6b5a..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 AnalyzedCommands { get; } = new HashSet(); + public TypeInferenceRuntimePermissions RuntimePermissions { get; set; } internal PowerShellExecutionHelper Helper { get; } @@ -195,7 +197,25 @@ internal IList 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(consolidatedString)); } @@ -298,7 +318,7 @@ internal void AddMembersByInferredTypeDefinitionAst( else { var functionMember = (FunctionMemberAst)member; - add = functionMember.IsStatic == isStatic; + add = (functionMember.IsConstructor && isStatic) || (!functionMember.IsConstructor && functionMember.IsStatic == isStatic); foundConstructor |= functionMember.IsConstructor; } @@ -322,7 +342,18 @@ internal void AddMembersByInferredTypeDefinitionAst( } var baseTypeDefinitionAst = baseTypeName._typeDefinitionAst; - results.AddRange(GetMembersByInferredType(new PSTypeName(baseTypeDefinitionAst), isStatic, filterToCall)); + if (baseTypeDefinitionAst is null) + { + var baseReflectionType = baseTypeName.GetReflectionType(); + if (baseReflectionType is not null) + { + results.AddRange(GetMembersByInferredType(new PSTypeName(baseReflectionType), isStatic, filterToCall)); + } + } + else + { + results.AddRange(GetMembersByInferredType(new PSTypeName(baseTypeDefinitionAst), isStatic, filterToCall)); + } } // Add stuff from our base class System.Object. @@ -353,7 +384,22 @@ internal void AddMembersByInferredTypeDefinitionAst( filterToCall = filter; } - results.AddRange(GetMembersByInferredType(new PSTypeName(typeof(object)), isStatic, filterToCall)); + PSTypeName baseMembersType; + if (typename.TypeDefinitionAst.IsEnum) + { + if (!isStatic) + { + results.Add(new PSInferredProperty("value__", new PSTypeName(typeof(int)))); + } + + baseMembersType = new PSTypeName(typeof(Enum)); + } + else + { + baseMembersType = new PSTypeName(typeof(object)); + } + + results.AddRange(GetMembersByInferredType(baseMembersType, isStatic, filterToCall)); } internal void AddMembersByInferredTypeCimType(PSTypeName typename, List results, Func filterToCall) @@ -416,7 +462,29 @@ private static bool TryGetRepresentativeTypeNameFromValue(object value, out PSTy value = PSObject.Base(value); if (value != null) { - type = new PSTypeName(value.GetType()); + var typeObject = value.GetType(); + + if (typeObject.FullName.Equals("System.Management.Automation.PSObject", StringComparison.Ordinal)) + { + var psobjectPropertyList = new List(); + foreach (var property in ((PSObject)value).Properties) + { + if (property.IsHidden) + { + continue; + } + + var propertyTypeName = new PSTypeName(property.TypeNameOfValue); + psobjectPropertyList.Add(new PSMemberNameAndType(property.Name, propertyTypeName, property.Value)); + } + + type = PSSyntheticTypeName.Create(typeObject, psobjectPropertyList); + } + else + { + type = new PSTypeName(typeObject); + } + return true; } } @@ -536,11 +604,24 @@ object ICustomAstVisitor.VisitHashtable(HashtableAst hashtableAst) if (hashtableAst.KeyValuePairs.Count > 0) { var properties = new List(); + void AddInferredTypes(Ast ast, string keyName) + { + bool foundAnyTypes = false; + foreach (PSTypeName item in InferTypes(ast)) + { + foundAnyTypes = true; + properties.Add(new PSMemberNameAndType(keyName, item)); + } + + if (!foundAnyTypes) + { + properties.Add(new PSMemberNameAndType(keyName, new PSTypeName("System.Object"))); + } + } foreach (var kv in hashtableAst.KeyValuePairs) { string name = null; - string typeName = null; if (kv.Item1 is StringConstantExpressionAst stringConstantExpressionAst) { name = stringConstantExpressionAst.Value; @@ -554,32 +635,33 @@ object ICustomAstVisitor.VisitHashtable(HashtableAst hashtableAst) name = nameValue.ToString(); } - if (name != null) + if (name is not null) { - object value = null; if (kv.Item2 is PipelineAst pipelineAst && pipelineAst.GetPureExpression() is ExpressionAst expression) { - switch (expression) + object value; + if (expression is ConstantExpressionAst constant) { - case ConstantExpressionAst constantExpression: - value = constantExpression.Value; - break; - default: - typeName = InferTypes(kv.Item2).FirstOrDefault()?.Name; - if (typeName == null) - { - if (SafeExprEvaluator.TrySafeEval(expression, _context.ExecutionContext, out object safeValue)) - { - value = safeValue; - } - } + value = constant.Value; + } + else + { + _ = SafeExprEvaluator.TrySafeEval(expression, _context.ExecutionContext, out value); + } - break; + if (value is null) + { + AddInferredTypes(expression, name); + continue; } - } - var pstypeName = value != null ? new PSTypeName(value.GetType()) : new PSTypeName(typeName ?? "System.Object"); - properties.Add(new PSMemberNameAndType(name, pstypeName, value)); + PSTypeName valueType = new(value.GetType()); + properties.Add(new PSMemberNameAndType(name, valueType, value)); + } + else + { + AddInferredTypes(kv.Item2, name); + } } } @@ -638,7 +720,176 @@ object ICustomAstVisitor.VisitMergingRedirection(MergingRedirectionAst mergingRe object ICustomAstVisitor.VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) { - return InferTypes(binaryExpressionAst.Left); + 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() { 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 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 rhsTypes = InferTypes(binaryExpressionAst.Right).ToList(); + HashSet addedReturnTypes = new HashSet(); + List result = new List(); + 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) + { + result.AddRange(lhsTypes); + } + + return result; } object ICustomAstVisitor.VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst) @@ -655,6 +906,11 @@ object ICustomAstVisitor.VisitConvertExpression(ConvertExpressionAst convertExpr // [PSObject] @{ Key = "Value" } and the [PSCustomObject] @{ Key = "Value" } case. var type = convertExpressionAst.Type.TypeName.GetReflectionType(); + if (type is null && convertExpressionAst.Type.TypeName is TypeName unavailableType && unavailableType._typeDefinitionAst is not null) + { + return new[] { new PSTypeName(unavailableType._typeDefinitionAst) }; + } + if (type == typeof(PSObject) && convertExpressionAst.Child is HashtableAst hashtableAst) { if (InferTypes(hashtableAst).FirstOrDefault() is PSSyntheticTypeName syntheticTypeName) @@ -686,19 +942,28 @@ object ICustomAstVisitor.VisitSubExpression(SubExpressionAst subExpressionAst) object ICustomAstVisitor.VisitErrorStatement(ErrorStatementAst errorStatementAst) { var inferredTypes = new List(); - foreach (var ast in errorStatementAst.Conditions) + if (errorStatementAst.Conditions is not null) { - inferredTypes.AddRange(InferTypes(ast)); + foreach (var ast in errorStatementAst.Conditions) + { + inferredTypes.AddRange(InferTypes(ast)); + } } - foreach (var ast in errorStatementAst.Bodies) + if (errorStatementAst.Bodies is not null) { - inferredTypes.AddRange(InferTypes(ast)); + foreach (var ast in errorStatementAst.Bodies) + { + inferredTypes.AddRange(InferTypes(ast)); + } } - foreach (var ast in errorStatementAst.NestedAst) + if (errorStatementAst.NestedAst is not null) { - inferredTypes.AddRange(InferTypes(ast)); + foreach (var ast in errorStatementAst.NestedAst) + { + inferredTypes.AddRange(InferTypes(ast)); + } } return inferredTypes; @@ -749,9 +1014,20 @@ object ICustomAstVisitor.VisitParamBlock(ParamBlockAst paramBlockAst) object ICustomAstVisitor.VisitNamedBlock(NamedBlockAst namedBlockAst) { var inferredTypes = new List(); - 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)); } @@ -820,8 +1096,19 @@ object ICustomAstVisitor.VisitFunctionDefinition(FunctionDefinitionAst functionD object ICustomAstVisitor.VisitStatementBlock(StatementBlockAst statementBlockAst) { var inferredTypes = new List(); - 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)); } @@ -929,6 +1216,11 @@ object ICustomAstVisitor.VisitContinueStatement(ContinueStatementAst continueSta object ICustomAstVisitor.VisitReturnStatement(ReturnStatementAst returnStatementAst) { + if (returnStatementAst.Pipeline is null) + { + return TypeInferenceContext.EmptyPSTypeNameArray; + } + return returnStatementAst.Pipeline.Accept(this); } @@ -949,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() { new(convert.Type.TypeName) }; + } + + child = attributeChild.Child; + } + + return assignmentStatementAst.Right.Accept(this); } object ICustomAstVisitor.VisitPipeline(PipelineAst pipelineAst) @@ -980,13 +1283,111 @@ object ICustomAstVisitor.VisitFileRedirection(FileRedirectionAst fileRedirection return TypeInferenceContext.EmptyPSTypeNameArray; } - private void InferTypesFrom(CommandAst commandAst, List inferredTypes) + private void InferTypesFrom(CommandAst commandAst, List inferredTypes, bool forRedirection = false) { + if (commandAst.Redirections.Count > 0) + { + var mergedStreams = new HashSet(); + 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); - if (pseudoBinding?.CommandInfo == null) + if (pseudoBinding?.CommandInfo is null) { + var commandName = commandAst.GetCommandName(); + if (string.IsNullOrEmpty(commandName)) + { + return; + } + + try + { + var foundCommand = CommandDiscovery.LookupCommandInfo( + commandName, + CommandTypes.Application, + SearchResolutionOptions.ResolveLiteralThenPathPatterns, + CommandOrigin.Internal, + _context.ExecutionContext); + + // There's no way to know whether or not an application outputs anything + // but when they do, PowerShell will treat it as string data. + inferredTypes.Add(new PSTypeName(typeof(string))); + } + catch + { + // The command wasn't found so we can't infer anything. + } + return; } @@ -1023,10 +1424,40 @@ private void InferTypesFrom(CommandAst commandAst, List inferredType inferredTypes.AddRange(inferTypesFromObjectCmdlets); return; } + + if (cmdletInfo.ImplementingType.FullName.EqualsOrdinalIgnoreCase("Microsoft.PowerShell.Commands.GetRandomCommand") + && pseudoBinding.BoundArguments.TryGetValue("InputObject", out var value)) + { + if (value.ParameterArgumentType == AstParameterArgumentType.PipeObject) + { + InferTypesFromPreviousCommand(commandAst, inferredTypes); + } + else if (value.ParameterArgumentType == AstParameterArgumentType.AstPair) + { + inferredTypes.AddRange(InferTypes(((AstPair)value).Argument)); + } + + return; + } + } + + 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 psuedo-binding, we actually know the candidate parameter sets, so we could take + // 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 // ParameterSetName in OutputType and of the ones I know about, it isn't that useful. inferredTypes.AddRange(commandInfo.OutputType); @@ -1177,7 +1608,7 @@ private void InferTypesFromGroupCommand(PseudoBindingInfo pseudoBinding, Command properties = new[] { stringConstant.Value }; break; case ArrayLiteralAst arrayLiteral: - properties = arrayLiteral.Elements.OfType().Select(c => c.Value).ToArray(); + properties = arrayLiteral.Elements.OfType().Select(static c => c.Value).ToArray(); scriptBlockProperty = arrayLiteral.Elements.OfType().Any(); break; case CommandElementAst _: @@ -1281,7 +1712,7 @@ private void InferTypesFromPreviousCommand(CommandAst commandAst, List 0) { - inferredTypes.AddRange(InferTypes(parentPipeline.PipelineElements[i - 1])); + inferredTypes.AddRange(GetInferredEnumeratedTypes(InferTypes(parentPipeline.PipelineElements[i - 1]))); } } } @@ -1292,7 +1723,7 @@ void InferFromSelectProperties(AstParameterArgumentPair astParameterArgumentPair { if (astParameterArgumentPair is AstPair astPair) { - object ToWildCardOrString(string value) => WildcardPattern.ContainsWildcardCharacters(value) ? (object)new WildcardPattern(value) : value; + static object ToWildCardOrString(string value) => WildcardPattern.ContainsWildcardCharacters(value) ? (object)new WildcardPattern(value) : value; object[] properties = null; switch (astPair.Argument) { @@ -1300,7 +1731,7 @@ void InferFromSelectProperties(AstParameterArgumentPair astParameterArgumentPair properties = new[] { ToWildCardOrString(stringConstant.Value) }; break; case ArrayLiteralAst arrayLiteral: - properties = arrayLiteral.Elements.OfType().Select(c => ToWildCardOrString(c.Value)).ToArray(); + properties = arrayLiteral.Elements.OfType().Select(static c => ToWildCardOrString(c.Value)).ToArray(); break; } @@ -1494,8 +1925,16 @@ private IEnumerable InferTypesFrom(MemberExpressionAst memberExpress return Array.Empty(); } + bool isInvokeMemberExpressionAst = false; var res = new List(10); - bool isInvokeMemberExpressionAst = memberExpressionAst is InvokeMemberExpressionAst; + IList genericTypeArguments = null; + + if (memberExpressionAst is InvokeMemberExpressionAst invokeMemberExpression) + { + isInvokeMemberExpressionAst = true; + genericTypeArguments = invokeMemberExpression.GenericTypeArguments; + } + var maybeWantDefaultCtor = isStatic && isInvokeMemberExpressionAst && memberAsStringConst.Value.EqualsOrdinalIgnoreCase("new"); @@ -1505,14 +1944,14 @@ private IEnumerable InferTypesFrom(MemberExpressionAst memberExpress var memberNameList = new List { memberAsStringConst.Value }; foreach (var type in exprType) { - if (type.Type == typeof(PSObject)) + if (type.Type == typeof(PSObject) && type is not PSSyntheticTypeName) { continue; } var members = _context.GetMembersByInferredType(type, isStatic, filter: null); - AddTypesOfMembers(type, memberNameList, members, ref maybeWantDefaultCtor, isInvokeMemberExpressionAst, res); + AddTypesOfMembers(type, memberNameList, members, ref maybeWantDefaultCtor, isInvokeMemberExpressionAst, genericTypeArguments, res); // We didn't find any constructors but they used [T]::new() syntax if (maybeWantDefaultCtor) @@ -1524,6 +1963,36 @@ private IEnumerable InferTypesFrom(MemberExpressionAst memberExpress return res; } + private static IEnumerable 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, @@ -1533,7 +2002,7 @@ private void GetTypesOfMembers( List inferredTypes) { var memberNamesToCheck = new List { memberName }; - AddTypesOfMembers(thisType, memberNamesToCheck, members, ref maybeWantDefaultCtor, isInvokeMemberExpressionAst, inferredTypes); + AddTypesOfMembers(thisType, memberNamesToCheck, members, ref maybeWantDefaultCtor, isInvokeMemberExpressionAst, genericTypeArguments: null, inferredTypes); } private void AddTypesOfMembers( @@ -1542,6 +2011,7 @@ private void AddTypesOfMembers( IList members, ref bool maybeWantDefaultCtor, bool isInvokeMemberExpressionAst, + IList genericTypeArguments, List result) { for (int i = 0; i < memberNamesToCheck.Count; i++) @@ -1549,7 +2019,7 @@ private void AddTypesOfMembers( string memberNameToCheck = memberNamesToCheck[i]; foreach (var member in members) { - if (TryGetTypeFromMember(currentType, member, memberNameToCheck, ref maybeWantDefaultCtor, isInvokeMemberExpressionAst, result, memberNamesToCheck)) + if (TryGetTypeFromMember(currentType, member, memberNameToCheck, ref maybeWantDefaultCtor, isInvokeMemberExpressionAst, genericTypeArguments, result, memberNamesToCheck)) { break; } @@ -1563,6 +2033,7 @@ private bool TryGetTypeFromMember( string memberName, ref bool maybeWantDefaultCtor, bool isInvokeMemberExpressionAst, + IList genericTypeArguments, List result, List memberNamesToCheck) { @@ -1588,7 +2059,7 @@ private bool TryGetTypeFromMember( if (methodCacheEntry[0].method.Name.Equals(memberName, StringComparison.OrdinalIgnoreCase)) { maybeWantDefaultCtor = false; - AddTypesFromMethodCacheEntry(methodCacheEntry, result, isInvokeMemberExpressionAst); + AddTypesFromMethodCacheEntry(methodCacheEntry, genericTypeArguments, result, isInvokeMemberExpressionAst); return true; } @@ -1637,7 +2108,7 @@ private bool TryGetTypeFromMember( case PSMethod m: if (m.adapterData is DotNetAdapter.MethodCacheEntry methodCacheEntry) { - AddTypesFromMethodCacheEntry(methodCacheEntry, result, isInvokeMemberExpressionAst); + AddTypesFromMethodCacheEntry(methodCacheEntry, genericTypeArguments, result, isInvokeMemberExpressionAst); return true; } @@ -1701,21 +2172,63 @@ private bool TryGetTypeFromMember( private static void AddTypesFromMethodCacheEntry( DotNetAdapter.MethodCacheEntry methodCacheEntry, + IList genericTypeArguments, List result, bool isInvokeMemberExpressionAst) { if (isInvokeMemberExpressionAst) { - foreach (var method in methodCacheEntry.methodInformationStructures) + Type[] resolvedTypeArguments = null; + if (genericTypeArguments is not null) { - if (method.method is MethodInfo methodInfo && !methodInfo.ReturnType.ContainsGenericParameters) + resolvedTypeArguments = new Type[genericTypeArguments.Count]; + for (int i = 0; i < genericTypeArguments.Count; i++) { - result.Add(new PSTypeName(methodInfo.ReturnType)); + Type resolvedType = genericTypeArguments[i].GetReflectionType(); + if (resolvedType is null) + { + // If any generic type argument cannot be resolved yet, + // we simply assume this information is unavailable. + resolvedTypeArguments = null; + break; + } + + resolvedTypeArguments[i] = resolvedType; } } - return; - } + var tempResult = new HashSet(StringComparer.OrdinalIgnoreCase) { "System.Void" }; + foreach (var method in methodCacheEntry.methodInformationStructures) + { + if (method.method is MethodInfo methodInfo) + { + Type retType = null; + if (!methodInfo.ReturnType.ContainsGenericParameters) + { + retType = methodInfo.ReturnType; + } + else if (resolvedTypeArguments is not null) + { + try + { + retType = methodInfo.MakeGenericMethod(resolvedTypeArguments).ReturnType; + } + catch + { + // If we can't build the generic method then just skip it to retain other completion results. + continue; + } + } + + if (retType is not null && tempResult.Add(retType.FullName)) + { + result.Add(new PSTypeName(retType)); + } + } + } + + return; + } // Accessing a method as a property, we'd return a wrapper over the method. result.Add(new PSTypeName(typeof(PSMethod))); @@ -1769,245 +2282,247 @@ private void InferTypeFrom(VariableExpressionAst variableExpressionAst, List 0) + if (switchErrorStatement.Conditions?.Count > 0) { - foreach (TypeConstraintAst catchType in catchBlock.CatchTypes) + if (switchErrorStatement.Conditions[0].Extent.EndOffset < variableExpressionAst.Extent.StartOffset) { - Type exceptionType = catchType.TypeName.GetReflectionType(); - if (exceptionType != null && typeof(Exception).IsAssignableFrom(exceptionType)) - { - inferredTypes.Add(new PSTypeName(typeof(ErrorRecord<>).MakeGenericType(exceptionType))); - } + currentAst = switchErrorStatement.Conditions[0]; + break; + } + else + { + // $_ is inside the condition that is being declared, eg: Get-Process | Sort-Object -Property {switch ($_.Proc + currentAst = switchErrorStatement.Parent; + continue; } - } - else - { - inferredTypes.Add(new PSTypeName(typeof(ErrorRecord))); } - return; + break; } - - if (parent.Parent is CommandAst commandAst) + else if (currentAst is ScriptBlockExpressionAst) + { + hasSeenScriptBlock = true; + } + else if (hasSeenScriptBlock) { - // We found a command, see if there is a previous command in the pipeline. - PipelineAst pipelineAst = (PipelineAst)commandAst.Parent; - var previousCommandIndex = pipelineAst.PipelineElements.IndexOf(commandAst) - 1; - if (previousCommandIndex < 0) + if (currentAst is InvokeMemberExpressionAst invokeMember) { - return; + currentAst = invokeMember.Expression; + break; } - - foreach (var result in InferTypes(pipelineAst.PipelineElements[0])) + else if (currentAst is CommandAst cmdAst && cmdAst.Parent is PipelineAst pipeline && pipeline.PipelineElements.Count > 1) { - if (result.Type != null) + // 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) { - // Assume (because we're looking at $_ and we're inside a script block that is an - // argument to some command) that the type we're getting is actually unrolled. - // This might not be right in all cases, but with our simple analysis, it's - // right more often than it's wrong. - if (result.Type.IsArray) - { - inferredTypes.Add(new PSTypeName(result.Type.GetElementType())); - continue; - } - - if (typeof(IEnumerable).IsAssignableFrom(result.Type)) - { - // We can't deduce much from IEnumerable, but we can if it's generic. - var enumerableInterfaces = result.Type.GetInterfaces(); - foreach (var t in enumerableInterfaces) - { - if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)) - { - inferredTypes.Add(new PSTypeName(t.GetGenericArguments()[0])); - } - } - - continue; - } + currentAst = pipeline.PipelineElements[indexOfPreviousCommand]; + break; } - - inferredTypes.Add(result); } - - return; } + + currentAst = currentAst.Parent; } - } - // For certain variables, we always know their type, well at least we can assume we know. - if (astVariablePath.IsUnqualified) - { - var isThis = astVariablePath.UserPath.EqualsOrdinalIgnoreCase(SpecialVariables.This); - if (!isThis || (_context.CurrentTypeDefinitionAst == null && _context.CurrentThisType == null)) + if (currentAst is CatchClauseAst catchBlock) { - for (int i = 0; i < SpecialVariables.AutomaticVariables.Length; i++) + if (catchBlock.CatchTypes.Count > 0) { - if (!astVariablePath.UserPath.EqualsOrdinalIgnoreCase(SpecialVariables.AutomaticVariables[i])) + foreach (TypeConstraintAst catchType in catchBlock.CatchTypes) { - continue; + Type exceptionType = catchType.TypeName.GetReflectionType(); + if (typeof(Exception).IsAssignableFrom(exceptionType)) + { + inferredTypes.Add(new PSTypeName(typeof(ErrorRecord<>).MakeGenericType(exceptionType))); + } } + } - var type = SpecialVariables.AutomaticVariableTypes[i]; - if (type != typeof(object)) + // Either no type constraint was specified, or all the specified catch types were unavailable but we still know it's an error record. + if (inferredTypes.Count == 0) + { + inferredTypes.Add(new PSTypeName(typeof(ErrorRecord))); + } + } + else if (currentAst is TrapStatementAst trap) + { + if (trap.TrapType is not null) + { + Type exceptionType = trap.TrapType.TypeName.GetReflectionType(); + if (typeof(Exception).IsAssignableFrom(exceptionType)) { - inferredTypes.Add(new PSTypeName(type)); + inferredTypes.Add(new PSTypeName(typeof(ErrorRecord<>).MakeGenericType(exceptionType))); } - - break; + } + if (inferredTypes.Count == 0) + { + inferredTypes.Add(new PSTypeName(typeof(ErrorRecord))); } } - else + else if (currentAst is not null) { - var typeName = _context.CurrentThisType ?? new PSTypeName(_context.CurrentTypeDefinitionAst); - inferredTypes.Add(typeName); - return; + inferredTypes.AddRange(GetInferredEnumeratedTypes(InferTypes(currentAst))); } - } - else - { - inferredTypes.Add(new PSTypeName(_context.CurrentTypeDefinitionAst)); + 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) + // Process the well known variable $this + if (astVariablePath.IsUnqualified + && astVariablePath.UnqualifiedPath.EqualsOrdinalIgnoreCase(SpecialVariables.This) + && (_context.CurrentTypeDefinitionAst is not null || _context.CurrentThisType is not null)) { - parent = parent.Parent; + // $this is special in script properties and in PowerShell classes + PSTypeName typeName = _context.CurrentThisType ?? new PSTypeName(_context.CurrentTypeDefinitionAst); + inferredTypes.Add(typeName); + return; } - if (parent?.Parent is FunctionDefinitionAst) + // Process other well known variables like $true and $pshome + if (SpecialVariables.AllScopeVariables.TryGetValue(astVariablePath.UnqualifiedPath, out Type knownType)) { - parent = parent.Parent; - } - - int startOffset = variableExpressionAst.Extent.StartOffset; - var targetAsts = (List)AstSearcher.FindAll( - parent, - ast => + if (knownType == typeof(object)) { - if (ast is ParameterAst || ast is AssignmentStatementAst || ast is CommandAst) + if (_context.TryGetRepresentativeTypeNameFromExpressionSafeEval(variableExpressionAst, out var psType)) { - return variableExpressionAst.AstAssignsToSameVariable(ast) - && ast.Extent.EndOffset < startOffset; - } - - if (ast is ForEachStatementAst) - { - return variableExpressionAst.AstAssignsToSameVariable(ast) - && ast.Extent.StartOffset < startOffset; + inferredTypes.Add(psType); } + } + else + { + inferredTypes.Add(new PSTypeName(knownType)); + } - return false; - }, - searchNestedScriptBlocks: true); + return; + } - foreach (var ast in targetAsts) + // Process automatic variables like $MyInvocation and $PSBoundParameters + for (int i = 0; i < SpecialVariables.AutomaticVariables.Length; i++) { - if (ast is ParameterAst parameterAst) + if (!astVariablePath.UnqualifiedPath.EqualsOrdinalIgnoreCase(SpecialVariables.AutomaticVariables[i])) { - var currentCount = inferredTypes.Count; - inferredTypes.AddRange(InferTypes(parameterAst)); + continue; + } - if (inferredTypes.Count != currentCount) - { - return; - } + Type type = SpecialVariables.AutomaticVariableTypes[i]; + if (type != typeof(object)) + { + inferredTypes.Add(new PSTypeName(type)); } - } - var assignAsts = targetAsts.OfType().ToArray(); + return; + } - // If any of the assignments lhs use a type constraint, then we use that. - // Otherwise, we use the rhs of the "nearest" assignment - foreach (var assignAst in assignAsts) + // 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() { - if (assignAst.Left is ConvertExpressionAst lhsConvert) + ScopeIsLocal = true, + LocalScopeOnly = variableExpressionAst.VariablePath.IsLocal || variableExpressionAst.VariablePath.IsPrivate, + StopSearchOffset = variableExpressionAst.Extent.StartOffset, + VariableTarget = variableExpressionAst + }; + while (currentAst is not null) + { + if (currentAst is IParameterMetadataProvider) { - inferredTypes.Add(new PSTypeName(lhsConvert.Type.TypeName)); - return; + if (currentAst is ScriptBlockAst && currentAst.Parent is FunctionDefinitionAst) + { + // 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; + } + + assignmentVisitor.ScopeDefinitionAst = currentAst; + currentAst.Visit(assignmentVisitor); + + 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()))) + { + // 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; + } + + assignmentVisitor.ScopeIsLocal = false; + assignmentVisitor.StopSearchOffset = currentAst.Extent.StartOffset; } - } - var foreachAst = targetAsts.OfType().FirstOrDefault(); - if (foreachAst != null) - { - inferredTypes.AddRange( - GetInferredEnumeratedTypes(InferTypes(foreachAst.Condition))); - return; + currentAst = currentAst.Parent; } - var commandCompletionAst = targetAsts.OfType().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)) @@ -2193,6 +2708,16 @@ private IEnumerable InferTypeFrom(IndexExpressionAst indexExpression bool foundAny = false; foreach (var psType in targetTypes) { + if (psType is PSSyntheticTypeName syntheticType) + { + foreach (var member in syntheticType.Members) + { + yield return member.PSTypeName; + } + + continue; + } + var type = psType.Type; if (type != null) { @@ -2202,6 +2727,17 @@ private IEnumerable InferTypeFrom(IndexExpressionAst indexExpression continue; } + + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IList<>)) + { + var valueType = type.GetGenericArguments()[0]; + if (!valueType.ContainsGenericParameters) + { + foundAny = true; + yield return new PSTypeName(valueType); + } + continue; + } foreach (var iface in type.GetInterfaces()) { @@ -2257,7 +2793,7 @@ private IEnumerable InferTypeFrom(IndexExpressionAst indexExpression /// The potentially enumerable types to infer enumerated type from. /// /// The enumerated item types. - private static IEnumerable GetInferredEnumeratedTypes(IEnumerable enumerableTypes) + internal static IEnumerable GetInferredEnumeratedTypes(IEnumerable enumerableTypes) { foreach (PSTypeName maybeEnumerableType in enumerableTypes) { @@ -2332,7 +2868,7 @@ object ICustomAstVisitor2.VisitPipelineChain(PipelineChainAst pipelineChainAst) var types = new List(); types.AddRange(InferTypes(pipelineChainAst.LhsPipelineChain)); types.AddRange(InferTypes(pipelineChainAst.RhsPipeline)); - return GetArrayType(types); + return types.Distinct(); } private static CommandBaseAst GetPreviousPipelineCommand(CommandAst commandAst) @@ -2341,93 +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 + { + /// + /// If set, we only look for local/private assignments in the scope of the variable we are inferring. + /// + internal bool LocalScopeOnly; + + /// + /// The current scope is local to the variable that is being inferred. + /// + internal bool ScopeIsLocal; + + /// + /// The variable that we are trying to determine the type of. + /// + internal VariableExpressionAst VariableTarget; + + /// + /// The last type constraint applied to the variable. This takes priority when determining the type of the variable. + /// + internal ITypeName LastConstraint; + + /// + /// The last ast that assigned a value to the variable. This determines the value of the variable unless a type constraint has been applied. + /// + internal Ast LastAssignment; + + /// + /// The inferred type from the most recent assignment. This is only used for stream redirections to variables, or the special OutVariable common parameters. + /// + internal PSTypeName LastAssignmentType; + + /// + /// Whether or not the types from the last assignment should be enumerated. + /// For assignments made by the PipelineVariable parameter or the foreach statement. + /// + internal bool EnumerateAssignment; + + /// + /// Whether or not the last assignment was via command redirection. + /// + internal bool RedirectionAssignment; + + /// + /// The Ast of the scope we are currently analyzing. + /// + 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{$_.} 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.) 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 GetGetterProperty(this Type type, string propertyName) - { - var res = new List(); - 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; - if (name.Length == propertyName.Length + 4 - && name.StartsWith("get_") - && propertyName.IndexOf(name, 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); + 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. + // } + // 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)), commandAst.Extent); + break; + + case "WarningVariable": + case "wv": + SetLastAssignmentType(new PSTypeName(typeof(List)), commandAst.Extent); + break; + + case "InformationVariable": + case "iv": + SetLastAssignmentType(new PSTypeName(typeof(List)), 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; + } + + public override AstVisitAction VisitAttribute(AttributeAst attributeAst) + { + // Attributes can't assign values to variables so they aren't interesting. + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst) + { + 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; } - var candidateVarPath = varExpr.VariablePath; - if (candidateVarPath.UserPath.Equals(variableAstVariablePath.UserPath, StringComparison.OrdinalIgnoreCase)) + public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { - return true; + 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); + } - // 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 static IEnumerable GetGetterProperty(this Type type, string propertyName) + { + var res = new List(); + foreach (var m in type.GetMethods(BindingFlags.Public | BindingFlags.Instance)) { - return true; + 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 8843ff5cd31..72258a4f459 100644 --- a/src/System.Management.Automation/engine/parser/TypeResolver.cs +++ b/src/System.Management.Automation/engine/parser/TypeResolver.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Specialized; #if !UNIX using System.DirectoryServices; #endif @@ -79,7 +80,10 @@ private static Type LookForTypeInAssemblies(TypeName typeName, foreach (Assembly assembly in assemblies) { // Skip the assemblies that we already searched and found no matching type. - if (searchedAssemblies.Contains(assembly)) { continue; } + if (searchedAssemblies.Contains(assembly)) + { + continue; + } try { @@ -370,10 +374,7 @@ internal static Type ResolveTypeNameWithContext(TypeName typeName, out Exception return typeName._typeDefinitionAst.Type; } - if (context == null) - { - context = LocalPipeline.GetExecutionContextFromTLS(); - } + context ??= LocalPipeline.GetExecutionContextFromTLS(); // Use the explicitly passed-in assembly list when it's specified by the caller. // Otherwise, retrieve all currently loaded assemblies. @@ -582,10 +583,7 @@ private TypeResolutionState(TypeResolutionState other, HashSet typesDefi internal static TypeResolutionState GetDefaultUsingState(ExecutionContext context) { - if (context == null) - { - context = LocalPipeline.GetExecutionContextFromTLS(); - } + context ??= LocalPipeline.GetExecutionContextFromTLS(); if (context != null) { @@ -673,7 +671,7 @@ public override int GetHashCode() internal static class TypeCache { - private class KeyComparer : IEqualityComparer> + private sealed class KeyComparer : IEqualityComparer> { public bool Equals(Tuple x, Tuple y) @@ -755,10 +753,12 @@ internal static class CoreTypes { typeof(CimConverter), new[] { "cimconverter" } }, { typeof(ModuleSpecification), null }, { typeof(IPEndPoint), new[] { "IPEndpoint" } }, + { typeof(NoRunspaceAffinityAttribute), new[] { "NoRunspaceAffinity" } }, { typeof(NullString), new[] { "NullString" } }, { typeof(OutputTypeAttribute), new[] { "OutputType" } }, { typeof(object[]), null }, { typeof(ObjectSecurity), new[] { "ObjectSecurity" } }, + { typeof(OrderedDictionary), new[] { "ordered" } }, { typeof(ParameterAttribute), new[] { "Parameter" } }, { typeof(PhysicalAddress), new[] { "PhysicalAddress" } }, { typeof(PSCredential), new[] { "pscredential" } }, @@ -787,6 +787,7 @@ internal static class CoreTypes { typeof(ValidateLengthAttribute), new[] { "ValidateLength" } }, { typeof(ValidateNotNullAttribute), new[] { "ValidateNotNull" } }, { typeof(ValidateNotNullOrEmptyAttribute), new[] { "ValidateNotNullOrEmpty" } }, + { typeof(ValidateNotNullOrWhiteSpaceAttribute), new[] { "ValidateNotNullOrWhiteSpace" } }, { typeof(ValidatePatternAttribute), new[] { "ValidatePattern" } }, { typeof(ValidateRangeAttribute), new[] { "ValidateRange" } }, { typeof(ValidateScriptAttribute), new[] { "ValidateScript" } }, @@ -933,10 +934,7 @@ public static void Add(string typeName, Type type) public static bool Remove(string typeName) { userTypeAccelerators.Remove(typeName); - if (s_allTypeAccelerators != null) - { - s_allTypeAccelerators.Remove(typeName); - } + s_allTypeAccelerators?.Remove(typeName); return true; } diff --git a/src/System.Management.Automation/engine/parser/VariableAnalysis.cs b/src/System.Management.Automation/engine/parser/VariableAnalysis.cs index 4cc8e98a2c0..8bf14d79aac 100644 --- a/src/System.Management.Automation/engine/parser/VariableAnalysis.cs +++ b/src/System.Management.Automation/engine/parser/VariableAnalysis.cs @@ -41,7 +41,7 @@ internal VariableAnalysisDetails() public List AssociatedAsts { get; } } - internal class FindAllVariablesVisitor : AstVisitor + internal sealed class FindAllVariablesVisitor : AstVisitor { private static readonly HashSet s_hashOfPessimizingCmdlets = new HashSet(StringComparer.OrdinalIgnoreCase); @@ -110,7 +110,7 @@ internal static Dictionary Visit(IParameterMeta visitor.VisitParameters(ast.Parameters); } - localsAllocated = visitor._variables.Count(details => details.Value.LocalTupleIndex != VariableAnalysis.Unanalyzed); + localsAllocated = visitor._variables.Count(static details => details.Value.LocalTupleIndex != VariableAnalysis.Unanalyzed); return visitor._variables; } @@ -185,8 +185,7 @@ private void VisitParameters(ReadOnlyCollection parameters) // valuetype because the parameter has no value yet. For example: // & { param([System.Reflection.MemberTypes]$m) ($null -eq $m) } - object unused; - if (!Compiler.TryGetDefaultParameterValue(analysisDetails.Type, out unused)) + if (!Compiler.TryGetDefaultParameterValue(analysisDetails.Type, out _)) { analysisDetails.LocalTupleIndex = VariableAnalysis.ForceDynamic; } @@ -338,7 +337,7 @@ internal class VariableAnalysis : ICustomAstVisitor2 // in these cases, we rely on the setter PSVariable.Value to handle those attributes. internal const int ForceDynamic = -2; - private class LoopGotoTargets + private sealed class LoopGotoTargets { internal LoopGotoTargets(string label, Block breakTarget, Block continueTarget) { @@ -354,7 +353,7 @@ internal LoopGotoTargets(string label, Block breakTarget, Block continueTarget) internal Block ContinueTarget { get; } } - private class Block + private sealed class Block { internal readonly List _asts = new List(); private readonly List _successors = new List(); @@ -439,7 +438,7 @@ private static void VisitDepthFirstOrder(Block block, List visitData) } } - private class AssignmentTarget : Ast + private sealed class AssignmentTarget : Ast { internal readonly ExpressionAst _targetAst; internal readonly string _variableName; @@ -502,7 +501,7 @@ internal static void NoteAllScopeVariable(string variableName) internal static bool AnyVariablesCouldBeAllScope(Dictionary variableNames) { - return variableNames.Any(keyValuePair => s_allScopeVariables.ContainsKey(keyValuePair.Key)); + return variableNames.Any(static keyValuePair => s_allScopeVariables.ContainsKey(keyValuePair.Key)); } private Dictionary _variables; @@ -581,7 +580,7 @@ internal static bool AnalyzeMemberFunction(FunctionMemberAst ast) { VariableAnalysis va = (new VariableAnalysis()); va.AnalyzeImpl(ast, false, false); - return va._exitBlock._predecessors.All(b => b._returns || b._throws || b._unreachable); + return va._exitBlock._predecessors.All(static b => b._returns || b._throws || b._unreachable); } private Tuple> AnalyzeImpl(IParameterMetadataProvider ast, bool disableOptimizations, bool scriptCmdlet) @@ -945,25 +944,11 @@ public object VisitScriptBlock(ScriptBlockAst scriptBlockAst) { _currentBlock = _entryBlock; - if (scriptBlockAst.DynamicParamBlock != null) - { - scriptBlockAst.DynamicParamBlock.Accept(this); - } - - if (scriptBlockAst.BeginBlock != null) - { - scriptBlockAst.BeginBlock.Accept(this); - } - - if (scriptBlockAst.ProcessBlock != null) - { - scriptBlockAst.ProcessBlock.Accept(this); - } - - if (scriptBlockAst.EndBlock != null) - { - scriptBlockAst.EndBlock.Accept(this); - } + scriptBlockAst.DynamicParamBlock?.Accept(this); + scriptBlockAst.BeginBlock?.Accept(this); + scriptBlockAst.ProcessBlock?.Accept(this); + scriptBlockAst.EndBlock?.Accept(this); + scriptBlockAst.CleanBlock?.Accept(this); _currentBlock.FlowsTo(_exitBlock); @@ -1317,10 +1302,7 @@ public object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) public object VisitForStatement(ForStatementAst forStatementAst) { - if (forStatementAst.Initializer != null) - { - forStatementAst.Initializer.Accept(this); - } + forStatementAst.Initializer?.Accept(this); var generateCondition = forStatementAst.Condition != null ? () => forStatementAst.Condition.Accept(this) @@ -1456,22 +1438,19 @@ where t.Label.Equals(labelStrAst.Value, StringComparison.OrdinalIgnoreCase) public object VisitBreakStatement(BreakStatementAst breakStatementAst) { - BreakOrContinue(breakStatementAst.Label, t => t.BreakTarget); + BreakOrContinue(breakStatementAst.Label, static t => t.BreakTarget); return null; } public object VisitContinueStatement(ContinueStatementAst continueStatementAst) { - BreakOrContinue(continueStatementAst.Label, t => t.ContinueTarget); + BreakOrContinue(continueStatementAst.Label, static t => t.ContinueTarget); return null; } private Block ControlFlowStatement(PipelineBaseAst pipelineAst) { - if (pipelineAst != null) - { - pipelineAst.Accept(this); - } + pipelineAst?.Accept(this); _currentBlock.FlowsTo(_exitBlock); var lastBlockInStatement = _currentBlock; @@ -1642,11 +1621,7 @@ public object VisitCommandExpression(CommandExpressionAst commandExpressionAst) public object VisitCommandParameter(CommandParameterAst commandParameterAst) { - if (commandParameterAst.Argument != null) - { - commandParameterAst.Argument.Accept(this); - } - + commandParameterAst.Argument?.Accept(this); return null; } diff --git a/src/System.Management.Automation/engine/parser/ast.cs b/src/System.Management.Automation/engine/parser/ast.cs index 56693513b75..0325cf94aeb 100644 --- a/src/System.Management.Automation/engine/parser/ast.cs +++ b/src/System.Management.Automation/engine/parser/ast.cs @@ -36,8 +36,6 @@ internal interface ISupportsAssignment IAssignableValue GetAssignableValue(); } -#nullable restore - internal interface IAssignableValue { /// @@ -45,7 +43,7 @@ internal interface IAssignableValue /// It returns the expressions that holds the value of the ast. It may append the exprs or temps lists if the return /// value relies on temps and other expressions. /// - Expression GetValue(Compiler compiler, List exprs, List temps); + Expression? GetValue(Compiler compiler, List exprs, List temps); /// /// SetValue is called to set the result of an assignment (=) or to write back the result of @@ -53,6 +51,7 @@ internal interface IAssignableValue /// Expression SetValue(Compiler compiler, Expression rhs); } +#nullable restore internal interface IParameterMetadataProvider { @@ -321,20 +320,20 @@ internal bool IsInWorkflow() while (current != null && !stopScanning) { - ScriptBlockAst scriptBlock = current as ScriptBlockAst; - if (scriptBlock != null) + if (current is ScriptBlockAst scriptBlock) { // See if this uses the workflow keyword - FunctionDefinitionAst functionDefinition = scriptBlock.Parent as FunctionDefinitionAst; - if ((functionDefinition != null)) + if (scriptBlock.Parent is FunctionDefinitionAst functionDefinition) { stopScanning = true; - if (functionDefinition.IsWorkflow) { return true; } + if (functionDefinition.IsWorkflow) + { + return true; + } } } - CommandAst commandAst = current as CommandAst; - if (commandAst != null && + if (current is CommandAst commandAst && string.Equals(TokenKind.InlineScript.Text(), commandAst.GetCommandName(), StringComparison.OrdinalIgnoreCase) && this != commandAst) { @@ -389,8 +388,7 @@ internal static TypeDefinitionAst GetAncestorTypeDefinitionAst(Ast ast) // Nested function isn't really a member of the type so stop looking // Anonymous script blocks are though - var functionDefinitionAst = ast as FunctionDefinitionAst; - if (functionDefinitionAst != null && functionDefinitionAst.Parent is not FunctionMemberAst) + if (ast is FunctionDefinitionAst functionDefinitionAst && functionDefinitionAst.Parent is not FunctionMemberAst) break; ast = ast.Parent; } @@ -522,7 +520,7 @@ internal ErrorStatementAst(IScriptExtent extent, Token kind, IEnumerable - /// Indicate the kind of the ErrorStatement. e.g. Kind == Switch means that this error statment is generated + /// Indicate the kind of the ErrorStatement. e.g. Kind == Switch means that this error statement is generated /// when parsing a switch statement. /// public Token Kind { get; } @@ -755,14 +753,6 @@ public class ScriptRequirements /// public ReadOnlyCollection RequiredModules { get; internal set; } - /// - /// The snapins this script requires, specified like: - /// #requires -PSSnapin Snapin - /// #requires -PSSnapin Snapin -Version 2 - /// If no snapins are required, this property is an empty collection. - /// - public ReadOnlyCollection RequiresPSSnapIns { get; internal set; } - /// /// The assemblies this script requires, specified like: /// #requires -Assembly path\to\foo.dll @@ -800,7 +790,7 @@ public class ScriptBlockAst : Ast, IParameterMetadataProvider /// Construct a ScriptBlockAst that uses explicitly named begin/process/end blocks. /// /// The extent of the script block. - /// The list of using statments, may be null. + /// The list of using statements, may be null. /// The set of attributes for the script block. /// The ast for the param block, may be null. /// The ast for the begin block, may be null. @@ -819,6 +809,46 @@ public ScriptBlockAst(IScriptExtent extent, NamedBlockAst processBlock, NamedBlockAst endBlock, NamedBlockAst dynamicParamBlock) + : this( + extent, + usingStatements, + attributes, + paramBlock, + beginBlock, + processBlock, + endBlock, + cleanBlock: null, + dynamicParamBlock) + { + } + + /// + /// Initializes a new instance of the class. + /// This construction uses explicitly named begin/process/end/clean blocks. + /// + /// The extent of the script block. + /// The list of using statements, may be null. + /// The set of attributes for the script block. + /// The ast for the param block, may be null. + /// The ast for the begin block, may be null. + /// The ast for the process block, may be null. + /// The ast for the end block, may be null. + /// The ast for the clean block, may be null. + /// The ast for the dynamicparam block, may be null. + /// + /// If is null. + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "param")] + public ScriptBlockAst( + IScriptExtent extent, + IEnumerable usingStatements, + IEnumerable attributes, + ParamBlockAst paramBlock, + NamedBlockAst beginBlock, + NamedBlockAst processBlock, + NamedBlockAst endBlock, + NamedBlockAst cleanBlock, + NamedBlockAst dynamicParamBlock) : base(extent) { SetUsingStatements(usingStatements); @@ -857,6 +887,12 @@ public ScriptBlockAst(IScriptExtent extent, SetParent(endBlock); } + if (cleanBlock != null) + { + this.CleanBlock = cleanBlock; + SetParent(cleanBlock); + } + if (dynamicParamBlock != null) { this.DynamicParamBlock = dynamicParamBlock; @@ -868,7 +904,7 @@ public ScriptBlockAst(IScriptExtent extent, /// Construct a ScriptBlockAst that uses explicitly named begin/process/end blocks. /// /// The extent of the script block. - /// The list of using statments, may be null. + /// The list of using statements, may be null. /// The ast for the param block, may be null. /// The ast for the begin block, may be null. /// The ast for the process block, may be null. @@ -889,6 +925,35 @@ public ScriptBlockAst(IScriptExtent extent, { } + /// + /// Initializes a new instance of the class. + /// This construction uses explicitly named begin/process/end/clean blocks. + /// + /// The extent of the script block. + /// The list of using statements, may be null. + /// The ast for the param block, may be null. + /// The ast for the begin block, may be null. + /// The ast for the process block, may be null. + /// The ast for the end block, may be null. + /// The ast for the clean block, may be null. + /// The ast for the dynamicparam block, may be null. + /// + /// If is null. + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "param")] + public ScriptBlockAst( + IScriptExtent extent, + IEnumerable usingStatements, + ParamBlockAst paramBlock, + NamedBlockAst beginBlock, + NamedBlockAst processBlock, + NamedBlockAst endBlock, + NamedBlockAst cleanBlock, + NamedBlockAst dynamicParamBlock) + : this(extent, usingStatements, null, paramBlock, beginBlock, processBlock, endBlock, cleanBlock, dynamicParamBlock) + { + } + /// /// Construct a ScriptBlockAst that uses explicitly named begin/process/end blocks. /// @@ -912,11 +977,38 @@ public ScriptBlockAst(IScriptExtent extent, { } + /// + /// Initializes a new instance of the class. + /// This construction uses explicitly named begin/process/end/clean blocks. + /// + /// The extent of the script block. + /// The ast for the param block, may be null. + /// The ast for the begin block, may be null. + /// The ast for the process block, may be null. + /// The ast for the end block, may be null. + /// The ast for the clean block, may be null. + /// The ast for the dynamicparam block, may be null. + /// + /// If is null. + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "param")] + public ScriptBlockAst( + IScriptExtent extent, + ParamBlockAst paramBlock, + NamedBlockAst beginBlock, + NamedBlockAst processBlock, + NamedBlockAst endBlock, + NamedBlockAst cleanBlock, + NamedBlockAst dynamicParamBlock) + : this(extent, null, paramBlock, beginBlock, processBlock, endBlock, cleanBlock, dynamicParamBlock) + { + } + /// /// Construct a ScriptBlockAst that does not use explicitly named blocks. /// /// The extent of the script block. - /// The list of using statments, may be null. + /// The list of using statements, may be null. /// The ast for the param block, may be null. /// /// The statements that go in the end block if is false, or the @@ -975,7 +1067,7 @@ public ScriptBlockAst(IScriptExtent extent, ParamBlockAst paramBlock, StatementB /// Construct a ScriptBlockAst that does not use explicitly named blocks. /// /// The extent of the script block. - /// The list of using statments, may be null. + /// The list of using statements, may be null. /// The ast for the param block, may be null. /// /// The statements that go in the end block if is false, or the @@ -1017,7 +1109,7 @@ public ScriptBlockAst(IScriptExtent extent, IEnumerable attributes /// Construct a ScriptBlockAst that does not use explicitly named blocks. /// /// The extent of the script block. - /// The list of using statments, may be null. + /// The list of using statements, may be null. /// The attributes for the script block. /// The ast for the param block, may be null. /// @@ -1116,6 +1208,11 @@ private void SetUsingStatements(IEnumerable usingStatements) /// public NamedBlockAst EndBlock { get; } + /// + /// Gets the ast representing the clean block for a script block, or null if no clean block was specified. + /// + public NamedBlockAst CleanBlock { get; } + /// /// The ast representing the dynamicparam block for a script block, or null if no dynamicparam block was specified. /// @@ -1195,17 +1292,25 @@ public override Ast Copy() var newBeginBlock = CopyElement(this.BeginBlock); var newProcessBlock = CopyElement(this.ProcessBlock); var newEndBlock = CopyElement(this.EndBlock); + var newCleanBlock = CopyElement(this.CleanBlock); var newDynamicParamBlock = CopyElement(this.DynamicParamBlock); var newAttributes = CopyElements(this.Attributes); var newUsingStatements = CopyElements(this.UsingStatements); - var scriptBlockAst = new ScriptBlockAst(this.Extent, newUsingStatements, newAttributes, newParamBlock, newBeginBlock, newProcessBlock, - newEndBlock, newDynamicParamBlock) + return new ScriptBlockAst( + this.Extent, + newUsingStatements, + newAttributes, + newParamBlock, + newBeginBlock, + newProcessBlock, + newEndBlock, + newCleanBlock, + newDynamicParamBlock) { IsConfiguration = this.IsConfiguration, ScriptRequirements = this.ScriptRequirements }; - return scriptBlockAst; } internal string ToStringForSerialization() @@ -1249,7 +1354,7 @@ internal string ToStringForSerialization(Tuple, stri string script = this.ToString(); var newScript = new StringBuilder(); - foreach (var ast in astElements.OrderBy(ast => ast.Extent.StartOffset)) + foreach (var ast in astElements.OrderBy(static ast => ast.Extent.StartOffset)) { int astStartOffset = ast.Extent.StartOffset - indexOffset; int astEndOffset = ast.Extent.EndOffset - indexOffset; @@ -1259,10 +1364,10 @@ internal string ToStringForSerialization(Tuple, stri // We are done processing the section that we care about if (astStartOffset >= endOffset) { break; } - var varAst = ast as VariableExpressionAst; - if (varAst != null) + if (ast is VariableExpressionAst varAst) { - string varName = varAst.VariablePath.UserPath; + VariablePath varPath = varAst.VariablePath; + string varName = varPath.IsDriveQualified ? $"{varPath.DriveName}_{varPath.UnqualifiedPath}" : $"{varPath.UnqualifiedPath}"; string varSign = varAst.Splatted ? "@" : "$"; string newVarName = varSign + UsingExpressionAst.UsingPrefix + varName; @@ -1339,8 +1444,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) if (action == AstVisitAction.SkipChildren) return visitor.CheckForPostAction(this, AstVisitAction.Continue); - var visitor2 = visitor as AstVisitor2; - if (visitor2 != null) + if (visitor is AstVisitor2 visitor2) { if (action == AstVisitAction.Continue) { @@ -1367,17 +1471,27 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) } } - if (action == AstVisitAction.Continue && ParamBlock != null) - action = ParamBlock.InternalVisit(visitor); - if (action == AstVisitAction.Continue && DynamicParamBlock != null) - action = DynamicParamBlock.InternalVisit(visitor); - if (action == AstVisitAction.Continue && BeginBlock != null) - action = BeginBlock.InternalVisit(visitor); - if (action == AstVisitAction.Continue && ProcessBlock != null) - action = ProcessBlock.InternalVisit(visitor); - if (action == AstVisitAction.Continue && EndBlock != null) - action = EndBlock.InternalVisit(visitor); + if (action == AstVisitAction.Continue) + { + _ = VisitAndShallContinue(ParamBlock) && + VisitAndShallContinue(DynamicParamBlock) && + VisitAndShallContinue(BeginBlock) && + VisitAndShallContinue(ProcessBlock) && + VisitAndShallContinue(EndBlock) && + VisitAndShallContinue(CleanBlock); + } + return visitor.CheckForPostAction(this, action); + + bool VisitAndShallContinue(Ast ast) + { + if (ast is not null) + { + action = ast.InternalVisit(visitor); + } + + return action == AstVisitAction.Continue; + } } #endregion Visitors @@ -1514,9 +1628,7 @@ Tuple IParameterMetadataProvider.GetWithInputHandlingForInvokeCo private string GetWithInputHandlingForInvokeCommandImpl(Tuple, string> usingVariablesTuple) { // do not add "$input |" to complex pipelines - string unused1; - string unused2; - var pipelineAst = GetSimplePipeline(false, out unused1, out unused2); + var pipelineAst = GetSimplePipeline(false, out _, out _); if (pipelineAst == null) { return (usingVariablesTuple == null) @@ -1567,7 +1679,7 @@ bool IParameterMetadataProvider.UsesCmdletBinding() if (ParamBlock != null) { - usesCmdletBinding = this.ParamBlock.Attributes.Any(attribute => typeof(CmdletBindingAttribute) == attribute.TypeName.GetReflectionAttributeType()); + usesCmdletBinding = this.ParamBlock.Attributes.Any(static attribute => typeof(CmdletBindingAttribute) == attribute.TypeName.GetReflectionAttributeType()); if (!usesCmdletBinding) { usesCmdletBinding = ParamBlockAst.UsesCmdletBinding(ParamBlock.Parameters); @@ -1581,9 +1693,12 @@ bool IParameterMetadataProvider.UsesCmdletBinding() internal PipelineAst GetSimplePipeline(bool allowMultiplePipelines, out string errorId, out string errorMsg) { - if (BeginBlock != null || ProcessBlock != null || DynamicParamBlock != null) + if (BeginBlock != null + || ProcessBlock != null + || CleanBlock != null + || DynamicParamBlock != null) { - errorId = "CanConvertOneClauseOnly"; + errorId = nameof(AutomationExceptions.CanConvertOneClauseOnly); errorMsg = AutomationExceptions.CanConvertOneClauseOnly; return null; } @@ -1749,7 +1864,7 @@ internal static bool UsesCmdletBinding(IEnumerable parameters) public class NamedBlockAst : Ast { /// - /// Construct the ast for a begin, process, end, or dynamic param block. + /// Construct the ast for a begin, process, end, clean, or dynamic param block. /// /// /// The extent of the block. If is false, the extent includes @@ -1761,6 +1876,7 @@ public class NamedBlockAst : Ast /// /// /// + /// /// /// /// @@ -1779,8 +1895,7 @@ public NamedBlockAst(IScriptExtent extent, TokenKind blockName, StatementBlockAs { // Validate the block name. If the block is unnamed, it must be an End block (for a function) // or Process block (for a filter). - if (!blockName.HasTrait(TokenFlags.ScriptBlockBlockName) - || (unnamed && (blockName == TokenKind.Begin || blockName == TokenKind.Dynamicparam))) + if (HasInvalidBlockName(blockName, unnamed)) { throw PSTraceSource.NewArgumentException(nameof(blockName)); } @@ -1817,8 +1932,7 @@ public NamedBlockAst(IScriptExtent extent, TokenKind blockName, StatementBlockAs if (!unnamed) { - var statementsExtent = statementBlock.Extent as InternalScriptExtent; - if (statementsExtent != null) + if (statementBlock.Extent is InternalScriptExtent statementsExtent) { this.OpenCurlyExtent = new InternalScriptExtent(statementsExtent.PositionHelper, statementsExtent.StartOffset, statementsExtent.StartOffset + 1); this.CloseCurlyExtent = new InternalScriptExtent(statementsExtent.PositionHelper, statementsExtent.EndOffset - 1, statementsExtent.EndOffset); @@ -1838,6 +1952,7 @@ public NamedBlockAst(IScriptExtent extent, TokenKind blockName, StatementBlockAs /// /// /// + /// /// /// /// @@ -1877,6 +1992,14 @@ public override Ast Copy() return new NamedBlockAst(this.Extent, this.BlockKind, statementBlock, this.Unnamed); } + private static bool HasInvalidBlockName(TokenKind blockName, bool unnamed) + { + return !blockName.HasTrait(TokenFlags.ScriptBlockBlockName) + || (unnamed + && blockName != TokenKind.Process + && blockName != TokenKind.End); + } + // Used by the debugger for command breakpoints internal IScriptExtent OpenCurlyExtent { get; } @@ -2330,7 +2453,8 @@ internal string GetParamTextWithDollarUsingHandling(IEnumerator= endOffset) { break; } - string varName = varAst.VariablePath.UserPath; + VariablePath varPath = varAst.VariablePath; + string varName = varPath.IsDriveQualified ? $"{varPath.DriveName}_{varPath.UnqualifiedPath}" : $"{varPath.UnqualifiedPath}"; string varSign = varAst.Splatted ? "@" : "$"; string newVarName = varSign + UsingExpressionAst.UsingPrefix + varName; @@ -2684,8 +2808,7 @@ internal override object Accept(ICustomAstVisitor visitor) internal override AstVisitAction InternalVisit(AstVisitor visitor) { var action = AstVisitAction.Continue; - var visitor2 = visitor as AstVisitor2; - if (visitor2 != null) + if (visitor is AstVisitor2 visitor2) { action = visitor2.VisitTypeDefinition(this); if (action == AstVisitAction.SkipChildren) @@ -2932,8 +3055,7 @@ internal override object Accept(ICustomAstVisitor visitor) internal override AstVisitAction InternalVisit(AstVisitor visitor) { var action = AstVisitAction.Continue; - var visitor2 = visitor as AstVisitor2; - if (visitor2 != null) + if (visitor is AstVisitor2 visitor2) { action = visitor2.VisitUsingStatement(this); if (action != AstVisitAction.Continue) @@ -3160,8 +3282,7 @@ internal override object Accept(ICustomAstVisitor visitor) internal override AstVisitAction InternalVisit(AstVisitor visitor) { var action = AstVisitAction.Continue; - var visitor2 = visitor as AstVisitor2; - if (visitor2 != null) + if (visitor is AstVisitor2 visitor2) { action = visitor2.VisitPropertyMember(this); if (action == AstVisitAction.SkipChildren) @@ -3176,7 +3297,10 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) { var attributeAst = Attributes[index]; action = attributeAst.InternalVisit(visitor); - if (action != AstVisitAction.Continue) break; + if (action != AstVisitAction.Continue) + { + break; + } } } @@ -3331,6 +3455,8 @@ public bool IsConstructor internal IScriptExtent NameExtent { get { return _functionDefinitionAst.NameExtent; } } + private string _toolTip; + /// /// Copy a function member ast. /// @@ -3345,28 +3471,56 @@ public override Ast Copy() internal override string GetTooltip() { - var sb = new StringBuilder(); - if (IsStatic) + if (!string.IsNullOrEmpty(_toolTip)) { - sb.Append("static "); + return _toolTip; } - sb.Append(IsReturnTypeVoid() ? "void" : ReturnType.TypeName.FullName); - sb.Append(' '); - sb.Append(Name); - sb.Append('('); - for (int i = 0; i < Parameters.Count; i++) + var sb = new StringBuilder(); + var classMembers = ((TypeDefinitionAst)Parent).Members; + for (int i = 0; i < classMembers.Count; i++) { - if (i > 0) + var methodMember = classMembers[i] as FunctionMemberAst; + if (methodMember is null || + !Name.Equals(methodMember.Name) || + IsStatic != methodMember.IsStatic) + { + continue; + } + + if (sb.Length > 0) { - sb.Append(", "); + sb.AppendLine(); } - sb.Append(Parameters[i].GetTooltip()); + if (methodMember.IsStatic) + { + sb.Append("static "); + } + + if (!methodMember.IsConstructor) + { + sb.Append(methodMember.IsReturnTypeVoid() ? "void" : methodMember.ReturnType.TypeName.FullName); + sb.Append(' '); + } + + sb.Append(methodMember.Name); + sb.Append('('); + for (int j = 0; j < methodMember.Parameters.Count; j++) + { + if (j > 0) + { + sb.Append(", "); + } + + sb.Append(methodMember.Parameters[j].GetTooltip()); + } + + sb.Append(')'); } - sb.Append(')'); - return sb.ToString(); + _toolTip = sb.ToString(); + return _toolTip; } #region Visitors @@ -3380,8 +3534,7 @@ internal override object Accept(ICustomAstVisitor visitor) internal override AstVisitAction InternalVisit(AstVisitor visitor) { var action = AstVisitAction.Continue; - var visitor2 = visitor as AstVisitor2; - if (visitor2 != null) + if (visitor is AstVisitor2 visitor2) { action = visitor2.VisitFunctionMember(this); if (action == AstVisitAction.SkipChildren) @@ -3392,7 +3545,10 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) { var attributeAst = Attributes[index]; action = attributeAst.InternalVisit(visitor); - if (action != AstVisitAction.Continue) break; + if (action != AstVisitAction.Continue) + { + break; + } } } @@ -3718,10 +3874,7 @@ public CommentHelpInfo GetHelpContent() /// public CommentHelpInfo GetHelpContent(Dictionary scriptBlockTokenCache) { - if (scriptBlockTokenCache == null) - { - throw new ArgumentNullException(nameof(scriptBlockTokenCache)); - } + ArgumentNullException.ThrowIfNull(scriptBlockTokenCache); var commentTokens = HelpCommentsParser.GetHelpCommentTokens(this, scriptBlockTokenCache); if (commentTokens != null) @@ -3754,7 +3907,7 @@ internal string GetParamTextFromParameterList(Tuple, Diagnostics.Assert( usingVariablesTuple.Item1 != null && usingVariablesTuple.Item1.Count > 0 && !string.IsNullOrEmpty(usingVariablesTuple.Item2), "Caller makes sure the value passed in is not null or empty."); - orderedUsingVars = usingVariablesTuple.Item1.OrderBy(varAst => varAst.Extent.StartOffset).GetEnumerator(); + orderedUsingVars = usingVariablesTuple.Item1.OrderBy(static varAst => varAst.Extent.StartOffset).GetEnumerator(); additionalNewUsingParams = usingVariablesTuple.Item2; } @@ -4041,7 +4194,7 @@ public DataStatementAst(IScriptExtent extent, { this.CommandsAllowed = new ReadOnlyCollection(commandsAllowed.ToArray()); SetParents(CommandsAllowed); - this.HasNonConstantAllowedCommand = CommandsAllowed.Any(ast => ast is not StringConstantExpressionAst); + this.HasNonConstantAllowedCommand = CommandsAllowed.Any(static ast => ast is not StringConstantExpressionAst); } else { @@ -4425,7 +4578,7 @@ public class DoWhileStatementAst : LoopStatementAst /// /// Construct a do/while statement. /// - /// The extent of the do/while statment from the label or do keyword to the closing curly brace. + /// The extent of the do/while statement from the label or do keyword to the closing curly brace. /// The optionally null label. /// The condition tested on each iteration of the loop. /// The body executed on each iteration of the loop. @@ -5411,15 +5564,9 @@ public PipelineChainAst( bool background = false) : base(extent) { - if (lhsChain == null) - { - throw new ArgumentNullException(nameof(lhsChain)); - } + ArgumentNullException.ThrowIfNull(lhsChain); - if (rhsPipeline == null) - { - throw new ArgumentNullException(nameof(rhsPipeline)); - } + ArgumentNullException.ThrowIfNull(rhsPipeline); if (chainOperator != TokenKind.AndAnd && chainOperator != TokenKind.OrOr) { @@ -5891,8 +6038,8 @@ public CommandAst(IScriptExtent extent, /// Returns the name of the command invoked by this ast. /// This command name may not be known statically, in which case null is returned. /// - /// For example, if the command name is in a variable: & $foo, then the parser cannot know which command is executed. - /// Similarly, if the command is being invoked in a module: & (gmo SomeModule) Bar, then the parser does not know the + /// For example, if the command name is in a variable: & $foo, then the parser cannot know which command is executed. + /// Similarly, if the command is being invoked in a module: & (gmo SomeModule) Bar, then the parser does not know the /// command name is Bar because the parser can't determine that the expression (gmo SomeModule) returns a module instead /// of a string. /// @@ -6258,19 +6405,13 @@ public AssignmentStatementAst(IScriptExtent extent, ExpressionAst left, TokenKin // If the assignment is just an expression and the expression is not backgrounded then // remove the pipeline wrapping the expression. - var pipelineAst = right as PipelineAst; - if (pipelineAst != null && !pipelineAst.Background) + if (right is PipelineAst pipelineAst + && !pipelineAst.Background + && pipelineAst.PipelineElements.Count == 1 + && pipelineAst.PipelineElements[0] is CommandExpressionAst commandExpressionAst) { - if (pipelineAst.PipelineElements.Count == 1) - { - var commandExpressionAst = pipelineAst.PipelineElements[0] as CommandExpressionAst; - - if (commandExpressionAst != null) - { - right = commandExpressionAst; - right.ClearParent(); - } - } + right = commandExpressionAst; + right.ClearParent(); } this.Operator = @operator; @@ -6319,8 +6460,7 @@ public override Ast Copy() /// All of the expressions assigned by the assignment statement. public IEnumerable GetAssignmentTargets() { - var arrayExpression = Left as ArrayLiteralAst; - if (arrayExpression != null) + if (Left is ArrayLiteralAst arrayExpression) { foreach (var element in arrayExpression.Elements) { @@ -6444,7 +6584,7 @@ public override Ast Copy() { LCurlyToken = this.LCurlyToken, ConfigurationToken = this.ConfigurationToken, - CustomAttributes = this.CustomAttributes?.Select(e => (AttributeAst)e.Copy()) + CustomAttributes = this.CustomAttributes?.Select(static e => (AttributeAst)e.Copy()) }; } @@ -6459,8 +6599,7 @@ internal override object Accept(ICustomAstVisitor visitor) internal override AstVisitAction InternalVisit(AstVisitor visitor) { var action = AstVisitAction.Continue; - var visitor2 = visitor as AstVisitor2; - if (visitor2 != null) + if (visitor is AstVisitor2 visitor2) { action = visitor2.VisitConfigurationDefinition(this); if (action == AstVisitAction.SkipChildren) @@ -6545,7 +6684,7 @@ internal PipelineAst GenerateSetItemPipelineAst() cea.Add(new CommandParameterAst(PositionUtilities.EmptyExtent, "ResourceModuleTuplesToImport", new ConstantExpressionAst(PositionUtilities.EmptyExtent, resourceModulePairsToImport), PositionUtilities.EmptyExtent)); var scriptBlockBody = new ScriptBlockAst(Body.Extent, - CustomAttributes?.Select(att => (AttributeAst)att.Copy()).ToList(), + CustomAttributes?.Select(static att => (AttributeAst)att.Copy()).ToList(), null, new StatementBlockAst(Body.Extent, resourceBody, null), false, false); @@ -6578,9 +6717,9 @@ internal PipelineAst GenerateSetItemPipelineAst() // ) // var attribAsts = - ConfigurationBuildInParameterAttribAsts.Select(attribAst => (AttributeAst)attribAst.Copy()).ToList(); + ConfigurationBuildInParameterAttribAsts.Select(static attribAst => (AttributeAst)attribAst.Copy()).ToList(); - var paramAsts = ConfigurationBuildInParameters.Select(paramAst => (ParameterAst)paramAst.Copy()).ToList(); + var paramAsts = ConfigurationBuildInParameters.Select(static paramAst => (ParameterAst)paramAst.Copy()).ToList(); // the parameters defined in the configuration keyword will be combined with above parameters // it will be used to construct $ArgsToBody in the set-item created function boby using below statement @@ -6591,7 +6730,7 @@ internal PipelineAst GenerateSetItemPipelineAst() // $Outputpath = $psboundparameters[""Outputpath""] if (Body.ScriptBlock.ParamBlock != null) { - paramAsts.AddRange(Body.ScriptBlock.ParamBlock.Parameters.Select(parameterAst => (ParameterAst)parameterAst.Copy())); + paramAsts.AddRange(Body.ScriptBlock.ParamBlock.Parameters.Select(static parameterAst => (ParameterAst)parameterAst.Copy())); } var paramBlockAst = new ParamBlockAst(this.Extent, attribAsts, paramAsts); @@ -6599,12 +6738,12 @@ internal PipelineAst GenerateSetItemPipelineAst() var cmdAst = new CommandAst(this.Extent, cea, TokenKind.Unknown, null); var pipeLineAst = new PipelineAst(this.Extent, cmdAst, background: false); - var funcStatements = ConfigurationExtraParameterStatements.Select(statement => (StatementAst)statement.Copy()).ToList(); + var funcStatements = ConfigurationExtraParameterStatements.Select(static statement => (StatementAst)statement.Copy()).ToList(); funcStatements.Add(pipeLineAst); var statmentBlockAst = new StatementBlockAst(this.Extent, funcStatements, null); var funcBody = new ScriptBlockAst(Body.Extent, - CustomAttributes?.Select(att => (AttributeAst)att.Copy()).ToList(), + CustomAttributes?.Select(static att => (AttributeAst)att.Copy()).ToList(), paramBlockAst, statmentBlockAst, false, true); var funcBodyExp = new ScriptBlockExpressionAst(this.Extent, funcBody); @@ -6928,8 +7067,7 @@ internal override object Accept(ICustomAstVisitor visitor) internal override AstVisitAction InternalVisit(AstVisitor visitor) { var action = AstVisitAction.Continue; - var visitor2 = visitor as AstVisitor2; - if (visitor2 != null) + if (visitor is AstVisitor2 visitor2) { action = visitor2.VisitDynamicKeywordStatement(this); if (action == AstVisitAction.SkipChildren) @@ -7031,7 +7169,6 @@ internal PipelineAst GenerateCommandCallPipelineAst() } ExpressionAst expr = BodyExpression; - HashtableAst hashtable = expr as HashtableAst; if (Keyword.DirectCall) { // If this keyword takes a name, then add it as the parameter -InstanceName @@ -7050,7 +7187,7 @@ internal PipelineAst GenerateCommandCallPipelineAst() // in the hash literal expression and map them to parameters. // We've already checked to make sure that they're all valid names. // - if (hashtable != null) + if (expr is HashtableAst hashtable) { bool isHashtableValid = true; // @@ -7126,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", @@ -7234,10 +7371,7 @@ protected ExpressionAst(IScriptExtent extent) /// internal virtual bool ShouldPreserveOutputInCaseOfException() { - var parenExpr = this as ParenExpressionAst; - var subExpr = this as SubExpressionAst; - - if (parenExpr == null && subExpr == null) + if (this is not ParenExpressionAst and not SubExpressionAst) { PSTraceSource.NewInvalidOperationException(); } @@ -7253,8 +7387,7 @@ internal virtual bool ShouldPreserveOutputInCaseOfException() return false; } - var parenExpressionAst = pipelineAst.Parent as ParenExpressionAst; - if (parenExpressionAst != null) + if (pipelineAst.Parent is ParenExpressionAst parenExpressionAst) { return parenExpressionAst.ShouldPreserveOutputInCaseOfException(); } @@ -7307,9 +7440,9 @@ public TernaryExpressionAst(IScriptExtent extent, ExpressionAst condition, Expre /// /// Copy the TernaryExpressionAst instance. /// - /// - /// Retirns a copy of the ast. - /// + /// + /// Returns a copy of the ast. + /// public override Ast Copy() { ExpressionAst newCondition = CopyElement(this.Condition); @@ -7458,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 @@ -7836,8 +7971,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) IAssignableValue ISupportsAssignment.GetAssignableValue() { - var varExpr = Child as VariableExpressionAst; - if (varExpr != null && varExpr.TupleIndex >= 0) + if (Child is VariableExpressionAst varExpr && varExpr.TupleIndex >= 0) { // In the common case of a single cast on the lhs of an assignment, we may have saved the type of the // variable in the mutable tuple, so conversions will get generated elsewhere, and we can just use @@ -7863,7 +7997,7 @@ internal bool IsRef() public class MemberExpressionAst : ExpressionAst, ISupportsAssignment { /// - /// Construct an ast to reference a property. + /// Initializes a new instance of the class. /// /// /// The extent of the expression, starting with the expression before the operator '.' or '::' and ending after @@ -7939,7 +8073,13 @@ public override Ast Copy() { var newExpression = CopyElement(this.Expression); var newMember = CopyElement(this.Member); - return new MemberExpressionAst(this.Extent, newExpression, newMember, this.Static, this.NullConditional); + + return new MemberExpressionAst( + this.Extent, + newExpression, + newMember, + this.Static, + this.NullConditional); } #region Visitors @@ -7975,7 +8115,7 @@ IAssignableValue ISupportsAssignment.GetAssignableValue() public class InvokeMemberExpressionAst : MemberExpressionAst, ISupportsAssignment { /// - /// Construct an instance of a method invocation expression. + /// Initializes a new instance of the class. /// /// /// The extent of the expression, starting with the expression before the invocation operator and ending with the @@ -7987,10 +8127,17 @@ public class InvokeMemberExpressionAst : MemberExpressionAst, ISupportsAssignmen /// /// True if the invocation is for a static method, using '::', false if invoking a method on an instance using '.'. /// + /// The generic type arguments passed to the method. /// /// If is null. /// - public InvokeMemberExpressionAst(IScriptExtent extent, ExpressionAst expression, CommandElementAst method, IEnumerable arguments, bool @static) + public InvokeMemberExpressionAst( + IScriptExtent extent, + ExpressionAst expression, + CommandElementAst method, + IEnumerable arguments, + bool @static, + IList genericTypes) : base(extent, expression, method, @static) { if (arguments != null && arguments.Any()) @@ -7998,6 +8145,37 @@ public InvokeMemberExpressionAst(IScriptExtent extent, ExpressionAst expression, this.Arguments = new ReadOnlyCollection(arguments.ToArray()); SetParents(Arguments); } + + if (genericTypes != null && genericTypes.Count > 0) + { + this.GenericTypeArguments = new ReadOnlyCollection(genericTypes); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The extent of the expression, starting with the expression before the invocation operator and ending with the + /// closing paren after the arguments. + /// + /// The expression before the invocation operator ('.', '::'). + /// The method to invoke. + /// The arguments to pass to the method. + /// + /// True if the invocation is for a static method, using '::', false if invoking a method on an instance using '.'. + /// + /// + /// If is null. + /// + public InvokeMemberExpressionAst( + IScriptExtent extent, + ExpressionAst expression, + CommandElementAst method, + IEnumerable arguments, + bool @static) + : this(extent, expression, method, arguments, @static, genericTypes: null) + { } /// @@ -8014,15 +8192,56 @@ public InvokeMemberExpressionAst(IScriptExtent extent, ExpressionAst expression, /// True if the invocation is for a static method, using '::', false if invoking a method on an instance using '.' or '?.'. /// /// True if the operator used is '?.'. + /// The generic type arguments passed to the method. /// /// If is null. /// - public InvokeMemberExpressionAst(IScriptExtent extent, ExpressionAst expression, CommandElementAst method, IEnumerable arguments, bool @static, bool nullConditional) - : this(extent, expression, method, arguments, @static) + public InvokeMemberExpressionAst( + IScriptExtent extent, + ExpressionAst expression, + CommandElementAst method, + IEnumerable arguments, + bool @static, + bool nullConditional, + IList genericTypes) + : this(extent, expression, method, arguments, @static, genericTypes) { this.NullConditional = nullConditional; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The extent of the expression, starting with the expression before the invocation operator and ending with the + /// closing paren after the arguments. + /// + /// The expression before the invocation operator ('.', '::' or '?.'). + /// The method to invoke. + /// The arguments to pass to the method. + /// + /// True if the invocation is for a static method, using '::', false if invoking a method on an instance using '.' or '?.'. + /// + /// True if the operator used is '?.'. + /// + /// If is null. + /// + public InvokeMemberExpressionAst( + IScriptExtent extent, + ExpressionAst expression, + CommandElementAst method, + IEnumerable arguments, + bool @static, + bool nullConditional) + : this(extent, expression, method, arguments, @static, nullConditional, genericTypes: null) + { + } + + /// + /// Gets a list of generic type arguments passed to this method invocation. + /// + public ReadOnlyCollection GenericTypeArguments { get; } + /// /// The non-empty collection of arguments to pass when invoking the method, or null if no arguments were specified. /// @@ -8036,7 +8255,15 @@ public override Ast Copy() var newExpression = CopyElement(this.Expression); var newMethod = CopyElement(this.Member); var newArguments = CopyElements(this.Arguments); - return new InvokeMemberExpressionAst(this.Extent, newExpression, newMethod, newArguments, this.Static, this.NullConditional); + + return new InvokeMemberExpressionAst( + this.Extent, + newExpression, + newMethod, + newArguments, + this.Static, + this.NullConditional, + this.GenericTypeArguments); } #region Visitors @@ -8116,8 +8343,7 @@ public BaseCtorInvokeMemberExpressionAst(IScriptExtent baseKeywordExtent, IScrip internal override AstVisitAction InternalVisit(AstVisitor visitor) { AstVisitAction action = AstVisitAction.Continue; - var visitor2 = visitor as AstVisitor2; - if (visitor2 != null) + if (visitor is AstVisitor2 visitor2) { action = visitor2.VisitBaseCtorInvokeMemberExpression(this); if (action == AstVisitAction.SkipChildren) @@ -8139,6 +8365,7 @@ internal override object Accept(ICustomAstVisitor visitor) /// /// The name and attributes of a type. /// +#nullable enable public interface ITypeName { /// @@ -8154,7 +8381,7 @@ public interface ITypeName /// /// The name of the assembly, if specified, otherwise null. /// - string AssemblyName { get; } + string? AssemblyName { get; } /// /// Returns true if the type names an array, false otherwise. @@ -8169,20 +8396,21 @@ public interface ITypeName /// /// Returns the that this typename represents, if such a type exists, null otherwise. /// - Type GetReflectionType(); + Type? GetReflectionType(); /// /// Assuming the typename is an attribute, returns the that this typename represents. /// By convention, the typename may omit the suffix "Attribute". Lookup will attempt to resolve the type as is, /// and if that fails, the suffix "Attribute" will be appended. /// - Type GetReflectionAttributeType(); + Type? GetReflectionAttributeType(); /// /// The extent of the typename. /// IScriptExtent Extent { get; } } +#nullable restore #nullable enable internal interface ISupportsTypeCaching @@ -8196,9 +8424,11 @@ internal interface ISupportsTypeCaching /// 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; /// @@ -8225,8 +8455,7 @@ public TypeName(IScriptExtent extent, string name) throw PSTraceSource.NewArgumentException(nameof(name)); } - int backtick = name.IndexOf('`'); - if (backtick != -1) + if (name.Contains('`')) { name = name.Replace("``", "`"); } @@ -8258,6 +8487,23 @@ public TypeName(IScriptExtent extent, string name, string assembly) AssemblyName = assembly; } + /// + /// Construct a typename that represents a generic type definition. + /// + /// The extent of the typename. + /// The name of the type. + /// The number of generic arguments. + internal TypeName(IScriptExtent extent, string name, int genericArgumentCount) + : this(extent, name) + { + ArgumentOutOfRangeException.ThrowIfLessThan(genericArgumentCount, 0); + + if (genericArgumentCount > 0 && !_name.Contains('`')) + { + _genericArgumentCount = genericArgumentCount; + } + } + /// /// Returns the full name of the type. /// @@ -8305,8 +8551,7 @@ internal bool HasDefaultCtor() bool hasExplicitCtor = false; foreach (var member in _typeDefinitionAst.Members) { - var function = member as FunctionMemberAst; - if (function != null) + if (member is FunctionMemberAst function) { if (function.IsConstructor) { @@ -8335,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`, 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 @@ -8371,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)) { @@ -8664,8 +8930,13 @@ internal Type GetGenericType(Type generic) { if (!TypeName.FullName.Contains('`')) { - var newTypeName = new TypeName(Extent, - string.Format(CultureInfo.InvariantCulture, "{0}`{1}", TypeName.FullName, GenericArguments.Count)); + TypeName newTypeName = new( + Extent, + string.Create(CultureInfo.InvariantCulture, $"{TypeName.Name}`{GenericArguments.Count}")) + { + AssemblyName = TypeName.AssemblyName + }; + generic = newTypeName.GetReflectionType(); } } @@ -8691,8 +8962,13 @@ public Type GetReflectionAttributeType() { if (!TypeName.FullName.Contains('`')) { - var newTypeName = new TypeName(Extent, - string.Format(CultureInfo.InvariantCulture, "{0}Attribute`{1}", TypeName.FullName, GenericArguments.Count)); + TypeName newTypeName = new( + Extent, + string.Create(CultureInfo.InvariantCulture, $"{TypeName.Name}Attribute`{GenericArguments.Count}")) + { + AssemblyName = TypeName.AssemblyName + }; + generic = newTypeName.GetReflectionType(); } } @@ -9579,8 +9855,7 @@ public ExpandableStringExpressionAst(IScriptExtent extent, } var ast = Language.Parser.ScanString(value); - var expandableStringAst = ast as ExpandableStringExpressionAst; - if (expandableStringAst != null) + if (ast is ExpandableStringExpressionAst expandableStringAst) { this.FormatExpression = expandableStringAst.FormatExpression; this.NestedExpressions = expandableStringAst.NestedExpressions; @@ -10201,10 +10476,7 @@ public override Ast Copy() [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "We want to get the underlying variable only for the UsingExpressionAst.")] public static VariableExpressionAst ExtractUsingVariable(UsingExpressionAst usingExpressionAst) { - if (usingExpressionAst == null) - { - throw new ArgumentNullException(nameof(usingExpressionAst)); - } + ArgumentNullException.ThrowIfNull(usingExpressionAst); return ExtractUsingVariableImpl(usingExpressionAst); } @@ -10216,10 +10488,9 @@ public static VariableExpressionAst ExtractUsingVariable(UsingExpressionAst usin /// private static VariableExpressionAst ExtractUsingVariableImpl(ExpressionAst expression) { - var usingExpr = expression as UsingExpressionAst; VariableExpressionAst variableExpr; - if (usingExpr != null) + if (expression is UsingExpressionAst usingExpr) { variableExpr = usingExpr.SubExpression as VariableExpressionAst; if (variableExpr != null) @@ -10230,8 +10501,7 @@ private static VariableExpressionAst ExtractUsingVariableImpl(ExpressionAst expr return ExtractUsingVariableImpl(usingExpr.SubExpression); } - var indexExpr = expression as IndexExpressionAst; - if (indexExpr != null) + if (expression is IndexExpressionAst indexExpr) { variableExpr = indexExpr.Target as VariableExpressionAst; if (variableExpr != null) @@ -10242,8 +10512,7 @@ private static VariableExpressionAst ExtractUsingVariableImpl(ExpressionAst expr return ExtractUsingVariableImpl(indexExpr.Target); } - var memberExpr = expression as MemberExpressionAst; - if (memberExpr != null) + if (expression is MemberExpressionAst memberExpr) { variableExpr = memberExpr.Expression as VariableExpressionAst; if (variableExpr != null) diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index 893ec9fc8e0..3cee7580ff9 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -200,7 +200,7 @@ public enum TokenKind /// The addition operator '+'. Plus = 40, - /// The substraction operator '-'. + /// The subtraction operator '-'. Minus = 41, /// The assignment operator '='. @@ -588,6 +588,9 @@ public enum TokenKind /// The 'default' keyword Default = 169, + /// The 'clean' keyword. + Clean = 170, + #endregion Keywords } @@ -659,7 +662,7 @@ public enum TokenFlags Keyword = 0x00000010, /// - /// The token one of the keywords that is a part of a script block: 'begin', 'process', 'end', or 'dynamicparam'. + /// The token is one of the keywords that is a part of a script block: 'begin', 'process', 'end', 'clean', or 'dynamicparam'. /// ScriptBlockBlockName = 0x00000020, @@ -948,6 +951,7 @@ public static class TokenTraits /* Hidden */ TokenFlags.Keyword, /* Base */ TokenFlags.Keyword, /* Default */ TokenFlags.Keyword, + /* Clean */ TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName, #endregion Flags for keywords }; @@ -1147,6 +1151,7 @@ public static class TokenTraits /* Hidden */ "hidden", /* Base */ "base", /* Default */ "default", + /* Clean */ "clean", #endregion Text for keywords }; @@ -1154,10 +1159,12 @@ public static class TokenTraits #if DEBUG static TokenTraits() { - Diagnostics.Assert(s_staticTokenFlags.Length == ((int)TokenKind.Default + 1), - "Table size out of sync with enum - _staticTokenFlags"); - Diagnostics.Assert(s_tokenText.Length == ((int)TokenKind.Default + 1), - "Table size out of sync with enum - _tokenText"); + Diagnostics.Assert( + s_staticTokenFlags.Length == ((int)TokenKind.Clean + 1), + "Table size out of sync with enum - _staticTokenFlags"); + Diagnostics.Assert( + s_tokenText.Length == ((int)TokenKind.Clean + 1), + "Table size out of sync with enum - _tokenText"); // Some random assertions to make sure the enum and the traits are in sync Diagnostics.Assert(GetTraits(TokenKind.Begin) == (TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName), "Table out of sync with enum - flags Begin"); @@ -1173,7 +1180,7 @@ static TokenTraits() #endif /// - /// Return all the flags for a given TokenKind. + /// Return all the flags for a given . /// public static TokenFlags GetTraits(this TokenKind kind) { @@ -1181,7 +1188,7 @@ public static TokenFlags GetTraits(this TokenKind kind) } /// - /// Return true if the TokenKind has the given trait. + /// Return true if the has the given trait. /// public static bool HasTrait(this TokenKind kind, TokenFlags flag) { @@ -1195,7 +1202,7 @@ internal static int GetBinaryPrecedence(this TokenKind kind) } /// - /// Return the text for a given TokenKind. + /// Return the text for a given . /// public static string Text(this TokenKind kind) { @@ -1264,7 +1271,7 @@ public override string ToString() internal virtual string ToDebugString(int indent) { - return string.Format(CultureInfo.InvariantCulture, "{0}{1}: <{2}>", StringUtil.Padding(indent), _kind, Text); + return string.Create(CultureInfo.InvariantCulture, $"{StringUtil.Padding(indent)}{_kind}: <{Text}>"); } } @@ -1283,8 +1290,14 @@ internal NumberToken(InternalScriptExtent scriptExtent, object value, TokenFlags internal override string ToDebugString(int indent) { - return string.Format(CultureInfo.InvariantCulture, - "{0}{1}: <{2}> Value:<{3}> Type:<{4}>", StringUtil.Padding(indent), Kind, Text, _value, _value.GetType().Name); + return string.Format( + CultureInfo.InvariantCulture, + "{0}{1}: <{2}> Value:<{3}> Type:<{4}>", + StringUtil.Padding(indent), + Kind, + Text, + _value, + _value.GetType().Name); } /// @@ -1325,8 +1338,13 @@ internal ParameterToken(InternalScriptExtent scriptExtent, string parameterName, internal override string ToDebugString(int indent) { - return string.Format(CultureInfo.InvariantCulture, - "{0}{1}: <-{2}{3}>", StringUtil.Padding(indent), Kind, _parameterName, _usedColon ? ":" : string.Empty); + return string.Format( + CultureInfo.InvariantCulture, + "{0}{1}: <-{2}{3}>", + StringUtil.Padding(indent), + Kind, + _parameterName, + _usedColon ? ":" : string.Empty); } } @@ -1353,8 +1371,13 @@ internal VariableToken(InternalScriptExtent scriptExtent, VariablePath path, Tok internal override string ToDebugString(int indent) { - return string.Format(CultureInfo.InvariantCulture, - "{0}{1}: <{2}> Name:<{3}>", StringUtil.Padding(indent), Kind, Text, Name); + return string.Format( + CultureInfo.InvariantCulture, + "{0}{1}: <{2}> Name:<{3}>", + StringUtil.Padding(indent), + Kind, + Text, + Name); } } @@ -1376,8 +1399,13 @@ internal StringToken(InternalScriptExtent scriptExtent, TokenKind kind, TokenFla internal override string ToDebugString(int indent) { - return string.Format(CultureInfo.InvariantCulture, - "{0}{1}: <{2}> Value:<{3}>", StringUtil.Padding(indent), Kind, Text, Value); + return string.Format( + CultureInfo.InvariantCulture, + "{0}{1}: <{2}> Value:<{3}>", + StringUtil.Padding(indent), + Kind, + Text, + Value); } } diff --git a/src/System.Management.Automation/engine/parser/tokenizer.cs b/src/System.Management.Automation/engine/parser/tokenizer.cs index 5ab09c0a40f..e2aed94cc98 100644 --- a/src/System.Management.Automation/engine/parser/tokenizer.cs +++ b/src/System.Management.Automation/engine/parser/tokenizer.cs @@ -12,6 +12,8 @@ using System.Text; using Microsoft.PowerShell.Commands; +using System.Management.Automation.Subsystem; +using System.Management.Automation.Subsystem.DSC; using Microsoft.PowerShell.DesiredStateConfiguration.Internal; namespace System.Management.Automation.Language @@ -312,7 +314,7 @@ public DynamicKeyword Copy() public bool HasReservedProperties { get; set; } /// - /// A list of the properties allowed for this constuctor. + /// A list of the properties allowed for this constructor. /// public Dictionary Properties { @@ -325,7 +327,7 @@ public Dictionary Properties private Dictionary _properties; /// - /// A list of the parameters allowed for this constuctor. + /// A list of the parameters allowed for this constructor. /// public Dictionary Parameters { @@ -361,7 +363,15 @@ internal static bool IsMetaDSCResource(this DynamicKeyword keyword) string implementingModule = keyword.ImplementingModule; if (implementingModule != null) { - return implementingModule.Equals(DscClassCache.DefaultModuleInfoForMetaConfigResource.Item1, StringComparison.OrdinalIgnoreCase); + ICrossPlatformDsc dscSubsystem = SubsystemManager.GetSubsystem(); + if (dscSubsystem != null) + { + dscSubsystem.IsDefaultModuleNameForMetaConfigResource(implementingModule); + } + else + { + return implementingModule.Equals(DscClassCache.DefaultModuleInfoForMetaConfigResource.Item1, StringComparison.OrdinalIgnoreCase); + } } return false; @@ -625,23 +635,23 @@ private static readonly Dictionary s_operatorTable /*A*/ "configuration", "public", "private", "static", /*A*/ /*B*/ "interface", "enum", "namespace", "module", /*B*/ /*C*/ "type", "assembly", "command", "hidden", /*C*/ - /*D*/ "base", "default", /*D*/ + /*D*/ "base", "default", "clean", /*D*/ }; private static readonly TokenKind[] s_keywordTokenKind = new TokenKind[] { - /*1*/ TokenKind.ElseIf, TokenKind.If, TokenKind.Else, TokenKind.Switch, /*1*/ - /*2*/ TokenKind.Foreach, TokenKind.From, TokenKind.In, TokenKind.For, /*2*/ - /*3*/ TokenKind.While, TokenKind.Until, TokenKind.Do, TokenKind.Try, /*3*/ - /*4*/ TokenKind.Catch, TokenKind.Finally, TokenKind.Trap, TokenKind.Data, /*4*/ - /*5*/ TokenKind.Return, TokenKind.Continue, TokenKind.Break, TokenKind.Exit, /*5*/ - /*6*/ TokenKind.Throw, TokenKind.Begin, TokenKind.Process, TokenKind.End, /*6*/ - /*7*/ TokenKind.Dynamicparam, TokenKind.Function, TokenKind.Filter, TokenKind.Param, /*7*/ - /*8*/ TokenKind.Class, TokenKind.Define, TokenKind.Var, TokenKind.Using, /*8*/ - /*9*/ TokenKind.Workflow, TokenKind.Parallel, TokenKind.Sequence, TokenKind.InlineScript, /*9*/ - /*A*/ TokenKind.Configuration, TokenKind.Public, TokenKind.Private, TokenKind.Static, /*A*/ - /*B*/ TokenKind.Interface, TokenKind.Enum, TokenKind.Namespace,TokenKind.Module, /*B*/ - /*C*/ TokenKind.Type, TokenKind.Assembly, TokenKind.Command, TokenKind.Hidden, /*C*/ - /*D*/ TokenKind.Base, TokenKind.Default, /*D*/ + /*1*/ TokenKind.ElseIf, TokenKind.If, TokenKind.Else, TokenKind.Switch, /*1*/ + /*2*/ TokenKind.Foreach, TokenKind.From, TokenKind.In, TokenKind.For, /*2*/ + /*3*/ TokenKind.While, TokenKind.Until, TokenKind.Do, TokenKind.Try, /*3*/ + /*4*/ TokenKind.Catch, TokenKind.Finally, TokenKind.Trap, TokenKind.Data, /*4*/ + /*5*/ TokenKind.Return, TokenKind.Continue, TokenKind.Break, TokenKind.Exit, /*5*/ + /*6*/ TokenKind.Throw, TokenKind.Begin, TokenKind.Process, TokenKind.End, /*6*/ + /*7*/ TokenKind.Dynamicparam, TokenKind.Function, TokenKind.Filter, TokenKind.Param, /*7*/ + /*8*/ TokenKind.Class, TokenKind.Define, TokenKind.Var, TokenKind.Using, /*8*/ + /*9*/ TokenKind.Workflow, TokenKind.Parallel, TokenKind.Sequence, TokenKind.InlineScript, /*9*/ + /*A*/ TokenKind.Configuration, TokenKind.Public, TokenKind.Private, TokenKind.Static, /*A*/ + /*B*/ TokenKind.Interface, TokenKind.Enum, TokenKind.Namespace, TokenKind.Module, /*B*/ + /*C*/ TokenKind.Type, TokenKind.Assembly, TokenKind.Command, TokenKind.Hidden, /*C*/ + /*D*/ TokenKind.Base, TokenKind.Default, TokenKind.Clean, /*D*/ }; internal static readonly string[] _operatorText = new string[] { @@ -704,7 +714,7 @@ static Tokenizer() // The hash we compute is intentionally dumb, we want collisions to catch similar strings, // so we just sum up the characters. const string beginSig = "sig#beginsignatureblock"; - beginSig.Aggregate(0, (current, t) => current + t); + beginSig.Aggregate(0, static (current, t) => current + t); // Spot check to help make sure the arrays are in sync Diagnostics.Assert(s_keywordTable["using"] == TokenKind.Using, "Keyword table out of sync w/ enum"); @@ -1208,10 +1218,7 @@ private Token NewCommentToken() private T SaveToken(T token) where T : Token { - if (TokenList != null) - { - TokenList.Add(token); - } + TokenList?.Add(token); // Keep track of the first and last token even if we're not saving tokens // for the special variables $$ and $^. @@ -1224,10 +1231,7 @@ private T SaveToken(T token) where T : Token // Don't remember these tokens, they aren't useful in $$ and $^. break; default: - if (FirstToken == null) - { - FirstToken = token; - } + FirstToken ??= token; LastToken = token; break; @@ -1269,7 +1273,7 @@ private StringToken NewStringExpandableToken(string value, string formatString, } else if ((flags & TokenFlags.TokenInError) == 0) { - if (nestedTokens.Any(tok => tok.HasError)) + if (nestedTokens.Any(static tok => tok.HasError)) { flags |= TokenFlags.TokenInError; } @@ -1797,8 +1801,7 @@ private void ScanLineComment() } else if (matchedRequires && _nestedTokensAdjustment == 0) { - if (RequiresTokens == null) - RequiresTokens = new List(); + RequiresTokens ??= new List(); RequiresTokens.Add(token); } } @@ -1935,10 +1938,7 @@ internal ScriptRequirements GetScriptRequirements() PSSnapinToken.StartsWith(parameter.ParameterName, StringComparison.OrdinalIgnoreCase)) { snapinSpecified = true; - if (requiredSnapins == null) - { - requiredSnapins = new List(); - } + requiredSnapins ??= new List(); break; } @@ -1976,9 +1976,6 @@ internal ScriptRequirements GetScriptRequirements() RequiredPSEditions = requiredEditions != null ? new ReadOnlyCollection(requiredEditions) : ScriptRequirements.EmptyEditionCollection, - RequiresPSSnapIns = requiredSnapins != null - ? new ReadOnlyCollection(requiredSnapins) - : ScriptRequirements.EmptySnapinCollection, RequiredAssemblies = requiredAssemblies != null ? new ReadOnlyCollection(requiredAssemblies) : ScriptRequirements.EmptyAssemblyCollection, @@ -2204,8 +2201,7 @@ private void HandleRequiresParameter(CommandParameterAst parameter, return; } - if (requiredModules == null) - requiredModules = new List(); + requiredModules ??= new List(); requiredModules.Add(moduleSpecification); } } @@ -2228,8 +2224,7 @@ private List HandleRequiresAssemblyArgument(Ast argumentAst, object arg, } else { - if (requiredAssemblies == null) - requiredAssemblies = new List(); + requiredAssemblies ??= new List(); if (!requiredAssemblies.Contains((string)arg)) { @@ -2251,8 +2246,7 @@ private List HandleRequiresPSEditionArgument(Ast argumentAst, object arg } else { - if (requiredEditions == null) - requiredEditions = new List(); + requiredEditions ??= new List(); var edition = (string)arg; if (!Utils.IsValidPSEditionValue(edition)) @@ -2566,7 +2560,7 @@ private bool ScanDollarInStringExpandable(StringBuilder sb, StringBuilder format // Make sure we didn't consume anything because we didn't find // any nested tokens (no variable or subexpression.) - Diagnostics.Assert(PeekChar() == c1, "We accidently consumed a character we shouldn't have."); + Diagnostics.Assert(PeekChar() == c1, "We accidentally consumed a character we shouldn't have."); return false; } @@ -3913,7 +3907,8 @@ private Token ScanNumber(char firstChar) return ScanGenericToken(GetStringBuilder()); } - ReportError(_currentIndex, + ReportError( + NewScriptExtent(_tokenStart, _currentIndex), nameof(ParserStrings.BadNumericConstant), ParserStrings.BadNumericConstant, _script.Substring(_tokenStart, _currentIndex - _tokenStart)); diff --git a/src/System.Management.Automation/engine/pipeline.cs b/src/System.Management.Automation/engine/pipeline.cs index 14837fd1c5c..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 _commands = new List(); private List _redirectionPipes; private PipelineReader _externalInputPipe; @@ -43,6 +45,10 @@ internal class PipelineProcessor : IDisposable private bool _linkedSuccessOutput = false; private bool _linkedErrorOutput = false; + private NativeCommandProcessor _lastNativeCommand; + + private bool _haveReportedNativePipeUsage; + #if !CORECLR // Impersonation Not Supported On CSS // This is the security context when the pipeline was allocated internal System.Security.SecurityContext SecurityContext = @@ -66,7 +72,6 @@ internal class PipelineProcessor : IDisposable public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } private void Dispose(bool disposing) @@ -82,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; @@ -115,6 +121,11 @@ internal bool ExecutionFailed } } + /// + /// Gets the CancellationToken that is signaled when the pipeline is stopping. + /// + internal CancellationToken PipelineStopToken => _pipelineStopTokenSource.Token; + internal void LogExecutionInfo(InvocationInfo invocationInfo, string text) { string message = StringUtil.Format(PipelineStrings.PipelineExecutionInformation, GetCommand(invocationInfo), text); @@ -214,50 +225,36 @@ private void Log(string logElement, InvocationInfo invocation, PipelineExecution // Log the cmdlet invocation execution details if we didn't have an associated script line with it. if ((invocation == null) || string.IsNullOrEmpty(invocation.Line)) { - if (hostInterface != null) - { - hostInterface.TranscribeCommand(logElement, invocation); - } + hostInterface?.TranscribeCommand(logElement, invocation); } - if (!string.IsNullOrEmpty(logElement)) + if (_needToLog && !string.IsNullOrEmpty(logElement)) { + _eventLogBuffer ??= new List(); _eventLogBuffer.Add(logElement); } } - internal void LogToEventLog() - { - if (NeedToLog()) - { - // We check to see if the command is needs writing (or if there is anything in the buffer) - // before we flush it. Flushing the empty buffer causes a measurable performance degradation. - if (_commands == null || _commands.Count == 0 || _eventLogBuffer.Count == 0) - return; - - MshLog.LogPipelineExecutionDetailEvent(_commands[0].Command.Context, - _eventLogBuffer, - _commands[0].Command.MyInvocation); - } - } - - private bool NeedToLog() + private void LogToEventLog() { - if (_commands == null) - return false; - - foreach (CommandProcessorBase commandProcessor in _commands) + // We check to see if there is anything in the buffer before we flush it. + // Flushing the empty buffer causes a measurable performance degradation. + if (_commands?.Count > 0 && _eventLogBuffer?.Count > 0) { - MshCommandRuntime cmdRuntime = commandProcessor.Command.commandRuntime as MshCommandRuntime; - - if (cmdRuntime != null && cmdRuntime.LogPipelineExecutionDetail) - return true; + InternalCommand firstCmd = _commands[0].Command; + MshLog.LogPipelineExecutionDetailEvent( + firstCmd.Context, + _eventLogBuffer, + firstCmd.MyInvocation); } - return false; + // Clear the log buffer after writing the event. + _eventLogBuffer?.Clear(); } - private List _eventLogBuffer = new List(); + private bool _needToLog = false; + private List _eventLogBuffer; + #endregion #region public_methods @@ -272,15 +269,40 @@ private bool NeedToLog() /// internal int Add(CommandProcessorBase commandProcessor) { + if (commandProcessor is NativeCommandProcessor nativeCommand) + { + if (_lastNativeCommand is not null) + { + // Only report experimental feature usage once per pipeline. + if (!_haveReportedNativePipeUsage) + { + ApplicationInsightsTelemetry.SendExperimentalUseData("PSNativeCommandPreserveBytePipe", "p"); + _haveReportedNativePipeUsage = true; + } + + _lastNativeCommand.DownStreamNativeCommand = nativeCommand; + nativeCommand.UpstreamIsNativeCommand = true; + } + + _lastNativeCommand = nativeCommand; + } + else + { + _lastNativeCommand = null; + } + commandProcessor.CommandRuntime.PipelineProcessor = this; - return AddCommand(commandProcessor, _commands.Count, false); + return AddCommand(commandProcessor, _commands.Count, readErrorQueue: false); } internal void AddRedirectionPipe(PipelineProcessor pipelineProcessor) { - if (pipelineProcessor == null) throw PSTraceSource.NewArgumentNullException(nameof(pipelineProcessor)); - if (_redirectionPipes == null) - _redirectionPipes = new List(); + if (pipelineProcessor is null) + { + throw PSTraceSource.NewArgumentNullException(nameof(pipelineProcessor)); + } + + _redirectionPipes ??= new List(); _redirectionPipes.Add(pipelineProcessor); } @@ -306,7 +328,7 @@ internal void AddRedirectionPipe(PipelineProcessor pipelineProcessor) /// PipeAlreadyTaken: the downstream pipe of command /// is already taken /// - internal int AddCommand(CommandProcessorBase commandProcessor, int readFromCommand, bool readErrorQueue) + private int AddCommand(CommandProcessorBase commandProcessor, int readFromCommand, bool readErrorQueue) { if (commandProcessor == null) { @@ -358,18 +380,15 @@ internal int AddCommand(CommandProcessorBase commandProcessor, int readFromComma } else { - CommandProcessorBase prevcommandProcessor = _commands[readFromCommand - 1] as CommandProcessorBase; - if (prevcommandProcessor == null || prevcommandProcessor.CommandRuntime == null) - { - // "PipelineProcessor.AddCommand(): previous request object == null" - throw PSTraceSource.NewInvalidOperationException(); - } + var prevcommandProcessor = _commands[readFromCommand - 1] as CommandProcessorBase; + ValidateCommandProcessorNotNull(prevcommandProcessor, errorMessage: null); + + Pipe UpstreamPipe = (readErrorQueue) + ? prevcommandProcessor.CommandRuntime.ErrorOutputPipe + : prevcommandProcessor.CommandRuntime.OutputPipe; - Pipe UpstreamPipe = (readErrorQueue) ? - prevcommandProcessor.CommandRuntime.ErrorOutputPipe : prevcommandProcessor.CommandRuntime.OutputPipe; if (UpstreamPipe == null) { - // "PipelineProcessor.AddCommand(): UpstreamPipe == null" throw PSTraceSource.NewInvalidOperationException(); } @@ -392,11 +411,8 @@ internal int AddCommand(CommandProcessorBase commandProcessor, int readFromComma for (int i = 0; i < _commands.Count; i++) { prevcommandProcessor = _commands[i]; - if (prevcommandProcessor == null || prevcommandProcessor.CommandRuntime == null) - { - // "PipelineProcessor.AddCommand(): previous request object == null" - throw PSTraceSource.NewInvalidOperationException(); - } + ValidateCommandProcessorNotNull(prevcommandProcessor, errorMessage: null); + // check whether the error output is already claimed if (prevcommandProcessor.CommandRuntime.ErrorOutputPipe.DownstreamCmdlet != null) continue; @@ -412,6 +428,9 @@ internal int AddCommand(CommandProcessorBase commandProcessor, int readFromComma _commands.Add(commandProcessor); + // We will log event(s) about the pipeline execution details if any command in the pipeline requests that. + _needToLog |= commandProcessor.CommandRuntime.LogPipelineExecutionDetail; + // We give the Command a pointer back to the // PipelineProcessor so that it can check whether the // command has been stopped. @@ -478,192 +497,303 @@ internal Array SynchronousExecuteEnumerate(object input) throw new PipelineStoppedException(); } - ExceptionDispatchInfo toRethrowInfo; + bool pipelineSucceeded = false; + ExceptionDispatchInfo toRethrowInfo = null; + CommandProcessorBase commandRequestingUpstreamCommandsToStop = null; + try { - CommandProcessorBase commandRequestingUpstreamCommandsToStop = null; try { - // If the caller specified an input object array, - // we run assuming there is an incoming "stream" - // of objects. This will prevent the one default call - // to ProcessRecord on the first command. - Start(input != AutomationNull.Value); + try + { + // If the caller specified an input object array, we run assuming there is an incoming "stream" + // of objects. This will prevent the one default call to ProcessRecord on the first command. + Start(incomingStream: input != AutomationNull.Value); - // Start has already validated firstcommandProcessor - CommandProcessorBase firstCommandProcessor = _commands[0]; + // Start has already validated firstcommandProcessor + CommandProcessorBase firstCommandProcessor = _commands[0]; - // Add any input to the first command. - if (ExternalInput != null) + // Add any input to the first command. + if (ExternalInput is not null) + { + firstCommandProcessor.CommandRuntime.InputPipe.ExternalReader = ExternalInput; + } + + Inject(input, enumerate: true); + } + catch (PipelineStoppedException) { - firstCommandProcessor.CommandRuntime.InputPipe.ExternalReader - = ExternalInput; + if (_firstTerminatingError?.SourceException is StopUpstreamCommandsException exception) + { + _firstTerminatingError = null; + commandRequestingUpstreamCommandsToStop = exception.RequestingCommandProcessor; + } + else + { + throw; + } } - Inject(input, enumerate: true); + DoCompleteCore(commandRequestingUpstreamCommandsToStop); + pipelineSucceeded = true; } - catch (PipelineStoppedException) + finally { - StopUpstreamCommandsException stopUpstreamCommandsException = - _firstTerminatingError != null - ? _firstTerminatingError.SourceException as StopUpstreamCommandsException - : null; - if (stopUpstreamCommandsException == null) - { - throw; - } - else - { - _firstTerminatingError = null; - commandRequestingUpstreamCommandsToStop = stopUpstreamCommandsException.RequestingCommandProcessor; - } + // Clean up resources for script commands, no matter the pipeline succeeded or not. + // This method catches and handles all exceptions inside, so it will never throw. + Clean(); } - DoCompleteCore(commandRequestingUpstreamCommandsToStop); - - // By this point, we are sure all commandProcessors hosted by the current pipelineProcess are done execution, - // so if there are any redirection pipelineProcessors associated with any of those commandProcessors, we should - // call DoComplete on them. - if (_redirectionPipes != null) + if (pipelineSucceeded) { - foreach (PipelineProcessor redirectPipelineProcessor in _redirectionPipes) + // Now, we are sure all 'commandProcessors' hosted by the current 'pipelineProcessor' are done execution, + // so if there are any redirection 'pipelineProcessors' associated with any of those 'commandProcessors', + // they must have successfully executed 'StartStepping' and 'Step', and thus we should call 'DoComplete' + // on them for completeness. + if (_redirectionPipes is not null) { - redirectPipelineProcessor.DoCompleteCore(null); + foreach (PipelineProcessor redirectPipelineProcessor in _redirectionPipes) + { + // The 'Clean' block for each 'commandProcessor' might still write to a pipe that is associated + // with the redirection 'pipelineProcessor' (e.g. a redirected error pipe), which would trigger + // the call to 'pipelineProcessor.Step'. + // It's possible (though very unlikely) that the call to 'pipelineProcessor.Step' failed with an + // exception, and in such case, the 'pipelineProcessor' would have been disposed, and therefore + // the call to 'DoComplete' will simply return, because '_commands' was already set to null. + redirectPipelineProcessor.DoCompleteCore(null); + } } - } - return RetrieveResults(); + // The 'Clean' blocks write nothing to the output pipe, so the results won't be affected by them. + return RetrieveResults(); + } } catch (RuntimeException e) { - // The error we want to report is the first terminating error - // which occurred during pipeline execution, regardless - // of whether other errors occurred afterward. - toRethrowInfo = _firstTerminatingError ?? ExceptionDispatchInfo.Capture(e); - this.LogExecutionException(toRethrowInfo.SourceException); - } - // NTRAID#Windows Out Of Band Releases-929020-2006/03/14-JonN - catch (System.Runtime.InteropServices.InvalidComObjectException comException) - { - // The error we want to report is the first terminating error - // which occurred during pipeline execution, regardless - // of whether other errors occurred afterward. - if (_firstTerminatingError != null) - { - toRethrowInfo = _firstTerminatingError; - } - else - { - string message = StringUtil.Format(ParserStrings.InvalidComObjectException, comException.Message); - var rte = new RuntimeException(message, comException); - rte.SetErrorId("InvalidComObjectException"); - toRethrowInfo = ExceptionDispatchInfo.Capture(rte); - } - - this.LogExecutionException(toRethrowInfo.SourceException); + toRethrowInfo = GetFirstError(e); } finally { DisposeCommands(); } - // By rethrowing the exception outside of the handler, - // we allow the CLR on X64/IA64 to free from the stack - // the exception records related to this exception. + // By rethrowing the exception outside of the handler, we allow the CLR on X64/IA64 to free from + // the stack the exception records related to this exception. - // The only reason we should get here is if - // an exception should be rethrown. + // The only reason we should get here is if an exception should be rethrown. Diagnostics.Assert(toRethrowInfo != null, "Alternate protocol path failure"); toRethrowInfo.Throw(); - return null; // UNREACHABLE + + // UNREACHABLE + return null; + } + + private ExceptionDispatchInfo GetFirstError(RuntimeException e) + { + // The error we want to report is the first terminating error which occurred during pipeline execution, + // regardless of whether other errors occurred afterward. + var firstError = _firstTerminatingError ?? ExceptionDispatchInfo.Capture(e); + LogExecutionException(firstError.SourceException); + return firstError; + } + + private void ThrowFirstErrorIfExisting(bool logException) + { + if (_firstTerminatingError != null) + { + if (logException) + { + LogExecutionException(_firstTerminatingError.SourceException); + } + + _firstTerminatingError.Throw(); + } } private void DoCompleteCore(CommandProcessorBase commandRequestingUpstreamCommandsToStop) { - // Call DoComplete() for all the commands. DoComplete() will internally call Complete() + if (_commands is null) + { + // This could happen to a redirection pipeline, either for an expression (e.g. 1 > a.txt) + // or for a command (e.g. command > a.txt). + // An exception may be thrown from the call to 'StartStepping' or 'Step' on the pipeline, + // which causes the pipeline commands to be disposed. + return; + } + + // Call DoComplete() for all the commands, which will internally call Complete() MshCommandRuntime lastCommandRuntime = null; - if (_commands != null) + for (int i = 0; i < _commands.Count; i++) { - for (int i = 0; i < _commands.Count; i++) - { - CommandProcessorBase commandProcessor = _commands[i]; + CommandProcessorBase commandProcessor = _commands[i]; - if (commandProcessor == null) - { - // "null command " + i - throw PSTraceSource.NewInvalidOperationException(); - } + if (commandProcessor is null) + { + // An internal error that should not happen. + throw PSTraceSource.NewInvalidOperationException(); + } - if (object.ReferenceEquals(commandRequestingUpstreamCommandsToStop, commandProcessor)) - { - commandRequestingUpstreamCommandsToStop = null; - continue; // do not call DoComplete/EndProcessing on the command that initiated stopping - } + if (object.ReferenceEquals(commandRequestingUpstreamCommandsToStop, commandProcessor)) + { + // Do not call DoComplete/EndProcessing on the command that initiated stopping. + commandRequestingUpstreamCommandsToStop = null; + continue; + } - if (commandRequestingUpstreamCommandsToStop != null) - { - continue; // do not call DoComplete/EndProcessing on commands that were stopped upstream - } + if (commandRequestingUpstreamCommandsToStop is not null) + { + // Do not call DoComplete/EndProcessing on commands that were stopped upstream. + continue; + } - try + try + { + commandProcessor.DoComplete(); + } + catch (PipelineStoppedException) + { + if (_firstTerminatingError?.SourceException is StopUpstreamCommandsException exception) { - commandProcessor.DoComplete(); + _firstTerminatingError = null; + commandRequestingUpstreamCommandsToStop = exception.RequestingCommandProcessor; } - catch (PipelineStoppedException) + else { - StopUpstreamCommandsException stopUpstreamCommandsException = - _firstTerminatingError != null - ? _firstTerminatingError.SourceException as StopUpstreamCommandsException - : null; - if (stopUpstreamCommandsException == null) - { - throw; - } - else - { - _firstTerminatingError = null; - commandRequestingUpstreamCommandsToStop = stopUpstreamCommandsException.RequestingCommandProcessor; - } + throw; } + } - EtwActivity.SetActivityId(commandProcessor.PipelineActivityId); - - // Log a command stopped event - MshLog.LogCommandLifecycleEvent( - commandProcessor.Command.Context, - CommandState.Stopped, - commandProcessor.Command.MyInvocation); + EtwActivity.SetActivityId(commandProcessor.PipelineActivityId); - // Log the execution of a command (not script chunks, as they - // are not commands in and of themselves) - if (commandProcessor.CommandInfo.CommandType != CommandTypes.Script) - { - commandProcessor.CommandRuntime.PipelineProcessor.LogExecutionComplete( - commandProcessor.Command.MyInvocation, commandProcessor.CommandInfo.Name); - } + // Log a command stopped event + MshLog.LogCommandLifecycleEvent( + commandProcessor.Command.Context, + CommandState.Stopped, + commandProcessor.Command.MyInvocation); - lastCommandRuntime = commandProcessor.CommandRuntime; + // Log the execution of a command (not script chunks, as they are not commands in and of themselves). + if (commandProcessor.CommandInfo.CommandType != CommandTypes.Script) + { + LogExecutionComplete(commandProcessor.Command.MyInvocation, commandProcessor.CommandInfo.Name); } + + lastCommandRuntime = commandProcessor.CommandRuntime; } // Log the pipeline completion. - if (lastCommandRuntime != null) + if (lastCommandRuntime is not null) { // Only log the pipeline completion if this wasn't a nested pipeline, as // pipeline state in transcription is associated with the toplevel pipeline - if ((this.LocalPipeline == null) || (!this.LocalPipeline.IsNested)) + if (LocalPipeline is null || !LocalPipeline.IsNested) { lastCommandRuntime.PipelineProcessor.LogPipelineComplete(); } } // If a terminating error occurred, report it now. - if (_firstTerminatingError != null) + // This pipeline could have been stopped asynchronously, by 'Ctrl+c' manually or + // 'PowerShell.Stop' programatically. We need to check and see if that's the case. + // An example: + // - 'Start-Sleep' is running in this pipeline, and 'pipelineProcessor.Stop' gets + // called on a different thread, which sets a 'PipelineStoppedException' object + // to '_firstTerminatingError' and runs 'StopProcessing' on 'Start-Sleep'. + // - The 'StopProcessing' will cause 'Start-Sleep' to return from 'ProcessRecord' + // call, and thus the pipeline execution will move forward to run 'DoComplete' + // for the 'Start-Sleep' command and thus the code flow will reach here. + // For this given example, we need to check '_firstTerminatingError' and throw out + // the 'PipelineStoppedException' if the pipeline was indeed being stopped. + ThrowFirstErrorIfExisting(logException: true); + } + + /// + /// Clean up resources for script commands in this pipeline processor. + /// + /// + /// Exception from a 'Clean' block is not allowed to propagate up and terminate the pipeline + /// so that other 'Clean' blocks can run without being affected. Therefore, this method will + /// catch and handle all exceptions inside, and it will never throw. + /// + private void Clean() + { + if (!_executionStarted || _commands is null) { - this.LogExecutionException(_firstTerminatingError.SourceException); - _firstTerminatingError.Throw(); + // Simply return if the pipeline execution wasn't even started, or the commands of + // the pipeline have already been disposed. + return; } + + // So far, if '_firstTerminatingError' is not null, then it must be a terminating error + // thrown from one of 'Begin/Process/End' blocks. There can be terminating error thrown + // from 'Clean' block as well, which needs to be handled in this method. + // In order to capture the subsequent first terminating error thrown from 'Clean', we + // need to forget the previous '_firstTerminatingError' value before calling 'DoClean' + // on each command processor, so we have to save the old value here and restore later. + ExceptionDispatchInfo oldFirstTerminatingError = _firstTerminatingError; + + // Suspend a stopping pipeline by setting 'IsStopping' to false and restore it afterwards. + bool oldIsStopping = ExceptionHandlingOps.SuspendStoppingPipelineImpl(LocalPipeline); + + try + { + foreach (CommandProcessorBase commandProcessor in _commands) + { + if (commandProcessor is null || !commandProcessor.HasCleanBlock) + { + continue; + } + + try + { + // Forget the terminating error we saw before, so a terminating error thrown + // from the subsequent 'Clean' block can be recorded and handled properly. + _firstTerminatingError = null; + commandProcessor.DoCleanup(); + } + catch (RuntimeException e) + { + // Retrieve and report the terminating error that was thrown in the 'Clean' block. + ExceptionDispatchInfo firstError = GetFirstError(e); + commandProcessor.ReportCleanupError(firstError.SourceException); + } + catch (Exception ex) + { + // Theoretically, only 'RuntimeException' could be thrown out, but we catch + // all and log them here just to be safe. + // Skip special flow control exceptions and log others. + if (ex is not FlowControlException && ex is not HaltCommandException) + { + MshLog.LogCommandHealthEvent(commandProcessor.Context, ex, Severity.Warning); + } + } + } + } + finally + { + _firstTerminatingError = oldFirstTerminatingError; + ExceptionHandlingOps.RestoreStoppingPipelineImpl(LocalPipeline, oldIsStopping); + } + } + + /// + /// Clean up resources for the script commands of a steppable pipeline. + /// + /// + /// The way we handle 'Clean' blocks in 'StartStepping', 'Step', and 'DoComplete' makes sure that: + /// 1. The 'Clean' blocks get to run if any exception is thrown from the pipeline execution. + /// 2. The 'Clean' blocks get to run if the pipeline runs to the end successfully. + /// 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 is how we will expose this functionality to 'SteppablePipeline'. + /// + internal void DoCleanup() + { + Clean(); + DisposeCommands(); } /// @@ -673,98 +803,79 @@ private void DoCompleteCore(CommandProcessorBase commandRequestingUpstreamComman /// The results of the execution. internal Array DoComplete() { - if (Stopping) - { - throw new PipelineStoppedException(); - } - if (!_executionStarted) { throw PSTraceSource.NewInvalidOperationException( PipelineStrings.PipelineNotStarted); } - ExceptionDispatchInfo toRethrowInfo; try { - DoCompleteCore(null); + if (Stopping) + { + throw new PipelineStoppedException(); + } - return RetrieveResults(); - } - catch (RuntimeException e) - { - // The error we want to report is the first terminating error - // which occurred during pipeline execution, regardless - // of whether other errors occurred afterward. - toRethrowInfo = _firstTerminatingError ?? ExceptionDispatchInfo.Capture(e); - this.LogExecutionException(toRethrowInfo.SourceException); - } - // NTRAID#Windows Out Of Band Releases-929020-2006/03/14-JonN - catch (System.Runtime.InteropServices.InvalidComObjectException comException) - { - // The error we want to report is the first terminating error - // which occurred during pipeline execution, regardless - // of whether other errors occurred afterward. - if (_firstTerminatingError != null) + ExceptionDispatchInfo toRethrowInfo; + try { - toRethrowInfo = _firstTerminatingError; + DoCompleteCore(null); + return RetrieveResults(); } - else + catch (RuntimeException e) { - string message = StringUtil.Format(ParserStrings.InvalidComObjectException, comException.Message); - var rte = new RuntimeException(message, comException); - rte.SetErrorId("InvalidComObjectException"); - toRethrowInfo = ExceptionDispatchInfo.Capture(rte); + toRethrowInfo = GetFirstError(e); } - this.LogExecutionException(toRethrowInfo.SourceException); + // By rethrowing the exception outside of the handler, we allow the CLR on X64/IA64 to free from the stack + // the exception records related to this exception. + + // The only reason we should get here is an exception should be rethrown. + Diagnostics.Assert(toRethrowInfo != null, "Alternate protocol path failure"); + toRethrowInfo.Throw(); + + // UNREACHABLE + return null; } finally { + Clean(); DisposeCommands(); } - - // By rethrowing the exception outside of the handler, - // we allow the CLR on X64/IA64 to free from the stack - // the exception records related to this exception. - - // The only reason we should get here is if - // an exception should be rethrown. - Diagnostics.Assert(toRethrowInfo != null, "Alternate protocol path failure"); - toRethrowInfo.Throw(); - return null; // UNREACHABLE } /// - /// This routine starts the stepping process. It is optional to - /// call this but can be useful if you want the begin clauses - /// of the pipeline to be run even when there may not be any input - /// to process as is the case for I/O redirection into a file. We - /// still want the file opened, even if there was nothing to write to it. + /// This routine starts the stepping process. It is optional to call this but can be useful + /// if you want the begin clauses of the pipeline to be run even when there may not be any + /// input to process as is the case for I/O redirection into a file. We still want the file + /// opened, even if there was nothing to write to it. /// /// True if you want to write to this pipeline. internal void StartStepping(bool expectInput) { + bool startSucceeded = false; try { Start(expectInput); + startSucceeded = true; - // If a terminating error occurred, report it now. - if (_firstTerminatingError != null) - { - _firstTerminatingError.Throw(); - } + // Check if this pipeline is being stopped asynchronously. + ThrowFirstErrorIfExisting(logException: false); } - catch (PipelineStoppedException) + catch (Exception e) { + Clean(); DisposeCommands(); - // The error we want to report is the first terminating error - // which occurred during pipeline execution, regardless - // of whether other errors occurred afterward. - if (_firstTerminatingError != null) + if (!startSucceeded && e is PipelineStoppedException) { - _firstTerminatingError.Throw(); + // When a terminating error happens during command execution, PowerShell will first save it + // to '_firstTerminatingError', and then throw a 'PipelineStoppedException' to tear down the + // pipeline. So when the caught exception here is 'PipelineStoppedException', it may not be + // the actual original terminating error. + // In this case, we want to report the first terminating error which occurred during pipeline + // execution, regardless of whether other errors occurred afterward. + ThrowFirstErrorIfExisting(logException: false); } throw; @@ -781,35 +892,37 @@ internal void Stop() // Only call StopProcessing if the pipeline is being stopped // for the first time - if (!RecordFailure(new PipelineStoppedException(), null)) + if (!RecordFailure(new PipelineStoppedException(), command: null)) + { return; + } // Retain copy of _commands in case Dispose() is called List commands = _commands; - if (commands == null) + if (commands is null) + { return; + } + + _pipelineStopTokenSource.Cancel(); // Call StopProcessing() for all the commands. - for (int i = 0; i < commands.Count; i++) + foreach (CommandProcessorBase commandProcessor in commands) { - CommandProcessorBase commandProcessor = commands[i]; - if (commandProcessor == null) { throw PSTraceSource.NewInvalidOperationException(); } -#pragma warning disable 56500 + try { commandProcessor.Command.DoStopProcessing(); } catch (Exception) { - // 2004/04/26-JonN We swallow exceptions - // which occur during StopProcessing. + // We swallow exceptions which occur during StopProcessing. continue; } -#pragma warning restore 56500 } } @@ -852,43 +965,35 @@ internal void Stop() /// internal Array Step(object input) { - if (Stopping) - { - throw new PipelineStoppedException(); - } - + bool injectSucceeded = false; try { Start(true); Inject(input, enumerate: false); + injectSucceeded = true; - // If a terminating error occurred, report it now. - if (_firstTerminatingError != null) - { - _firstTerminatingError.Throw(); - } - + // Check if this pipeline is being stopped asynchronously. + ThrowFirstErrorIfExisting(logException: false); return RetrieveResults(); } - catch (PipelineStoppedException) + catch (Exception e) { + Clean(); DisposeCommands(); - // The error we want to report is the first terminating error - // which occurred during pipeline execution, regardless - // of whether other errors occurred afterward. - if (_firstTerminatingError != null) + if (!injectSucceeded && e is PipelineStoppedException) { - _firstTerminatingError.Throw(); + // When a terminating error happens during command execution, PowerShell will first save it + // to '_firstTerminatingError', and then throw a 'PipelineStoppedException' to tear down the + // pipeline. So when the caught exception here is 'PipelineStoppedException', it may not be + // the actual original terminating error. + // In this case, we want to report the first terminating error which occurred during pipeline + // execution, regardless of whether other errors occurred afterward. + ThrowFirstErrorIfExisting(logException: false); } throw; } - catch (Exception) - { - DisposeCommands(); - throw; - } } /// @@ -935,7 +1040,9 @@ private void Start(bool incomingStream) } if (_executionStarted) + { return; + } if (_commands == null || _commands.Count == 0) { @@ -944,32 +1051,18 @@ private void Start(bool incomingStream) } CommandProcessorBase firstcommandProcessor = _commands[0]; - if (firstcommandProcessor == null - || firstcommandProcessor.CommandRuntime == null) - { - throw PSTraceSource.NewInvalidOperationException( - PipelineStrings.PipelineExecuteRequiresAtLeastOneCommand); - } + ValidateCommandProcessorNotNull(firstcommandProcessor, PipelineStrings.PipelineExecuteRequiresAtLeastOneCommand); // Set the execution scope using the current scope - if (_executionScope == null) - { - _executionScope = firstcommandProcessor.Context.EngineSessionState.CurrentScope; - } + _executionScope ??= firstcommandProcessor.Context.EngineSessionState.CurrentScope; // add ExternalSuccessOutput to the last command CommandProcessorBase LastCommandProcessor = _commands[_commands.Count - 1]; - if (LastCommandProcessor == null - || LastCommandProcessor.CommandRuntime == null) - { - // "PipelineProcessor.Start(): LastCommandProcessor == null" - throw PSTraceSource.NewInvalidOperationException(); - } + ValidateCommandProcessorNotNull(LastCommandProcessor, errorMessage: null); if (ExternalSuccessOutput != null) { - LastCommandProcessor.CommandRuntime.OutputPipe.ExternalWriter - = ExternalSuccessOutput; + LastCommandProcessor.CommandRuntime.OutputPipe.ExternalWriter = ExternalSuccessOutput; } // add ExternalErrorOutput to all commands whose error @@ -983,20 +1076,17 @@ private void Start(bool incomingStream) } // We want the value of PSDefaultParameterValues before possibly changing to the commands scopes. - // This ensures we use the value from the callers scope, not the callees scope. + // This ensures we use the value from the caller's scope, not the callee's scope. IDictionary psDefaultParameterValues = firstcommandProcessor.Context.GetVariableValue(SpecialVariables.PSDefaultParameterValuesVarPath, false) as IDictionary; _executionStarted = true; - // // Allocate the pipeline iteration array; note that the pipeline position for // each command starts at 1 so we need to allocate _commands.Count + 1 items. - // int[] pipelineIterationInfo = new int[_commands.Count + 1]; - // Prepare all commands from Engine's side, - // and make sure they are all valid + // Prepare all commands from Engine's side, and make sure they are all valid for (int i = 0; i < _commands.Count; i++) { CommandProcessorBase commandProcessor = _commands[i]; @@ -1008,8 +1098,6 @@ private void Start(bool incomingStream) // Generate new Activity Id for the thread Guid pipelineActivityId = EtwActivity.CreateActivityId(); - - // commandProcess.PipelineActivityId = new Activity id EtwActivity.SetActivityId(pipelineActivityId); commandProcessor.PipelineActivityId = pipelineActivityId; @@ -1019,20 +1107,14 @@ private void Start(bool incomingStream) CommandState.Started, commandProcessor.Command.MyInvocation); - // Telemetry here - // the type of command should be sent along - // commandProcessor.CommandInfo.CommandType - ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ApplicationType, commandProcessor.Command.CommandInfo.CommandType.ToString()); #if LEGACYTELEMETRY Microsoft.PowerShell.Telemetry.Internal.TelemetryAPI.TraceExecutedCommand(commandProcessor.Command.CommandInfo, commandProcessor.Command.CommandOrigin); #endif - // Log the execution of a command (not script chunks, as they - // are not commands in and of themselves) + // Log the execution of a command (not script chunks, as they are not commands in and of themselves) if (commandProcessor.CommandInfo.CommandType != CommandTypes.Script) { - commandProcessor.CommandRuntime.PipelineProcessor.LogExecutionInfo( - commandProcessor.Command.MyInvocation, commandProcessor.CommandInfo.Name); + LogExecutionInfo(commandProcessor.Command.MyInvocation, commandProcessor.CommandInfo.Name); } InvocationInfo myInfo = commandProcessor.Command.MyInvocation; @@ -1065,8 +1147,7 @@ private void Start(bool incomingStream) } /// - /// Add ExternalErrorOutput to all commands whose error - /// output is not yet claimed. + /// Add ExternalErrorOutput to all commands whose error output is not yet claimed. /// private void SetExternalErrorOutput() { @@ -1075,14 +1156,12 @@ private void SetExternalErrorOutput() for (int i = 0; i < _commands.Count; i++) { CommandProcessorBase commandProcessor = _commands[i]; - Pipe UpstreamPipe = - commandProcessor.CommandRuntime.ErrorOutputPipe; + Pipe errorPipe = commandProcessor.CommandRuntime.ErrorOutputPipe; // check whether a cmdlet is consuming the error pipe - if (!UpstreamPipe.IsRedirected) + if (!errorPipe.IsRedirected) { - UpstreamPipe.ExternalWriter = - ExternalErrorOutput; + errorPipe.ExternalWriter = ExternalErrorOutput; } } } @@ -1093,14 +1172,9 @@ private void SetExternalErrorOutput() /// private void SetupParameterVariables() { - for (int i = 0; i < _commands.Count; i++) + foreach (CommandProcessorBase commandProcessor in _commands) { - CommandProcessorBase commandProcessor = _commands[i]; - if (commandProcessor == null || commandProcessor.CommandRuntime == null) - { - // "null command " + i - throw PSTraceSource.NewInvalidOperationException(); - } + ValidateCommandProcessorNotNull(commandProcessor, errorMessage: null); commandProcessor.CommandRuntime.SetupOutVariable(); commandProcessor.CommandRuntime.SetupErrorVariable(); @@ -1110,6 +1184,16 @@ private void SetupParameterVariables() } } + private static void ValidateCommandProcessorNotNull(CommandProcessorBase commandProcessor, string errorMessage) + { + if (commandProcessor?.CommandRuntime is null) + { + throw errorMessage is null + ? PSTraceSource.NewInvalidOperationException() + : PSTraceSource.NewInvalidOperationException(errorMessage, Array.Empty()); + } + } + /// /// Partially execute the pipeline. The output remains in /// the pipes. @@ -1139,12 +1223,7 @@ private void Inject(object input, bool enumerate) { // Add any input to the first command. CommandProcessorBase firstcommandProcessor = _commands[0]; - if (firstcommandProcessor == null - || firstcommandProcessor.CommandRuntime == null) - { - throw PSTraceSource.NewInvalidOperationException( - PipelineStrings.PipelineExecuteRequiresAtLeastOneCommand); - } + ValidateCommandProcessorNotNull(firstcommandProcessor, PipelineStrings.PipelineExecuteRequiresAtLeastOneCommand); if (input != AutomationNull.Value) { @@ -1182,27 +1261,26 @@ private void Inject(object input, bool enumerate) /// private Array RetrieveResults() { + if (_commands is null) + { + // This could happen to an expression redirection pipeline (e.g. 1 > a.txt). + // An exception may be thrown from the call to 'StartStepping' or 'Step' on the pipeline, + // which causes the pipeline commands to be disposed. + return MshCommandRuntime.StaticEmptyArray; + } + // If the error queue has been linked, it's up to the link to // deal with the output. Don't do anything here... if (!_linkedErrorOutput) { - // Retrieve any accumulated error objects from each of the pipes - // and add them to the error results hash table. - for (int i = 0; i < _commands.Count; i++) + foreach (CommandProcessorBase commandProcessor in _commands) { - CommandProcessorBase commandProcessor = _commands[i]; - if (commandProcessor == null - || commandProcessor.CommandRuntime == null) - { - // "null command or request or ErrorOutputPipe " + i - throw PSTraceSource.NewInvalidOperationException(); - } + ValidateCommandProcessorNotNull(commandProcessor, errorMessage: null); Pipe ErrorPipe = commandProcessor.CommandRuntime.ErrorOutputPipe; if (ErrorPipe.DownstreamCmdlet == null && !ErrorPipe.Empty) { - // 2003/10/02-JonN - // Do not return the same error results more than once + // Clear the error pipe if it's not empty and will not be consumed. ErrorPipe.Clear(); } } @@ -1211,26 +1289,18 @@ private Array RetrieveResults() // If the success queue has been linked, it's up to the link to // deal with the output. Don't do anything here... if (_linkedSuccessOutput) + { return MshCommandRuntime.StaticEmptyArray; + } CommandProcessorBase LastCommandProcessor = _commands[_commands.Count - 1]; - if (LastCommandProcessor == null - || LastCommandProcessor.CommandRuntime == null) - { - // "PipelineProcessor.RetrieveResults(): LastCommandProcessor == null" - throw PSTraceSource.NewInvalidOperationException(); - } + ValidateCommandProcessorNotNull(LastCommandProcessor, errorMessage: null); - Array results = - LastCommandProcessor.CommandRuntime.GetResultsAsArray(); + Array results = LastCommandProcessor.CommandRuntime.GetResultsAsArray(); - // 2003/10/02-JonN // Do not return the same results more than once LastCommandProcessor.CommandRuntime.OutputPipe.Clear(); - - if (results == null) - return MshCommandRuntime.StaticEmptyArray; - return results; + return results is null ? MshCommandRuntime.StaticEmptyArray : results; } /// @@ -1244,12 +1314,7 @@ internal void LinkPipelineSuccessOutput(Pipe pipeToUse) Dbg.Assert(pipeToUse != null, "Caller should verify pipeToUse != null"); CommandProcessorBase LastCommandProcessor = _commands[_commands.Count - 1]; - if (LastCommandProcessor == null - || LastCommandProcessor.CommandRuntime == null) - { - // "PipelineProcessor.RetrieveResults(): LastCommandProcessor == null" - throw PSTraceSource.NewInvalidOperationException(); - } + ValidateCommandProcessorNotNull(LastCommandProcessor, errorMessage: null); LastCommandProcessor.CommandRuntime.OutputPipe = pipeToUse; _linkedSuccessOutput = true; @@ -1259,15 +1324,9 @@ internal void LinkPipelineErrorOutput(Pipe pipeToUse) { Dbg.Assert(pipeToUse != null, "Caller should verify pipeToUse != null"); - for (int i = 0; i < _commands.Count; i++) + foreach (CommandProcessorBase commandProcessor in _commands) { - CommandProcessorBase commandProcessor = _commands[i]; - if (commandProcessor == null - || commandProcessor.CommandRuntime == null) - { - // "null command or request or ErrorOutputPipe " + i - throw PSTraceSource.NewInvalidOperationException(); - } + ValidateCommandProcessorNotNull(commandProcessor, errorMessage: null); if (commandProcessor.CommandRuntime.ErrorOutputPipe.DownstreamCmdlet == null) { @@ -1288,62 +1347,65 @@ internal void LinkPipelineErrorOutput(Pipe pipeToUse) private void DisposeCommands() { // Note that this is not in a lock. - // We do not make Dispose() wait until StopProcessing() - // has completed. + // We do not make Dispose() wait until StopProcessing() has completed. _stopping = true; + if (_commands is null && _redirectionPipes is null) + { + // Commands were already disposed. + return; + } + LogToEventLog(); - if (_commands != null) + if (_commands is not null) { - for (int i = 0; i < _commands.Count; i++) + foreach (CommandProcessorBase commandProcessor in _commands) { - CommandProcessorBase commandProcessor = _commands[i]; - if (commandProcessor != null) + if (commandProcessor is null) { -#pragma warning disable 56500 - // If Dispose throws an exception, record it as a - // pipeline failure and continue disposing cmdlets. - try - { - commandProcessor.CommandRuntime.RemoveVariableListsInPipe(); - commandProcessor.Dispose(); - } - // 2005/04/13-JonN: The only vaguely plausible reason - // for a failure here is an exception in Command.Dispose. - // As such, this should be covered by the overall - // exemption. - catch (Exception e) // Catch-all OK, 3rd party callout. - { - InvocationInfo myInvocation = null; - if (commandProcessor.Command != null) - myInvocation = commandProcessor.Command.MyInvocation; + continue; + } - ProviderInvocationException pie = - e as ProviderInvocationException; - if (pie != null) + // If Dispose throws an exception, record it as a pipeline failure and continue disposing cmdlets. + try + { + // Only cmdlets can have variables defined via the common parameters. + // We handle the cleanup of those variables only if we need to. + if (commandProcessor is CommandProcessor) + { + if (commandProcessor.Command is not PSScriptCmdlet) { - e = new CmdletProviderInvocationException( - pie, - myInvocation); + // For script cmdlets, the variable lists were already removed when exiting a scope. + // So we only need to take care of binary cmdlets here. + commandProcessor.CommandRuntime.RemoveVariableListsInPipe(); } - else - { - e = new CmdletInvocationException( - e, - myInvocation); - // Log a command health event + // Remove the pipeline variable if we need to. + commandProcessor.CommandRuntime.RemovePipelineVariable(); + } - MshLog.LogCommandHealthEvent( - commandProcessor.Command.Context, - e, - Severity.Warning); - } + commandProcessor.Dispose(); + } + catch (Exception e) + { + // The only vaguely plausible reason for a failure here is an exception in 'Command.Dispose'. + // As such, this should be covered by the overall exemption. + InvocationInfo myInvocation = commandProcessor.Command?.MyInvocation; - RecordFailure(e, commandProcessor.Command); + if (e is ProviderInvocationException pie) + { + e = new CmdletProviderInvocationException(pie, myInvocation); } -#pragma warning restore 56500 + else + { + e = new CmdletInvocationException(e, myInvocation); + + // Log a command health event + MshLog.LogCommandHealthEvent(commandProcessor.Command.Context, e, Severity.Warning); + } + + RecordFailure(e, commandProcessor.Command); } } } @@ -1351,25 +1413,31 @@ private void DisposeCommands() _commands = null; // Now dispose any pipes that were used for redirection... - if (_redirectionPipes != null) + if (_redirectionPipes is not null) { foreach (PipelineProcessor redirPipe in _redirectionPipes) { -#pragma warning disable 56500 + if (redirPipe is null) + { + continue; + } + + // Clean resources for script commands. + // It is possible (though very unlikely) that the call to 'Step' on the redirection pipeline failed. + // In such a case, 'Clean' would have run and the 'pipelineProcessor' would have been disposed. + // Therefore, calling 'Clean' again will simply return, because '_commands' was already set to null. + redirPipe.Clean(); + // The complicated logic of disposing the commands is taken care // of through recursion, this routine should not be getting any // exceptions... try { - if (redirPipe != null) - { - redirPipe.Dispose(); - } + redirPipe.Dispose(); } catch (Exception) { } -#pragma warning restore 56500 } } @@ -1383,7 +1451,7 @@ private void DisposeCommands() /// /// Error which terminated the pipeline. /// Command against which to log SecondFailure. - /// True iff the pipeline was not already stopped. + /// True if-and-only-if the pipeline was not already stopped. internal bool RecordFailure(Exception e, InternalCommand command) { bool wasStopping = false; @@ -1393,11 +1461,9 @@ internal bool RecordFailure(Exception e, InternalCommand command) { _firstTerminatingError = ExceptionDispatchInfo.Capture(e); } - // 905900-2005/05/12 - // Drop5: Error Architecture: Log/trace second and subsequent RecordFailure - // Note that the pipeline could have been stopped asynchronously - // before hitting the error, therefore we check whether - // firstTerminatingError is PipelineStoppedException. + // Error Architecture: Log/trace second and subsequent RecordFailure. + // Note that the pipeline could have been stopped asynchronously before hitting the error, + // therefore we check whether '_firstTerminatingError' is 'PipelineStoppedException'. else if (_firstTerminatingError.SourceException is not PipelineStoppedException && command?.Context != null) { @@ -1416,11 +1482,10 @@ internal bool RecordFailure(Exception e, InternalCommand command) ex.GetType().Name, ex.StackTrace ); - InvalidOperationException ioe - = new InvalidOperationException(message, ex); + MshLog.LogCommandHealthEvent( command.Context, - ioe, + new InvalidOperationException(message, ex), Severity.Warning); } } diff --git a/src/System.Management.Automation/engine/regex.cs b/src/System.Management.Automation/engine/regex.cs index 3f827dc08f8..cead035f057 100644 --- a/src/System.Management.Automation/engine/regex.cs +++ b/src/System.Management.Automation/engine/regex.cs @@ -57,12 +57,12 @@ public sealed class WildcardPattern // The size is less than MaxShortPath = 260. private const int StackAllocThreshold = 256; + // chars that are considered special in a wildcard pattern + private const string SpecialChars = "*?[]`"; + // we convert a wildcard pattern to a predicate private Predicate _isMatch; - // chars that are considered special in a wildcard pattern - private static readonly char[] s_specialChars = new[] { '*', '?', '[', ']', '`' }; - // static match-all delegate that is shared by all WildcardPattern instances private static readonly Predicate s_matchAll = _ => true; @@ -173,8 +173,8 @@ StringComparison GetStringComparison() return; } - int index = Pattern.IndexOfAny(s_specialChars); - if (index == -1) + int index = Pattern.AsSpan().IndexOfAny(SpecialChars); + if (index < 0) { // No special characters present in the pattern, so we can just do a string comparison. _isMatch = str => string.Equals(str, Pattern, GetStringComparison()); @@ -238,9 +238,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; } @@ -314,6 +314,43 @@ public static bool ContainsWildcardCharacters(string pattern) return result; } + /// + /// Checks if the string contains a left bracket "[" followed by a right bracket "]" after any number of characters. + /// + /// The string to check. + /// Returns true if the string contains both a left and right bracket "[" "]" and if the right bracket comes after the left bracket. + internal static bool ContainsRangeWildcard(string pattern) + { + if (string.IsNullOrEmpty(pattern)) + { + return false; + } + + bool foundStart = false; + bool result = false; + for (int index = 0; index < pattern.Length; ++index) + { + if (pattern[index] is '[') + { + foundStart = true; + continue; + } + + if (foundStart && pattern[index] is ']') + { + result = true; + break; + } + + if (pattern[index] == escapeChar) + { + ++index; + } + } + + return result; + } + /// /// Unescapes any escaped characters in the input string. /// @@ -432,7 +469,6 @@ public string ToWql() /// /// Thrown when a wildcard pattern is invalid. /// - [Serializable] public class WildcardPatternException : RuntimeException { /// @@ -447,10 +483,7 @@ public class WildcardPatternException : RuntimeException internal WildcardPatternException(ErrorRecord errorRecord) : base(RetrieveMessage(errorRecord)) { - if (errorRecord == null) - { - throw new ArgumentNullException(nameof(errorRecord)); - } + ArgumentNullException.ThrowIfNull(errorRecord); _errorRecord = errorRecord; } @@ -491,10 +524,11 @@ public WildcardPatternException(string message, /// /// Serialization information. /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected WildcardPatternException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } } @@ -1001,7 +1035,7 @@ internal bool IsMatch(string str) } } - private class PatternPositionsVisitor : IDisposable + private sealed class PatternPositionsVisitor : IDisposable { private readonly int _lengthOfPattern; @@ -1122,7 +1156,7 @@ public override void ProcessEndOfString( } } - private class LiteralCharacterElement : QuestionMarkElement + private sealed class LiteralCharacterElement : QuestionMarkElement { private readonly char _literalCharacter; @@ -1148,7 +1182,7 @@ public override void ProcessStringCharacter( } } - private class BracketExpressionElement : QuestionMarkElement + private sealed class BracketExpressionElement : QuestionMarkElement { private readonly Regex _regex; @@ -1173,7 +1207,7 @@ public override void ProcessStringCharacter( } } - private class AsterixElement : PatternElement + private sealed class AsterixElement : PatternElement { public override void ProcessStringCharacter( char currentStringCharacter, @@ -1197,7 +1231,7 @@ public override void ProcessEndOfString( } } - private class MyWildcardPatternParser : WildcardPatternParser + private sealed class MyWildcardPatternParser : WildcardPatternParser { private readonly List _patternElements = new List(); private CharacterNormalizer _characterNormalizer; diff --git a/src/System.Management.Automation/engine/remoting/client/ClientMethodExecutor.cs b/src/System.Management.Automation/engine/remoting/client/ClientMethodExecutor.cs index 120ca07a5af..73c432158f3 100644 --- a/src/System.Management.Automation/engine/remoting/client/ClientMethodExecutor.cs +++ b/src/System.Management.Automation/engine/remoting/client/ClientMethodExecutor.cs @@ -13,7 +13,7 @@ namespace System.Management.Automation.Remoting /// /// Executes methods on the client. /// - internal class ClientMethodExecutor + internal sealed class ClientMethodExecutor { /// /// Transport manager. @@ -133,7 +133,10 @@ internal static void Dispatch( /// private static bool IsRunspacePushed(PSHost host) { - if (!(host is IHostSupportsInteractiveSession host2)) { return false; } + if (!(host is IHostSupportsInteractiveSession host2)) + { + return false; + } // IsRunspacePushed can throw (not implemented exception) try @@ -159,10 +162,7 @@ internal void Execute(PSDataCollectionStream errorStream) { try { - if (_clientHost.UI != null) - { - _clientHost.UI.WriteErrorLine(errorRecord.ToString()); - } + _clientHost.UI?.WriteErrorLine(errorRecord.ToString()); } catch (Exception) { diff --git a/src/System.Management.Automation/engine/remoting/client/ClientRemotePowerShell.cs b/src/System.Management.Automation/engine/remoting/client/ClientRemotePowerShell.cs index 4ea06ea2feb..631e49fc189 100644 --- a/src/System.Management.Automation/engine/remoting/client/ClientRemotePowerShell.cs +++ b/src/System.Management.Automation/engine/remoting/client/ClientRemotePowerShell.cs @@ -15,11 +15,11 @@ namespace System.Management.Automation.Runspaces.Internal /// PowerShell client side proxy base which handles invocation /// of powershell on a remote machine. /// - internal class ClientRemotePowerShell : IDisposable + 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 @@ -166,10 +166,7 @@ internal void UnblockCollections() outputstream.Close(); errorstream.Close(); - if (inputstream != null) - { - inputstream.Close(); - } + inputstream?.Close(); } /// @@ -902,28 +899,28 @@ private void HandleRobustConnectionNotification( #endregion Private Methods - #region Protected Members - - protected ObjectStreamBase inputstream; - protected ObjectStreamBase errorstream; - protected PSInformationalBuffers informationalBuffers; - protected PowerShell shell; - protected Guid clientRunspacePoolId; - protected bool noInput; - protected PSInvocationSettings settings; - protected ObjectStreamBase outputstream; - protected string computerName; - protected ClientPowerShellDataStructureHandler dataStructureHandler; - protected bool stopCalled = false; - protected PSHost hostToUse; - protected RemoteRunspacePoolInternal runspacePool; - - protected const string WRITE_DEBUG_LINE = "WriteDebugLine"; - protected const string WRITE_VERBOSE_LINE = "WriteVerboseLine"; - protected const string WRITE_WARNING_LINE = "WriteWarningLine"; - protected const string WRITE_PROGRESS = "WriteProgress"; - - protected bool initialized = false; + #region Private Fields + + private ObjectStreamBase inputstream; + private ObjectStreamBase errorstream; + private PSInformationalBuffers informationalBuffers; + private readonly PowerShell shell; + private readonly Guid clientRunspacePoolId; + private bool noInput; + private PSInvocationSettings settings; + private ObjectStreamBase outputstream; + private readonly string computerName; + private ClientPowerShellDataStructureHandler dataStructureHandler; + private bool stopCalled = false; + private PSHost hostToUse; + private readonly RemoteRunspacePoolInternal runspacePool; + + private const string WRITE_DEBUG_LINE = "WriteDebugLine"; + private const string WRITE_VERBOSE_LINE = "WriteVerboseLine"; + private const string WRITE_WARNING_LINE = "WriteWarningLine"; + private const string WRITE_PROGRESS = "WriteProgress"; + + private bool initialized = false; /// /// This queue is for the state change events that resulted in closing the underlying /// datastructure handler. We cannot send the state back to the upper layers until @@ -933,33 +930,20 @@ private void HandleRobustConnectionNotification( private PSConnectionRetryStatus _connectionRetryStatus = PSConnectionRetryStatus.None; - #endregion Protected Members + #endregion Private Fields #region IDisposable /// - /// Public interface for dispose. + /// Release all resources. /// public void Dispose() { - Dispose(true); - - GC.SuppressFinalize(this); + // inputstream.Dispose(); + // outputstream.Dispose(); + // errorstream.Dispose(); } - /// - /// Release all resources. - /// - /// If true, release all managed resources. - protected void Dispose(bool disposing) - { - if (disposing) - { - // inputstream.Dispose(); - // outputstream.Dispose(); - // errorstream.Dispose(); - } - } #endregion IDisposable } diff --git a/src/System.Management.Automation/engine/remoting/client/Job.cs b/src/System.Management.Automation/engine/remoting/client/Job.cs index 657b72bef7a..b3a0135afc7 100644 --- a/src/System.Management.Automation/engine/remoting/client/Job.cs +++ b/src/System.Management.Automation/engine/remoting/client/Job.cs @@ -93,7 +93,6 @@ public enum JobState /// Defines exception which is thrown when state of the PSJob is different /// from the expected state. /// - [Serializable] public class InvalidJobStateException : SystemException { /// @@ -191,10 +190,11 @@ internal InvalidJobStateException(JobState currentState) /// The that contains contextual information /// about the source or destination. /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected InvalidJobStateException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion @@ -633,10 +633,7 @@ public IList ChildJobs { lock (syncObject) { - if (_childJobs == null) - { - _childJobs = new List(); - } + _childJobs ??= new List(); } } @@ -644,7 +641,7 @@ public IList ChildJobs } } - /// + /// /// Success status of the command execution. /// public abstract string StatusMessage { get; } @@ -1014,11 +1011,17 @@ protected virtual void DoUnloadJobStreams() /// public void LoadJobStreams() { - if (_jobStreamsLoaded) return; + if (_jobStreamsLoaded) + { + return; + } lock (syncObject) { - if (_jobStreamsLoaded) return; + if (_jobStreamsLoaded) + { + return; + } _jobStreamsLoaded = true; } @@ -1451,10 +1454,7 @@ internal void SetJobState(JobState state, Exception reason) { lock (syncObject) { - if (_finished != null) - { - _finished.Set(); - } + _finished?.Set(); } } #pragma warning restore 56500 @@ -2048,7 +2048,7 @@ private static void SubmitAndWaitForConnect(List connectJobO /// /// Simple throttle operation class for connecting jobs. /// - private class ConnectJobOperation : IThrottleOperation + private sealed class ConnectJobOperation : IThrottleOperation { private readonly PSRemotingChildJob _psRemoteChildJob; @@ -2360,7 +2360,7 @@ private void SetStatusMessage() #region finish logic - // This variable is set to true if atleast one child job failed. + // This variable is set to true if at least one child job failed. private bool _atleastOneChildJobFailed = false; // count of number of child jobs which have finished @@ -3226,7 +3226,7 @@ protected virtual void HandleOperationComplete(object sender, OperationStateEven // no pipeline is created and no pipeline state changed event is raised. // We can wait for throttle complete, but it is raised only when all the // operations are completed and this means that status of job is not updated - // untill Operation Complete. + // until Operation Complete. ExecutionCmdletHelper helper = sender as ExecutionCmdletHelper; Dbg.Assert(helper != null, "Sender of OperationComplete has to be ExecutionCmdletHelper"); @@ -3313,8 +3313,7 @@ protected void ProcessJobFailure(ExecutionCmdletHelper helper, out Exception fai errorId = "InvalidSessionState"; if (!string.IsNullOrEmpty(failureException.Source)) { - errorId = string.Format(System.Globalization.CultureInfo.InvariantCulture, - "{0},{1}", errorId, failureException.Source); + errorId = string.Create(System.Globalization.CultureInfo.InvariantCulture, $"{errorId},{failureException.Source}"); } } @@ -3371,13 +3370,10 @@ protected void ProcessJobFailure(ExecutionCmdletHelper helper, out Exception fai } } - if (failureException == null) - { - failureException = new RuntimeException( - PSRemotingErrorInvariants.FormatResourceString( - RemotingErrorIdStrings.RemoteRunspaceOpenUnknownState, - runspace.RunspaceStateInfo.State)); - } + failureException ??= new RuntimeException( + PSRemotingErrorInvariants.FormatResourceString( + RemotingErrorIdStrings.RemoteRunspaceOpenUnknownState, + runspace.RunspaceStateInfo.State)); failureErrorRecord = new ErrorRecord(failureException, targetObject, fullyQualifiedErrorId, ErrorCategory.OpenError, @@ -3926,7 +3922,7 @@ public override void SetBreakpoints(IEnumerable breakpoints, int? ru /// /// Id of the breakpoint you want. /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. - /// A a breakpoint with the specified id. + /// A breakpoint with the specified id. public override Breakpoint GetBreakpoint(int id, int? runspaceId) => _wrappedDebugger.GetBreakpoint(id, runspaceId); @@ -4080,10 +4076,7 @@ public override void SetDebuggerStepMode(bool enabled) internal void CheckStateAndRaiseStopEvent() { RemoteDebugger remoteDebugger = _wrappedDebugger as RemoteDebugger; - if (remoteDebugger != null) - { - remoteDebugger.CheckStateAndRaiseStopEvent(); - } + remoteDebugger?.CheckStateAndRaiseStopEvent(); } /// @@ -4131,13 +4124,7 @@ private Pipeline DrainAndBlockRemoteOutput() return null; } - private static void RestoreRemoteOutput(Pipeline runningCmd) - { - if (runningCmd != null) - { - runningCmd.ResumeIncomingData(); - } - } + private static void RestoreRemoteOutput(Pipeline runningCmd) => runningCmd?.ResumeIncomingData(); private void HandleBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) { diff --git a/src/System.Management.Automation/engine/remoting/client/Job2.cs b/src/System.Management.Automation/engine/remoting/client/Job2.cs index ae5976ec9f8..a5ac9e5ec0b 100644 --- a/src/System.Management.Automation/engine/remoting/client/Job2.cs +++ b/src/System.Management.Automation/engine/remoting/client/Job2.cs @@ -93,8 +93,7 @@ public List StartParameters { lock (_syncobject) { - if (_parameters == null) - _parameters = new List(); + _parameters ??= new List(); } } @@ -506,7 +505,7 @@ public sealed class ContainerParentJob : Job2 private const int DisposedTrue = 1; private const int DisposedFalse = 0; - // This variable is set to true if atleast one child job failed. + // This variable is set to true if at least one child job failed. // count of number of child jobs which have finished private int _finishedChildJobsCount = 0; @@ -707,10 +706,8 @@ public ContainerParentJob(string command, string name, string jobType) public void AddChildJob(Job2 childJob) { AssertNotDisposed(); - if (childJob == null) - { - throw new ArgumentNullException(nameof(childJob)); - } + + ArgumentNullException.ThrowIfNull(childJob); _tracer.WriteMessage(TraceClassName, "AddChildJob", Guid.Empty, childJob, "Adding Child to Parent with InstanceId : ", InstanceId.ToString()); @@ -2022,11 +2019,8 @@ protected override void Dispose(bool disposing) job.Dispose(); } - if (_jobRunning != null) - _jobRunning.Dispose(); - - if (_jobSuspendedOrAborted != null) - _jobSuspendedOrAborted.Dispose(); + _jobRunning?.Dispose(); + _jobSuspendedOrAborted?.Dispose(); } finally { @@ -2038,7 +2032,7 @@ private string ConstructLocation() { if (ChildJobs == null || ChildJobs.Count == 0) return string.Empty; - string location = ChildJobs.Select((job) => job.Location).Aggregate((s1, s2) => s1 + ',' + s2); + string location = ChildJobs.Select(static (job) => job.Location).Aggregate((s1, s2) => s1 + ',' + s2); return location; } @@ -2112,7 +2106,6 @@ private void UnregisterAllJobEvents() /// Container exception for jobs that can map errors and exceptions /// to specific lines in their input. /// - [Serializable] public class JobFailedException : SystemException { /// @@ -2157,11 +2150,10 @@ public JobFailedException(Exception innerException, ScriptExtent displayScriptPo /// /// Serialization info. /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected JobFailedException(SerializationInfo serializationInfo, StreamingContext streamingContext) - : base(serializationInfo, streamingContext) { - _reason = (Exception)serializationInfo.GetValue("Reason", typeof(Exception)); - _displayScriptPosition = (ScriptExtent)serializationInfo.GetValue("DisplayScriptPosition", typeof(ScriptExtent)); + throw new NotSupportedException(); } /// @@ -2178,22 +2170,6 @@ protected JobFailedException(SerializationInfo serializationInfo, StreamingConte private readonly ScriptExtent _displayScriptPosition; - /// - /// Gets the information for serialization. - /// - /// The standard SerializationInfo. - /// The standard StreaminContext. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - throw new ArgumentNullException(nameof(info)); - - base.GetObjectData(info, context); - - info.AddValue("Reason", _reason); - info.AddValue("DisplayScriptPosition", _displayScriptPosition); - } - /// /// Returns the reason for this exception. /// diff --git a/src/System.Management.Automation/engine/remoting/client/JobManager.cs b/src/System.Management.Automation/engine/remoting/client/JobManager.cs index f907cf922d8..3b2baec6654 100644 --- a/src/System.Management.Automation/engine/remoting/client/JobManager.cs +++ b/src/System.Management.Automation/engine/remoting/client/JobManager.cs @@ -158,7 +158,11 @@ internal static void SaveJobId(Guid instanceId, int id, string typeName) { lock (s_syncObject) { - if (s_jobIdsForReuse.ContainsKey(instanceId)) return; + if (s_jobIdsForReuse.ContainsKey(instanceId)) + { + return; + } + s_jobIdsForReuse.Add(instanceId, new KeyValuePair(id, typeName)); } } @@ -176,10 +180,7 @@ internal static void SaveJobId(Guid instanceId, int id, string typeName) /// public Job2 NewJob(JobDefinition definition) { - if (definition == null) - { - throw new ArgumentNullException(nameof(definition)); - } + ArgumentNullException.ThrowIfNull(definition); JobSourceAdapter sourceAdapter = GetJobSourceAdapter(definition); Job2 newJob; @@ -216,10 +217,7 @@ public Job2 NewJob(JobDefinition definition) /// public Job2 NewJob(JobInvocationInfo specification) { - if (specification == null) - { - throw new ArgumentNullException(nameof(specification)); - } + ArgumentNullException.ThrowIfNull(specification); if (specification.Definition == null) { @@ -593,7 +591,11 @@ private List GetFilteredJobs( } #pragma warning restore 56500 - if (jobs == null) continue; + if (jobs == null) + { + continue; + } + allJobs.AddRange(jobs); } } @@ -758,7 +760,10 @@ private Job2 GetJobThroughId(Guid guid, int id, Cmdlet cmdlet, bool writeErro WriteErrorOrWarning(writeErrorOnException, cmdlet, exception, "JobSourceAdapterGetJobByInstanceIdError", sourceAdapter); } - if (job == null) continue; + if (job == null) + { + continue; + } if (writeObject) { @@ -934,12 +939,20 @@ internal bool RemoveJob(Job2 job, Cmdlet cmdlet, bool writeErrorOnException, boo // sourceAdapter.GetJobByInstanceId() threw unknown exception. _tracer.TraceException(exception); - if (throwExceptions) throw; + if (throwExceptions) + { + throw; + } + WriteErrorOrWarning(writeErrorOnException, cmdlet, exception, "JobSourceAdapterGetJobError", sourceAdapter); } #pragma warning restore 56500 - if (foundJob == null) continue; + if (foundJob == null) + { + continue; + } + jobFound = true; RemoveJobIdForReuse(foundJob); @@ -957,7 +970,11 @@ internal bool RemoveJob(Job2 job, Cmdlet cmdlet, bool writeErrorOnException, boo // sourceAdapter.RemoveJob() threw unknown exception. _tracer.TraceException(exception); - if (throwExceptions) throw; + if (throwExceptions) + { + throw; + } + WriteErrorOrWarning(writeErrorOnException, cmdlet, exception, "JobSourceAdapterRemoveJobError", sourceAdapter); } #pragma warning restore 56500 diff --git a/src/System.Management.Automation/engine/remoting/client/JobSourceAdapter.cs b/src/System.Management.Automation/engine/remoting/client/JobSourceAdapter.cs index f5a5022d705..9f6297fbfab 100644 --- a/src/System.Management.Automation/engine/remoting/client/JobSourceAdapter.cs +++ b/src/System.Management.Automation/engine/remoting/client/JobSourceAdapter.cs @@ -20,7 +20,6 @@ namespace System.Management.Automation /// /// The actual implementation of this class will /// happen in M2 - [Serializable] public class JobDefinition : ISerializable { private string _name; @@ -172,7 +171,6 @@ public virtual void GetObjectData(SerializationInfo info, StreamingContext conte /// CommandParameterCollection adds a public /// constructor.The actual implementation of /// this class will happen in M2 - [Serializable] public class JobInvocationInfo : ISerializable { /// @@ -361,7 +359,7 @@ private static CommandParameterCollection ConvertDictionaryToParameterCollection return null; CommandParameterCollection paramCollection = new CommandParameterCollection(); foreach (CommandParameter paramItem in - parameters.Select(param => new CommandParameter(param.Key, param.Value))) + parameters.Select(static param => new CommandParameter(param.Key, param.Value))) { paramCollection.Add(paramItem); } diff --git a/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs b/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs index 94584c613f5..167b4219147 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. /// - internal class RemoteRunspacePoolInternal : RunspacePoolInternal, IDisposable + internal sealed class RemoteRunspacePoolInternal : RunspacePoolInternal { #region Constructor @@ -81,7 +81,7 @@ internal RemoteRunspacePoolInternal(int minRunspaces, minPoolSz.ToString(CultureInfo.InvariantCulture), maxPoolSz.ToString(CultureInfo.InvariantCulture)); - _connectionInfo = connectionInfo.InternalCopy(); + _connectionInfo = connectionInfo.Clone(); this.host = host; ApplicationArguments = applicationArguments; @@ -128,7 +128,7 @@ internal RemoteRunspacePoolInternal(Guid instanceId, string name, bool isDisconn if (connectionInfo is WSManConnectionInfo) { - _connectionInfo = connectionInfo.InternalCopy(); + _connectionInfo = connectionInfo.Clone(); } else { @@ -347,7 +347,7 @@ internal override bool SetMaxRunspaces(int maxRunspaces) return true; } - // sending the message should be done withing the lock + // sending the message should be done within the lock // to ensure that multiple calls to SetMaxRunspaces // will be executed on the server in the order in which // they were called in the client @@ -410,7 +410,7 @@ internal override bool SetMinRunspaces(int minRunspaces) return true; } - // sending the message should be done withing the lock + // sending the message should be done within the lock // to ensure that multiple calls to SetMinRunspaces // will be executed on the server in the order in which // they were called in the client @@ -452,7 +452,7 @@ internal override int GetAvailableRunspaces() // return maxrunspaces if (stateInfo.State == RunspacePoolState.Opened) { - // sending the message should be done withing the lock + // sending the message should be done within the lock // to ensure that multiple calls to GetAvailableRunspaces // will be executed on the server in the order in which // they were called in the client @@ -1215,7 +1215,7 @@ public override Collection CreateDisconnectedPowerShells(RunspacePoo return psCollection; } - /// + /// /// Returns RunspacePool capabilities. /// /// RunspacePoolCapability. @@ -1826,20 +1826,14 @@ private void ResetDisconnectedOnExpiresOn() { // Reset DisconnectedOn/ExpiresOn WSManConnectionInfo wsManConnectionInfo = _connectionInfo as WSManConnectionInfo; - if (wsManConnectionInfo != null) - { - wsManConnectionInfo.NullDisconnectedExpiresOn(); - } + wsManConnectionInfo?.NullDisconnectedExpiresOn(); } private void UpdateDisconnectedExpiresOn() { // Set DisconnectedOn/ExpiresOn for disconnected session. WSManConnectionInfo wsManConnectionInfo = _connectionInfo as WSManConnectionInfo; - if (wsManConnectionInfo != null) - { - wsManConnectionInfo.SetDisconnectedExpiresOnToNow(); - } + wsManConnectionInfo?.SetDisconnectedExpiresOnToNow(); } /// @@ -1899,21 +1893,11 @@ private void WaitAndRaiseConnectEventsProc(object state) #region IDisposable - /// - /// Public method for Dispose. - /// - public void Dispose() - { - Dispose(true); - - GC.SuppressFinalize(this); - } - /// /// Release all resources. /// /// If true, release all managed resources. - public override void Dispose(bool disposing) + protected override void Dispose(bool disposing) { // dispose the base class before disposing dataStructure handler. base.Dispose(disposing); @@ -2039,7 +2023,7 @@ internal static Collection GetRemoteCommands(Guid shellId, WSManConnec powerShell.AddCommand("Get-WSManInstance"); // Add parameters to enumerate commands. - string filterStr = string.Format(CultureInfo.InvariantCulture, "ShellId='{0}'", shellId.ToString().ToUpperInvariant()); + string filterStr = string.Create(CultureInfo.InvariantCulture, $"ShellId='{shellId.ToString().ToUpperInvariant()}'"); powerShell.AddParameter("ResourceURI", @"Shell/Command"); powerShell.AddParameter("Enumerate", true); powerShell.AddParameter("Dialect", "Selector"); diff --git a/src/System.Management.Automation/engine/remoting/client/RemotingErrorRecord.cs b/src/System.Management.Automation/engine/remoting/client/RemotingErrorRecord.cs index 0de48ff6dfc..527bdcf9e21 100644 --- a/src/System.Management.Automation/engine/remoting/client/RemotingErrorRecord.cs +++ b/src/System.Management.Automation/engine/remoting/client/RemotingErrorRecord.cs @@ -11,7 +11,6 @@ namespace System.Management.Automation.Runspaces /// /// Error record in remoting cases. /// - [Serializable] public class RemotingErrorRecord : ErrorRecord { /// @@ -57,32 +56,15 @@ private RemotingErrorRecord( #region ISerializable implementation - /// - /// Serializer method for class. - /// - /// Serializer information. - /// Streaming context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw PSTraceSource.NewArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - - info.AddValue("RemoteErrorRecord_OriginInfo", _originInfo); - } - /// /// Deserializer constructor. /// /// Serializer information. /// Streaming context. - protected RemotingErrorRecord(SerializationInfo info, StreamingContext context) - : base(info, context) + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + protected RemotingErrorRecord(SerializationInfo info, StreamingContext context) : base(info, context) { - _originInfo = (OriginInfo)info.GetValue("RemoteErrorRecord_OriginInfo", typeof(OriginInfo)); + throw new NotSupportedException(); } #endregion @@ -108,7 +90,7 @@ internal override ErrorRecord WrapException(Exception replaceParentContainsError /// /// Progress record containing origin information. /// - [DataContract()] + [DataContract] public class RemotingProgressRecord : ProgressRecord { /// @@ -119,7 +101,7 @@ public OriginInfo OriginInfo get { return _originInfo; } } - [DataMemberAttribute()] + [DataMember] private readonly OriginInfo _originInfo; /// @@ -149,7 +131,7 @@ public RemotingProgressRecord(ProgressRecord progressRecord, OriginInfo originIn private static ProgressRecord Validate(ProgressRecord progressRecord) { - if (progressRecord == null) throw new ArgumentNullException(nameof(progressRecord)); + ArgumentNullException.ThrowIfNull(progressRecord); return progressRecord; } } @@ -157,7 +139,7 @@ private static ProgressRecord Validate(ProgressRecord progressRecord) /// /// Warning record containing origin information. /// - [DataContract()] + [DataContract] public class RemotingWarningRecord : WarningRecord { /// @@ -168,7 +150,7 @@ public OriginInfo OriginInfo get { return _originInfo; } } - [DataMemberAttribute()] + [DataMember] private readonly OriginInfo _originInfo; /// @@ -199,7 +181,7 @@ internal RemotingWarningRecord( /// /// Debug record containing origin information. /// - [DataContract()] + [DataContract] public class RemotingDebugRecord : DebugRecord { /// @@ -210,7 +192,7 @@ public OriginInfo OriginInfo get { return _originInfo; } } - [DataMemberAttribute()] + [DataMember] private readonly OriginInfo _originInfo; /// @@ -228,7 +210,7 @@ public RemotingDebugRecord(string message, OriginInfo originInfo) /// /// Verbose record containing origin information. /// - [DataContract()] + [DataContract] public class RemotingVerboseRecord : VerboseRecord { /// @@ -239,7 +221,7 @@ public OriginInfo OriginInfo get { return _originInfo; } } - [DataMemberAttribute()] + [DataMember] private readonly OriginInfo _originInfo; /// @@ -257,7 +239,7 @@ public RemotingVerboseRecord(string message, OriginInfo originInfo) /// /// Information record containing origin information. /// - [DataContract()] + [DataContract] public class RemotingInformationRecord : InformationRecord { /// @@ -268,7 +250,7 @@ public OriginInfo OriginInfo get { return _originInfo; } } - [DataMemberAttribute()] + [DataMember] private readonly OriginInfo _originInfo; /// @@ -294,8 +276,7 @@ namespace System.Management.Automation.Remoting /// In case of output objects, the information /// should directly be added to the object as /// properties - [Serializable] - [DataContract()] + [DataContract] public class OriginInfo { /// @@ -311,7 +292,7 @@ public string PSComputerName } } - [DataMemberAttribute()] + [DataMember] private readonly string _computerName; /// @@ -326,7 +307,7 @@ public Guid RunspaceID } } - [DataMemberAttribute()] + [DataMember] private readonly Guid _runspaceID; /// @@ -346,7 +327,7 @@ public Guid InstanceID } } - [DataMemberAttribute()] + [DataMember] private Guid _instanceId; /// diff --git a/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs b/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs index 8b8908b6e61..4c5aec933b4 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. /// - internal class ClientRunspacePoolDataStructureHandler : IDisposable + internal sealed class ClientRunspacePoolDataStructureHandler : IDisposable { private bool _reconnecting = false; @@ -275,10 +275,7 @@ internal void DispatchMessageToPowerShell(RemoteDataObject rcvdData) // if a data structure handler does not exist it means // the association has been removed - // discard messages - if (dsHandler != null) - { - dsHandler.ProcessReceivedData(rcvdData); - } + dsHandler?.ProcessReceivedData(rcvdData); } /// @@ -800,10 +797,7 @@ private void HandleReadyForDisconnect(object sender, EventArgs args) return; } - if (_preparingForDisconnectList.Contains(bcmdTM)) - { - _preparingForDisconnectList.Remove(bcmdTM); - } + _preparingForDisconnectList.Remove(bcmdTM); if (_preparingForDisconnectList.Count == 0) { @@ -813,7 +807,7 @@ private void HandleReadyForDisconnect(object sender, EventArgs args) // what thread this callback is made from. If it was made from a transport // callback event then a deadlock may occur when DisconnectAsync is called on // that same thread. - ThreadPool.QueueUserWorkItem(new WaitCallback(StartDisconnectAsync), RemoteSession); + ThreadPool.QueueUserWorkItem(new WaitCallback(StartDisconnectAsync)); } } } @@ -821,10 +815,18 @@ private void HandleReadyForDisconnect(object sender, EventArgs args) /// /// WaitCallback method to start an asynchronous disconnect. /// - /// - private void StartDisconnectAsync(object remoteSession) + /// + private void StartDisconnectAsync(object state) { - ((ClientRemoteSession)remoteSession).DisconnectAsync(); + var remoteSession = RemoteSession; + try + { + remoteSession?.DisconnectAsync(); + } + catch + { + // remoteSession may have already been disposed resulting in unexpected exceptions. + } } /// @@ -979,7 +981,7 @@ public void Dispose(bool disposing) /// Base class for ClientPowerShellDataStructureHandler to handle all /// references. /// - internal class ClientPowerShellDataStructureHandler + internal sealed class ClientPowerShellDataStructureHandler { #region Data Structure Handler events @@ -1150,8 +1152,8 @@ internal void SendHostResponseToServer(RemoteHostResponse hostResponse) RemoteDataObject dataToBeSent = RemoteDataObject.CreateFrom(RemotingDestination.Server, RemotingDataType.RemotePowerShellHostResponseData, - clientRunspacePoolId, - clientPowerShellId, + _clientRunspacePoolId, + _clientPowerShellId, hostResponse.Encode()); TransportManager.DataToBeSentCollection.Add(dataToBeSent, @@ -1173,7 +1175,7 @@ internal void SendInput(ObjectStreamBase inputstream) { // send input closed information to server SendDataAsync(RemotingEncoder.GeneratePowerShellInputEnd( - clientRunspacePoolId, clientPowerShellId)); + _clientRunspacePoolId, _clientPowerShellId)); } } else @@ -1202,10 +1204,10 @@ internal void SendInput(ObjectStreamBase inputstream) internal void ProcessReceivedData(RemoteDataObject 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 @@ -1425,7 +1427,7 @@ internal void ProcessDisconnect(RunspacePoolStateInfo rsStateInfo) /// /// This does not ensure that the corresponding session/runspacepool is in connected stated - /// Its the caller responsiblity to ensure that this is the case + /// It's the caller responsibility to ensure that this is the case /// At the protocols layers, this logic is delegated to the transport layer. /// WSMan transport ensures that WinRS commands cannot be reconnected when the parent shell is not in connected state. /// @@ -1468,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 /// @@ -1491,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; } @@ -1508,7 +1503,7 @@ internal Guid PowerShellId { get { - return clientPowerShellId; + return _clientPowerShellId; } } @@ -1561,7 +1556,7 @@ private void WriteInput(ObjectStreamBase inputstream) foreach (object inputObject in inputObjects) { SendDataAsync(RemotingEncoder.GeneratePowerShellInput(inputObject, - clientRunspacePoolId, clientPowerShellId)); + _clientRunspacePoolId, _clientPowerShellId)); } if (!inputstream.IsOpen) @@ -1572,7 +1567,7 @@ private void WriteInput(ObjectStreamBase inputstream) foreach (object inputObject in inputObjects) { SendDataAsync(RemotingEncoder.GeneratePowerShellInput(inputObject, - clientRunspacePoolId, clientPowerShellId)); + _clientRunspacePoolId, _clientPowerShellId)); } // we are sending input end to the server. Ignore the future @@ -1581,7 +1576,7 @@ private void WriteInput(ObjectStreamBase inputstream) inputstream.DataReady -= HandleInputDataReady; // stream close: send end of input SendDataAsync(RemotingEncoder.GeneratePowerShellInputEnd( - clientRunspacePoolId, clientPowerShellId)); + _clientRunspacePoolId, _clientPowerShellId)); } } @@ -1603,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(); @@ -1621,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 dfd1cd3bf7f..f76b836885a 100644 --- a/src/System.Management.Automation/engine/remoting/client/RunspaceRef.cs +++ b/src/System.Management.Automation/engine/remoting/client/RunspaceRef.cs @@ -5,6 +5,7 @@ using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; using System.Management.Automation.Runspaces.Internal; +using System.Management.Automation.Security; using Dbg = System.Management.Automation.Diagnostics; @@ -94,7 +95,22 @@ private PSCommand ParsePsCommandUsingScriptBlock(string line, bool? useLocalScop // to be parsed and evaluated on the remote session (not in the current local session). RemoteRunspace remoteRunspace = _runspaceRef.Value as RemoteRunspace; bool isConfiguredLoopback = remoteRunspace != null && remoteRunspace.IsConfiguredLoopBack; - bool isTrustedInput = !isConfiguredLoopback && (localRunspace.ExecutionContext.LanguageMode == PSLanguageMode.FullLanguage); + + bool inFullLanguage = context.LanguageMode == PSLanguageMode.FullLanguage; + if (context.LanguageMode == PSLanguageMode.ConstrainedLanguage + && SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Audit) + { + // In audit mode, report but don't enforce. + inFullLanguage = true; + SystemPolicy.LogWDACAuditMessage( + context: context, + title: RemotingErrorIdStrings.WDACGetPowerShellLogTitle, + message: RemotingErrorIdStrings.WDACGetPowerShellLogMessage, + fqid: "GetPowerShellMayFail", + dropIntoDebugger: true); + } + + bool isTrustedInput = !isConfiguredLoopback && inFullLanguage; // Create PowerShell from ScriptBlock. ScriptBlock scriptBlock = ScriptBlock.Create(context, line); @@ -208,12 +224,9 @@ internal Pipeline CreatePipeline(string line, bool addToHistory, bool useNestedP } // If that didn't work out fall-back to the traditional approach. - if (pipeline == null) - { - pipeline = useNestedPipelines ? - _runspaceRef.Value.CreateNestedPipeline(line, addToHistory) : - _runspaceRef.Value.CreatePipeline(line, addToHistory); - } + pipeline ??= useNestedPipelines ? + _runspaceRef.Value.CreateNestedPipeline(line, addToHistory) : + _runspaceRef.Value.CreatePipeline(line, addToHistory); // Add robust connection callback if this is a pushed runspace. RemotePipeline remotePipeline = pipeline as RemotePipeline; @@ -353,7 +366,7 @@ internal void Override(RemoteRunspace remoteRunspace, object syncObject, out boo /// private void HandleHostCall(object sender, RemoteDataEventArgs eventArgs) { - System.Management.Automation.Runspaces.Internal.ClientRemotePowerShell.ExitHandler(sender, eventArgs); + ClientRemotePowerShell.ExitHandler(sender, eventArgs); } #region Robust Connection Support diff --git a/src/System.Management.Automation/engine/remoting/client/ThrottlingJob.cs b/src/System.Management.Automation/engine/remoting/client/ThrottlingJob.cs index a00fdee17c2..59f4fb5135d 100644 --- a/src/System.Management.Automation/engine/remoting/client/ThrottlingJob.cs +++ b/src/System.Management.Automation/engine/remoting/client/ThrottlingJob.cs @@ -53,11 +53,7 @@ protected override void Dispose(bool disposing) childJob.Dispose(); } - if (_jobResultsThrottlingSemaphore != null) - { - _jobResultsThrottlingSemaphore.Dispose(); - } - + _jobResultsThrottlingSemaphore?.Dispose(); _cancellationTokenSource.Dispose(); } } @@ -298,7 +294,7 @@ internal void AddChildJobAndPotentiallyBlock( { using (var jobGotEnqueued = new ManualResetEventSlim(initialState: false)) { - if (childJob == null) throw new ArgumentNullException(nameof(childJob)); + ArgumentNullException.ThrowIfNull(childJob); this.AddChildJobWithoutBlocking(childJob, flags, jobGotEnqueued.Set); jobGotEnqueued.Wait(); @@ -312,7 +308,7 @@ internal void AddChildJobAndPotentiallyBlock( { using (var forwardingCancellation = new CancellationTokenSource()) { - if (childJob == null) throw new ArgumentNullException(nameof(childJob)); + ArgumentNullException.ThrowIfNull(childJob); this.AddChildJobWithoutBlocking(childJob, flags, forwardingCancellation.Cancel); this.ForwardAllResultsToCmdlet(cmdlet, forwardingCancellation.Token); @@ -372,15 +368,26 @@ internal void DisableFlowControlForPendingCmdletActionsQueue() /// internal void AddChildJobWithoutBlocking(StartableJob childJob, ChildJobFlags flags, Action jobEnqueuedAction = null) { - if (childJob == null) throw new ArgumentNullException(nameof(childJob)); - if (childJob.JobStateInfo.State != JobState.NotStarted) throw new ArgumentException(RemotingErrorIdStrings.ThrottlingJobChildAlreadyRunning, nameof(childJob)); + ArgumentNullException.ThrowIfNull(childJob); + if (childJob.JobStateInfo.State != JobState.NotStarted) + { + throw new ArgumentException(RemotingErrorIdStrings.ThrottlingJobChildAlreadyRunning, nameof(childJob)); + } + this.AssertNotDisposed(); JobStateInfo newJobStateInfo = null; lock (_lockObject) { - if (this.IsEndOfChildJobs) throw new InvalidOperationException(RemotingErrorIdStrings.ThrottlingJobChildAddedAfterEndOfChildJobs); - if (_isStopping) { return; } + if (this.IsEndOfChildJobs) + { + throw new InvalidOperationException(RemotingErrorIdStrings.ThrottlingJobChildAddedAfterEndOfChildJobs); + } + + if (_isStopping) + { + return; + } if (_countOfAllChildJobs == 0) { @@ -541,10 +548,7 @@ private void StartChildJobIfPossible() } while (false); } - if (readyToRunChildJob != null) - { - readyToRunChildJob.StartJob(); - } + readyToRunChildJob?.StartJob(); } private void EnqueueReadyToRunChildJob(StartableJob childJob) @@ -784,7 +788,7 @@ public override bool HasMoreData { get { - return this.GetChildJobsSnapshot().Any(childJob => childJob.HasMoreData) || (this.Results.Count != 0); + return this.GetChildJobsSnapshot().Any(static childJob => childJob.HasMoreData) || (this.Results.Count != 0); } } @@ -846,7 +850,7 @@ internal override void ForwardAvailableResultsToCmdlet(Cmdlet cmdlet) } } - private class ForwardingHelper : IDisposable + private sealed class ForwardingHelper : IDisposable { // This is higher than 1000 used in // RxExtensionMethods+ToEnumerableObserver.BlockingCollectionCapacity @@ -1227,10 +1231,7 @@ public static void ForwardAllResultsToCmdlet(ThrottlingJob throttlingJob, Cmdlet } finally { - if (cancellationTokenRegistration != null) - { - cancellationTokenRegistration.Dispose(); - } + cancellationTokenRegistration?.Dispose(); } } finally diff --git a/src/System.Management.Automation/engine/remoting/client/clientremotesession.cs b/src/System.Management.Automation/engine/remoting/client/clientremotesession.cs index 772beb61040..efcab242bb6 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 /// 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) /// 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; diff --git a/src/System.Management.Automation/engine/remoting/client/clientremotesessionprotocolstatemachine.cs b/src/System.Management.Automation/engine/remoting/client/clientremotesessionprotocolstatemachine.cs index d035ce6e820..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 /// internal class ClientRemoteSessionDSHandlerStateMachine { - [TraceSourceAttribute("CRSessionFSM", "CRSessionFSM")] + [TraceSource("CRSessionFSM", "CRSessionFSM")] private static readonly PSTraceSource s_trace = PSTraceSource.GetTracer("CRSessionFSM", "CRSessionFSM"); /// @@ -254,10 +254,7 @@ private void SetStateHandler(object sender, RemoteSessionStateMachineEventArgs e if (_state == RemoteSessionState.EstablishedAndKeySent) { Timer tmp = Interlocked.Exchange(ref _keyExchangeTimer, null); - if (tmp != null) - { - tmp.Dispose(); - } + tmp?.Dispose(); _keyExchanged = true; SetState(RemoteSessionState.Established, eventArgs.Reason); @@ -339,10 +336,7 @@ private void HandleKeyExchangeTimeout(object sender) Dbg.Assert(_state == RemoteSessionState.EstablishedAndKeySent, "timeout should only happen when waiting for a key"); Timer tmp = Interlocked.Exchange(ref _keyExchangeTimer, null); - if (tmp != null) - { - tmp.Dispose(); - } + tmp?.Dispose(); PSRemotingDataStructureException exception = new PSRemotingDataStructureException(RemotingErrorIdStrings.ClientKeyExchangeFailed); @@ -456,7 +450,7 @@ internal ClientRemoteSessionDSHandlerStateMachine() _stateMachineHandle[(int)RemoteSessionState.EstablishedAndKeyRequested, (int)RemoteSessionEvent.KeySendFailed] += SetStateToClosedHandler; // TODO: All these are potential unexpected state transitions.. should have a way to track these calls.. - // should atleast put a dbg assert in this handler + // should at least put a dbg assert in this handler for (int i = 0; i < _stateMachineHandle.GetLength(0); i++) { for (int j = 0; j < _stateMachineHandle.GetLength(1); j++) diff --git a/src/System.Management.Automation/engine/remoting/client/remotepipeline.cs b/src/System.Management.Automation/engine/remoting/client/remotepipeline.cs index c791d728809..cf476cec226 100644 --- a/src/System.Management.Automation/engine/remoting/client/remotepipeline.cs +++ b/src/System.Management.Automation/engine/remoting/client/remotepipeline.cs @@ -34,7 +34,7 @@ internal class RemotePipeline : Pipeline private readonly ConnectCommandInfo _connectCmdInfo = null; /// - /// This is queue of all the state change event which have occured for + /// This is queue of all the state change event which have occurred for /// this pipeline. RaisePipelineStateEvents raises event for each /// item in this queue. We don't raise the event with in SetPipelineState /// because often SetPipelineState is called with in a lock. @@ -42,7 +42,7 @@ internal class RemotePipeline : Pipeline /// private Queue _executionEventQueue = new Queue(); - private class ExecutionEventQueueItem + private sealed class ExecutionEventQueueItem { public ExecutionEventQueueItem(PipelineStateInfo pipelineStateInfo, RunspaceAvailability currentAvailability, RunspaceAvailability newAvailability) { @@ -95,7 +95,7 @@ private RemotePipeline(RemoteRunspace runspace, bool addToHistory, bool isNested SetCommandCollection(_commands); // Create event which will be signalled when pipeline execution - // is completed/failed/stoped. + // is completed/failed/stopped. // Note:Runspace.Close waits for all the running pipeline // to finish. This Event must be created before pipeline is // added to list of running pipelines. This avoids the race condition @@ -599,7 +599,7 @@ private bool CanStopPipeline(out bool isAlreadyStopping) break; // If pipeline execution has failed or completed or - // stoped, return silently. + // stopped, return silently. case PipelineState.Stopped: case PipelineState.Completed: case PipelineState.Failed: @@ -1018,7 +1018,7 @@ private void Cleanup() /// /// ManualResetEvent which is signaled when pipeline execution is - /// completed/failed/stoped. + /// completed/failed/stopped. /// internal ManualResetEvent PipelineFinishedEvent { get; } diff --git a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs index 7ddd657872e..8448007025f 100644 --- a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs +++ b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs @@ -48,7 +48,7 @@ internal class RemoteRunspace : Runspace, IDisposable private long _currentLocalPipelineId = 0; /// - /// This is queue of all the state change event which have occured for + /// This is queue of all the state change event which have occurred for /// this runspace. RaiseRunspaceStateEvents raises event for each /// item in this queue. We don't raise events from with SetRunspaceState /// because SetRunspaceState is often called from with in the a lock. @@ -137,8 +137,8 @@ internal RemoteRunspace(TypeTable typeTable, RunspaceConnectionInfo connectionIn PSTask.CreateRunspace, PSKeyword.UseAlwaysOperational, InstanceId.ToString()); - _connectionInfo = connectionInfo.InternalCopy(); - OriginalConnectionInfo = connectionInfo.InternalCopy(); + _connectionInfo = connectionInfo.Clone(); + OriginalConnectionInfo = connectionInfo.Clone(); RunspacePool = new RunspacePool(1, 1, typeTable, host, applicationArguments, connectionInfo, name); @@ -170,7 +170,7 @@ internal RemoteRunspace(RunspacePool runspacePool) RunspacePool.RemoteRunspacePoolInternal.SetMinRunspaces(1); RunspacePool.RemoteRunspacePoolInternal.SetMaxRunspaces(1); - _connectionInfo = runspacePool.ConnectionInfo.InternalCopy(); + _connectionInfo = runspacePool.ConnectionInfo.Clone(); // Update runspace DisconnectedOn and ExpiresOn property from WSManConnectionInfo UpdateDisconnectExpiresOn(); @@ -221,11 +221,7 @@ public override InitialSessionState InitialSessionState { get { -#pragma warning disable 56503 - throw PSTraceSource.NewNotImplementedException(); - -#pragma warning restore 56503 } } @@ -236,11 +232,7 @@ public override JobManager JobManager { get { -#pragma warning disable 56503 - throw PSTraceSource.NewNotImplementedException(); - -#pragma warning restore 56503 } } @@ -642,11 +634,8 @@ protected override void Dispose(bool disposing) // } - if (_remoteDebugger != null) - { - // Release RunspacePool event forwarding handlers. - _remoteDebugger.Dispose(); - } + // Release RunspacePool event forwarding handlers. + _remoteDebugger?.Dispose(); try { @@ -946,6 +935,11 @@ public override RunspaceCapability GetCapabilities() returnCaps |= RunspaceCapability.SupportsDisconnect; } + if (_connectionInfo is WSManConnectionInfo) + { + return returnCaps; + } + if (_connectionInfo is NamedPipeConnectionInfo) { returnCaps |= RunspaceCapability.NamedPipeTransport; @@ -958,16 +952,20 @@ public override RunspaceCapability GetCapabilities() { returnCaps |= RunspaceCapability.SSHTransport; } - else + else if (_connectionInfo is ContainerConnectionInfo containerConnectionInfo) { - ContainerConnectionInfo containerConnectionInfo = _connectionInfo as ContainerConnectionInfo; - if ((containerConnectionInfo != null) && (containerConnectionInfo.ContainerProc.RuntimeId == Guid.Empty)) { returnCaps |= RunspaceCapability.NamedPipeTransport; } } + else + { + // Unknown connection info type means a custom connection/transport, which at + // minimum supports remote runspace capability starting from PowerShell v7.x. + returnCaps |= RunspaceCapability.CustomTransport; + } return returnCaps; } @@ -1731,10 +1729,7 @@ internal void AbortOpen() System.Management.Automation.Remoting.Client.NamedPipeClientSessionTransportManager transportManager = RunspacePool.RemoteRunspacePoolInternal.DataStructureHandler.TransportManager as System.Management.Automation.Remoting.Client.NamedPipeClientSessionTransportManager; - if (transportManager != null) - { - transportManager.AbortConnect(); - } + transportManager?.AbortConnect(); } #endregion Internal Methods @@ -2785,8 +2780,8 @@ private void CheckForValidateState() { throw new PSInvalidOperationException( // The remote session to which you are connected does not support remote debugging. - // You must connect to a remote computer that is running PowerShell {0} or greater. - StringUtil.Format(RemotingErrorIdStrings.RemoteDebuggingEndpointVersionError, PSVersionInfo.PSV4Version), + // You must connect to a remote computer that is running PowerShell 4.0 or greater. + RemotingErrorIdStrings.RemoteDebuggingEndpointVersionError, null, "RemoteDebugger:RemoteDebuggingNotSupported", ErrorCategory.NotImplemented, @@ -2910,10 +2905,7 @@ private T InvokeRemoteBreakpointFunction(string functionName, Dictionary /// Static variable which is incremented to generate id. @@ -302,7 +303,8 @@ internal PSSession(RemoteRunspace remoteRunspace) break; default: - Dbg.Assert(false, "Invalid Runspace"); + // Default for custom connection and transports. + ComputerType = TargetMachineType.RemoteMachine; break; } } @@ -338,7 +340,7 @@ private string GetTransportName() return "VMBus"; default: - return "Unknown"; + return string.IsNullOrEmpty(_transportName) ? "Custom" : _transportName; } } @@ -359,6 +361,34 @@ private static string GetDisplayShellName(string shell) #region Static Methods + /// + /// Creates a PSSession object from the provided remote runspace object. + /// If psCmdlet argument is non-null, then the new PSSession object is added to the + /// session runspace repository (Get-PSSession). + /// + /// Runspace for the new PSSession. + /// Optional transport name. + /// Optional cmdlet associated with the PSSession creation. + public static PSSession Create( + Runspace runspace, + string transportName, + PSCmdlet psCmdlet) + { + if (!(runspace is RemoteRunspace remoteRunspace)) + { + throw new PSArgumentException(RemotingErrorIdStrings.InvalidPSSessionArgument); + } + + var psSession = new PSSession(remoteRunspace) + { + _transportName = transportName + }; + + psCmdlet?.RunspaceRepository.Add(psSession); + + return psSession; + } + /// /// Generates a unique runspace id. /// diff --git a/src/System.Management.Automation/engine/remoting/client/remotingprotocolimplementation.cs b/src/System.Management.Automation/engine/remoting/client/remotingprotocolimplementation.cs index d8800311127..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 /// /// Implements ServerRemoteSessionDataStructureHandler. /// - 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"; @@ -329,7 +329,7 @@ private void HandleStateChanged(object sender, RemoteSessionStateEventArgs arg) } /// - /// Clubing negotiation packet + runspace creation and then doing transportManager.ConnectAsync(). + /// Clubbing negotiation packet + runspace creation and then doing transportManager.ConnectAsync(). /// This will save us 2 network calls by doing all the work in one network call. /// private void HandleNegotiationSendingStateChange() @@ -735,26 +735,12 @@ internal void ProcessNonSessionMessages(RemoteDataObject rcvdData) #region IDisposable - /// - /// Public method for dispose. - /// - public void Dispose() - { - Dispose(true); - - GC.SuppressFinalize(this); - } - /// /// Release all resources. /// - /// If true, release all managed resources. - 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/ConnectPSSession.cs b/src/System.Management.Automation/engine/remoting/commands/ConnectPSSession.cs index f58aa35632e..b7474fe34c2 100644 --- a/src/System.Management.Automation/engine/remoting/commands/ConnectPSSession.cs +++ b/src/System.Management.Automation/engine/remoting/commands/ConnectPSSession.cs @@ -83,7 +83,7 @@ public class ConnectPSSessionCommand : PSRunspaceCmdlet, IDisposable /// /// This parameters specifies the appname which identifies the connection /// end point on the remote machine. If this parameter is not specified - /// then the value specified in DEFAULTREMOTEAPPNAME will be used. If thats + /// then the value specified in DEFAULTREMOTEAPPNAME will be used. If that's /// not specified as well, then "WSMAN" will be used. /// [Parameter(ValueFromPipelineByPropertyName = true, @@ -92,7 +92,10 @@ public class ConnectPSSessionCommand : PSRunspaceCmdlet, IDisposable ParameterSetName = ConnectPSSessionCommand.ComputerNameGuidParameterSet)] public string ApplicationName { - get { return _appName; } + get + { + return _appName; + } set { @@ -117,7 +120,10 @@ public string ApplicationName ParameterSetName = ConnectPSSessionCommand.ConnectionUriGuidParameterSet)] public string ConfigurationName { - get { return _shell; } + get + { + return _shell; + } set { @@ -199,10 +205,13 @@ public override string[] Name [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameGuidParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriGuidParameterSet)] - [Credential()] + [Credential] public PSCredential Credential { - get { return _psCredential; } + get + { + return _psCredential; + } set { @@ -223,7 +232,10 @@ public PSCredential Credential [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriGuidParameterSet)] public AuthenticationMechanism Authentication { - get { return _authentication; } + get + { + return _authentication; + } set { @@ -245,7 +257,10 @@ public AuthenticationMechanism Authentication [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriGuidParameterSet)] public string CertificateThumbprint { - get { return _thumbprint; } + get + { + return _thumbprint; + } set { @@ -472,7 +487,7 @@ protected override void StopProcessing() /// /// Throttle class to perform a remoterunspace connect operation. /// - private class ConnectRunspaceOperation : IThrottleOperation + private sealed class ConnectRunspaceOperation : IThrottleOperation { private PSSession _session; private PSSession _oldSession; @@ -554,10 +569,7 @@ internal override void StartOperation() internal override void StopOperation() { - if (_queryRunspaces != null) - { - _queryRunspaces.StopAllOperations(); - } + _queryRunspaces?.StopAllOperations(); _session.Runspace.StateChanged -= StateCallBackHandler; SendStopComplete(); diff --git a/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs b/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs index aa90fca60a4..e600661eab9 100644 --- a/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs +++ b/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs @@ -52,7 +52,8 @@ function Register-PSSessionConfiguration [system.security.securestring] $runAsPassword, [System.Management.Automation.Runspaces.PSSessionConfigurationAccessMode] $accessMode, [bool] $isSddlSpecified, - [string] $configTableSddl + [string] $configTableSddl, + [bool] $noRestart ) begin @@ -139,7 +140,7 @@ function Register-PSSessionConfiguration ## Replace the SDDL with any groups or restrictions defined in the PSSessionConfigurationFile if($? -and $configTableSddl -and (-not $isSddlSpecified)) {{ - $null = Set-PSSessionConfiguration -Name $pluginName -SecurityDescriptorSddl $configTableSddl -Force:$force + $null = Set-PSSessionConfiguration -Name $pluginName -SecurityDescriptorSddl $configTableSddl -NoServiceRestart:$noRestart -Force:$force }} if ($? -and $shouldShowUI) @@ -227,11 +228,11 @@ function Register-PSSessionConfiguration if ($runAsUserName) {{ $runAsCredential = new-object system.management.automation.PSCredential($runAsUserName, $runAsPassword) - $null = Set-PSSessionConfiguration -Name $pluginName -SecurityDescriptorSddl $newSDDL -NoServiceRestart -force -WarningAction 0 -RunAsCredential $runAsCredential + $null = Set-PSSessionConfiguration -Name $pluginName -SecurityDescriptorSddl $newSDDL -NoServiceRestart:$noRestart -Force:$force -WarningAction 0 -RunAsCredential $runAsCredential }} else {{ - $null = Set-PSSessionConfiguration -Name $pluginName -SecurityDescriptorSddl $newSDDL -NoServiceRestart -force -WarningAction 0 + $null = Set-PSSessionConfiguration -Name $pluginName -SecurityDescriptorSddl $newSDDL -NoServiceRestart:$noRestart -Force:$force -WarningAction 0 }} }} catch {{ @@ -262,13 +263,13 @@ function Register-PSSessionConfiguration }} }} -if ($null -eq $args[14]) +if ($null -eq $args[15]) {{ - Register-PSSessionConfiguration -filepath $args[0] -pluginName $args[1] -shouldShowUI $args[2] -force $args[3] -whatif:$args[4] -confirm:$args[5] -restartWSManTarget $args[6] -restartWSManAction $args[7] -restartWSManRequired $args[8] -runAsUserName $args[9] -runAsPassword $args[10] -accessMode $args[11] -isSddlSpecified $args[12] -configTableSddl $args[13] + Register-PSSessionConfiguration -filepath $args[0] -pluginName $args[1] -shouldShowUI $args[2] -force $args[3] -whatif:$args[4] -confirm:$args[5] -restartWSManTarget $args[6] -restartWSManAction $args[7] -restartWSManRequired $args[8] -runAsUserName $args[9] -runAsPassword $args[10] -accessMode $args[11] -isSddlSpecified $args[12] -configTableSddl $args[13] -noRestart $args[14] }} else {{ - Register-PSSessionConfiguration -filepath $args[0] -pluginName $args[1] -shouldShowUI $args[2] -force $args[3] -whatif:$args[4] -confirm:$args[5] -restartWSManTarget $args[6] -restartWSManAction $args[7] -restartWSManRequired $args[8] -runAsUserName $args[9] -runAsPassword $args[10] -accessMode $args[11] -isSddlSpecified $args[12] -configTableSddl $args[13] -erroraction $args[14] + Register-PSSessionConfiguration -filepath $args[0] -pluginName $args[1] -shouldShowUI $args[2] -force $args[3] -whatif:$args[4] -confirm:$args[5] -restartWSManTarget $args[6] -restartWSManAction $args[7] -restartWSManRequired $args[8] -runAsUserName $args[9] -runAsPassword $args[10] -accessMode $args[11] -isSddlSpecified $args[12] -configTableSddl $args[13] -noRestart $args[14] -erroraction $args[15] }} "; @@ -348,7 +349,7 @@ static RegisterPSSessionConfigurationCommand() string localSDDL = GetLocalSddl(); // compile the script block statically and reuse the same instance - // everytime the command is run..This will save on parsing time. + // every time the command is run..This will save on parsing time. string newPluginSbString = string.Format(CultureInfo.InvariantCulture, newPluginSbFormat, WSManNativeApi.ResourceURIPrefix, localSDDL, RemoteManagementUsersSID, InteractiveUsersSID); @@ -544,9 +545,7 @@ protected override void ProcessRecord() string restartServiceTarget = StringUtil.Format(RemotingErrorIdStrings.RestartWSManServiceTarget, "WinRM"); string restartWSManRequiredForUI = StringUtil.Format(RemotingErrorIdStrings.RestartWSManRequiredShowUI, - string.Format(CultureInfo.InvariantCulture, - "Set-PSSessionConfiguration {0} -ShowSecurityDescriptorUI", - shellName)); + string.Create(CultureInfo.InvariantCulture, $"Set-PSSessionConfiguration {shellName} -ShowSecurityDescriptorUI")); // gather -WhatIf, -Confirm parameter data and pass it to the script block bool whatIf = false; @@ -594,6 +593,7 @@ protected override void ProcessRecord() AccessMode, isSddlSpecified, _configTableSDDL, + noRestart, errorAction }); @@ -1373,7 +1373,7 @@ internal static PSCredential CreateGMSAAccountCredentials(string gmsaAccount) Dbg.Assert(!string.IsNullOrEmpty(gmsaAccount), "Should not be null or empty string."); // Validate account name form (must be DomainName\UserName) - var parts = gmsaAccount.Split(Utils.Separators.Backslash); + var parts = gmsaAccount.Split('\\'); if ((parts.Length != 2) || (string.IsNullOrEmpty(parts[0])) || (string.IsNullOrEmpty(parts[1])) @@ -1462,7 +1462,7 @@ internal static string GetRunAsVirtualAccountGroupsString(string[] groups) { if (groups == null) { return string.Empty; } - return string.Join(";", groups); + return string.Join(';', groups); } /// @@ -1962,7 +1962,10 @@ public string Name [Parameter(Position = 1, Mandatory = true, ParameterSetName = PSSessionConfigurationCommandBase.AssemblyNameParameterSetName)] public string AssemblyName { - get { return assemblyName; } + get + { + return assemblyName; + } set { @@ -1984,7 +1987,10 @@ public string AssemblyName [Parameter(ParameterSetName = AssemblyNameParameterSetName)] public string ApplicationBase { - get { return applicationBase; } + get + { + return applicationBase; + } set { @@ -2004,7 +2010,10 @@ public string ApplicationBase [Parameter(Position = 2, Mandatory = true, ParameterSetName = PSSessionConfigurationCommandBase.AssemblyNameParameterSetName)] public string ConfigurationTypeName { - get { return configurationTypeName; } + get + { + return configurationTypeName; + } set { @@ -2053,7 +2062,10 @@ public ApartmentState ThreadApartmentState return ApartmentState.Unknown; } - set { threadAptState = value; } + set + { + threadAptState = value; + } } internal ApartmentState? threadAptState; @@ -2074,7 +2086,10 @@ public PSThreadOptions ThreadOptions return PSThreadOptions.UseCurrentThread; } - set { threadOptions = value; } + set + { + threadOptions = value; + } } internal PSThreadOptions? threadOptions; @@ -2085,7 +2100,10 @@ public PSThreadOptions ThreadOptions [Parameter] public PSSessionConfigurationAccessMode AccessMode { - get { return _accessMode; } + get + { + return _accessMode; + } set { @@ -2124,7 +2142,10 @@ public SwitchParameter UseSharedProcess [Parameter()] public string StartupScript { - get { return configurationScript; } + get + { + return configurationScript; + } set { @@ -2144,7 +2165,10 @@ public string StartupScript [AllowNull] public double? MaximumReceivedDataSizePerCommandMB { - get { return maxCommandSizeMB; } + get + { + return maxCommandSizeMB; + } set { @@ -2171,7 +2195,10 @@ public double? MaximumReceivedDataSizePerCommandMB [AllowNull] public double? MaximumReceivedObjectSizeMB { - get { return maxObjectSizeMB; } + get + { + return maxObjectSizeMB; + } set { @@ -2198,7 +2225,10 @@ public double? MaximumReceivedObjectSizeMB [Parameter()] public string SecurityDescriptorSddl { - get { return sddl; } + get + { + return sddl; + } set { @@ -2228,7 +2258,10 @@ public string SecurityDescriptorSddl [Parameter()] public SwitchParameter ShowSecurityDescriptorUI { - get { return _showUI; } + get + { + return _showUI; + } set { @@ -2280,17 +2313,15 @@ public SwitchParameter NoServiceRestart [ValidateNotNullOrEmpty] public Version PSVersion { - get { return psVersion; } + get + { + return psVersion; + } set { - RemotingCommandUtil.CheckPSVersion(value); - - // Check if specified version of PowerShell is installed - RemotingCommandUtil.CheckIfPowerShellVersionIsInstalled(value); - - psVersion = value; - isPSVersionSpecified = true; + // PowerShell 7 remoting endpoints do not support PSVersion. + throw new PSNotSupportedException(RemotingErrorIdStrings.PowerShellVersionNotSupported); } } @@ -2589,7 +2620,7 @@ static UnregisterPSSessionConfigurationCommand() removePluginSbFormat, RemotingConstants.PSPluginDLLName); // compile the script block statically and reuse the same instance - // everytime the command is run..This will save on parsing time. + // every time the command is run..This will save on parsing time. s_removePluginSb = ScriptBlock.Create(removePluginScript); s_removePluginSb.LanguageMode = PSLanguageMode.FullLanguage; } @@ -2805,7 +2836,7 @@ static GetPSSessionConfigurationCommand() PSSessionConfigurationCommandUtilities.PSCustomShellTypeName, RemotingConstants.PSPluginDLLName); // compile the script block statically and reuse the same instance - // everytime the command is run..This will save on parsing time. + // every time the command is run..This will save on parsing time. s_getPluginSb = ScriptBlock.Create(scriptToRun); s_getPluginSb.LanguageMode = PSLanguageMode.FullLanguage; } @@ -3229,7 +3260,7 @@ static SetPSSessionConfigurationCommand() RemotingConstants.PSPluginDLLName, localSDDL, RemoteManagementUsersSID, InteractiveUsersSID); // compile the script block statically and reuse the same instance - // everytime the command is run..This will save on parsing time. + // every time the command is run..This will save on parsing time. s_setPluginSb = ScriptBlock.Create(setPluginScript); s_setPluginSb.LanguageMode = PSLanguageMode.FullLanguage; } @@ -3962,7 +3993,7 @@ public sealed class EnablePSSessionConfigurationCommand : PSCmdlet function Test-WinRMQuickConfigNeeded {{ - # see issue #11005 - Function Test-WinRMQuickConfigNeeded needs to be updated: + # see issue #11005 - Function Test-WinRMQuickConfigNeeded needs to be updated: # 1) currently this function always returns $True # 2) checking for a firewall rule using Get-NetFirewallRule engages WinCompat code and has significant perf impact on Enable-PSRemoting; maybe change to Get-CimInstance -ClassName MSFT_NetFirewallRule return $True @@ -4179,7 +4210,7 @@ static EnablePSSessionConfigurationCommand() enablePluginSbFormat, setWSManConfigCommand, PSSessionConfigurationCommandBase.RemoteManagementUsersSID, PSSessionConfigurationCommandBase.InteractiveUsersSID); // compile the script block statically and reuse the same instance - // everytime the command is run..This will save on parsing time. + // every time the command is run..This will save on parsing time. s_enablePluginSb = ScriptBlock.Create(enablePluginScript); s_enablePluginSb.LanguageMode = PSLanguageMode.FullLanguage; } @@ -4466,7 +4497,7 @@ static DisablePSSessionConfigurationCommand() disablePluginSbFormat); // compile the script block statically and reuse the same instance - // everytime the command is run..This will save on parsing time. + // every time the command is run..This will save on parsing time. s_disablePluginSb = ScriptBlock.Create(disablePluginScript); s_disablePluginSb.LanguageMode = PSLanguageMode.FullLanguage; } @@ -4873,7 +4904,7 @@ static EnablePSRemotingCommand() RemotingConstants.MaxIdleTimeoutMS, RemotingConstants.PSPluginDLLName); // compile the script block statically and reuse the same instance - // everytime the command is run..This will save on parsing time. + // every time the command is run..This will save on parsing time. s_enableRemotingSb = ScriptBlock.Create(enableRemotingScript); s_enableRemotingSb.LanguageMode = PSLanguageMode.FullLanguage; } @@ -5097,7 +5128,7 @@ static DisablePSRemotingCommand() string disableRemotingScript = string.Format(CultureInfo.InvariantCulture, disablePSRemotingFormat, localSDDL); // compile the script block statically and reuse the same instance - // everytime the command is run..This will save on parsing time. + // every time the command is run..This will save on parsing time. s_disableRemotingSb = ScriptBlock.Create(disableRemotingScript); s_disableRemotingSb.LanguageMode = PSLanguageMode.FullLanguage; } @@ -5233,7 +5264,7 @@ protected override void BeginProcessing() } // The validator that will be applied to the role lookup - Func validator = (role) => true; + Func validator = static (role) => true; if (!string.IsNullOrEmpty(this.Username)) { @@ -5242,7 +5273,7 @@ protected override void BeginProcessing() validator = null; // Convert DOMAIN\user to the upn (user@DOMAIN) - string[] upnComponents = this.Username.Split(Utils.Separators.Backslash); + string[] upnComponents = this.Username.Split('\\'); if (upnComponents.Length == 2) { this.Username = upnComponents[1] + "@" + upnComponents[0]; diff --git a/src/System.Management.Automation/engine/remoting/commands/DebugJob.cs b/src/System.Management.Automation/engine/remoting/commands/DebugJob.cs index 777f56807fe..ece0ce45dbf 100644 --- a/src/System.Management.Automation/engine/remoting/commands/DebugJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/DebugJob.cs @@ -100,7 +100,6 @@ public Guid InstanceId /// /// Gets or sets a flag that tells PowerShell to automatically perform a BreakAll when the debugger is attached to the remote target. /// - [Experimental("Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace", ExperimentAction.Show)] [Parameter] public SwitchParameter BreakAll { get; set; } @@ -204,10 +203,7 @@ protected override void StopProcessing() // Unblock the data collection. PSDataCollection debugCollection = _debugCollection; - if (debugCollection != null) - { - debugCollection.Complete(); - } + debugCollection?.Complete(); } #endregion @@ -230,7 +226,10 @@ private bool CheckForDebuggableJob() foreach (var cJob in _job.ChildJobs) { debuggableJobFound = GetJobDebuggable(cJob); - if (debuggableJobFound) { break; } + if (debuggableJobFound) + { + break; + } } } @@ -261,10 +260,7 @@ private void WaitAndReceiveJobOutput() // or this command is cancelled. foreach (var streamItem in _debugCollection) { - if (streamItem != null) - { - streamItem.WriteStreamObject(this); - } + streamItem?.WriteStreamObject(this); } } catch (Exception) diff --git a/src/System.Management.Automation/engine/remoting/commands/DisconnectPSSession.cs b/src/System.Management.Automation/engine/remoting/commands/DisconnectPSSession.cs index 0e2458daba0..74bcbac2c33 100644 --- a/src/System.Management.Automation/engine/remoting/commands/DisconnectPSSession.cs +++ b/src/System.Management.Automation/engine/remoting/commands/DisconnectPSSession.cs @@ -392,7 +392,7 @@ private static string GetLocalhostWithNetworkAccessEnabled(Dictionary /// Throttle class to perform a remoterunspace disconnect operation. /// - private class DisconnectRunspaceOperation : IThrottleOperation + private sealed class DisconnectRunspaceOperation : IThrottleOperation { private readonly PSSession _remoteSession; private readonly ObjectStream _writeStream; diff --git a/src/System.Management.Automation/engine/remoting/commands/EnterPSHostProcessCommand.cs b/src/System.Management.Automation/engine/remoting/commands/EnterPSHostProcessCommand.cs index 756250eb8d7..f7aef0530b8 100644 --- a/src/System.Management.Automation/engine/remoting/commands/EnterPSHostProcessCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/EnterPSHostProcessCommand.cs @@ -54,7 +54,7 @@ public sealed class EnterPSHostProcessCommand : PSCmdlet /// Process to enter. /// [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ParameterSetName = EnterPSHostProcessCommand.ProcessParameterSet)] - [ValidateNotNull()] + [ValidateNotNull] public Process Process { get; @@ -76,7 +76,7 @@ public int Id /// Name of process to enter. An error will result if more than one such process exists. /// [Parameter(Position = 0, Mandatory = true, ParameterSetName = EnterPSHostProcessCommand.ProcessNameParameterSet)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public string Name { get; @@ -87,7 +87,7 @@ public string Name /// Host Process Info object that describes a connectible process. /// [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ParameterSetName = EnterPSHostProcessCommand.PSHostProcessInfoParameterSet)] - [ValidateNotNull()] + [ValidateNotNull] public PSHostProcessInfo HostProcessInfo { get; @@ -212,10 +212,7 @@ protected override void EndProcessing() protected override void StopProcessing() { RemoteRunspace connectingRunspace = _connectingRemoteRunspace; - if (connectingRunspace != null) - { - connectingRunspace.AbortOpen(); - } + connectingRunspace?.AbortOpen(); } #endregion @@ -318,22 +315,19 @@ private static void PrepareRunspace(Runspace runspace) private Process GetProcessById(int procId) { - try - { - return Process.GetProcessById(procId); - } - catch (System.ArgumentException) + var process = PSHostProcessUtils.GetProcessById(procId); + if (process is null) { ThrowTerminatingError( - new ErrorRecord( - new PSArgumentException(StringUtil.Format(RemotingErrorIdStrings.EnterPSHostProcessNoProcessFoundWithId, procId)), - "EnterPSHostProcessNoProcessFoundWithId", - ErrorCategory.InvalidArgument, - this) - ); - - return null; + new ErrorRecord( + new PSArgumentException(StringUtil.Format(RemotingErrorIdStrings.EnterPSHostProcessNoProcessFoundWithId, procId)), + "EnterPSHostProcessNoProcessFoundWithId", + ErrorCategory.InvalidArgument, + this) + ); } + + return process; } private Process GetProcessByHostProcessInfo(PSHostProcessInfo hostProcessInfo) @@ -403,7 +397,7 @@ private void VerifyProcess(Process process) { ThrowTerminatingError( new ErrorRecord( - new PSInvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.EnterPSHostProcessNoPowerShell, Process.ProcessName)), + new PSInvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.EnterPSHostProcessNoPowerShell, Process.Id)), "EnterPSHostProcessNoPowerShell", ErrorCategory.InvalidOperation, this) @@ -506,7 +500,7 @@ public sealed class GetPSHostProcessInfoCommand : PSCmdlet /// [Parameter(Position = 0, ParameterSetName = GetPSHostProcessInfoCommand.ProcessNameParameterSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public string[] Name { get; @@ -518,7 +512,7 @@ public string[] Name /// [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ParameterSetName = GetPSHostProcessInfoCommand.ProcessParameterSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public Process[] Process { get; @@ -530,7 +524,7 @@ public Process[] Process /// [Parameter(Position = 0, Mandatory = true, ParameterSetName = GetPSHostProcessInfoCommand.ProcessIdParameterSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public int[] Id { get; @@ -599,9 +593,22 @@ private static int[] GetProcIdsFromNames(string[] names) WildcardPattern namePattern = WildcardPattern.Get(name, WildcardOptions.IgnoreCase); foreach (var proc in processes) { - if (namePattern.IsMatch(proc.ProcessName)) + // Skip processes that have already terminated. + if (proc.HasExited) { - returnIds.Add(proc.Id); + continue; + } + + try + { + if (namePattern.IsMatch(proc.ProcessName)) + { + returnIds.Add(proc.Id); + } + } + catch (InvalidOperationException) + { + // Ignore if process has exited in the mean time. } } } @@ -665,7 +672,10 @@ internal static IReadOnlyCollection GetAppDomainNamesFromProc } } - if (!found) { continue; } + if (!found) + { + continue; + } } } else @@ -681,10 +691,9 @@ internal static IReadOnlyCollection GetAppDomainNamesFromProc string pName = namedPipe.Substring(pNameIndex + 1); Process process = null; - try { - process = System.Diagnostics.Process.GetProcessById(id); + process = PSHostProcessUtils.GetProcessById(id); } catch (Exception) { @@ -704,10 +713,20 @@ internal static IReadOnlyCollection GetAppDomainNamesFromProc // best effort to cleanup } } - else if (process.ProcessName.Equals(pName, StringComparison.Ordinal)) + else { - // only add if the process name matches - procAppDomainInfo.Add(new PSHostProcessInfo(pName, id, appDomainName, namedPipe)); + try + { + if (process.ProcessName.Equals(pName, StringComparison.Ordinal)) + { + // only add if the process name matches + procAppDomainInfo.Add(new PSHostProcessInfo(pName, id, appDomainName, namedPipe)); + } + } + catch (InvalidOperationException) + { + // Ignore if process has exited in the mean time. + } } } } @@ -796,8 +815,8 @@ internal PSHostProcessInfo( MainWindowTitle = string.Empty; try { - var proc = Process.GetProcessById(processId); - MainWindowTitle = proc.MainWindowTitle ?? string.Empty; + var process = PSHostProcessUtils.GetProcessById(processId); + MainWindowTitle = process?.MainWindowTitle ?? string.Empty; } catch (ArgumentException) { @@ -831,4 +850,30 @@ public string GetPipeNameFilePath() } #endregion + + #region PSHostProcessUtils + + internal static class PSHostProcessUtils + { + /// + /// Return a System.Diagnostics.Process object by process Id, + /// or null if not found or process has exited. + /// + /// Process of Id to find. + /// Process object or null. + public static Process GetProcessById(int procId) + { + try + { + var process = Process.GetProcessById(procId); + return process.HasExited ? null : process; + } + catch (System.ArgumentException) + { + return null; + } + } + } + + #endregion } diff --git a/src/System.Management.Automation/engine/remoting/commands/GetJob.cs b/src/System.Management.Automation/engine/remoting/commands/GetJob.cs index 17ee25c52cf..fe5946f44f0 100644 --- a/src/System.Management.Automation/engine/remoting/commands/GetJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/GetJob.cs @@ -110,7 +110,7 @@ protected override void ProcessRecord() { List jobList = FindJobs(); - jobList.Sort((x, y) => x != null ? x.Id.CompareTo(y != null ? y.Id : 1) : -1); + jobList.Sort(static (x, y) => x != null ? x.Id.CompareTo(y != null ? y.Id : 1) : -1); WriteObject(jobList, true); } @@ -256,7 +256,10 @@ private List FindChildJobs(List jobList) { foreach (Job childJob in job.ChildJobs) { - if (childJob.JobStateInfo.State != ChildJobState) continue; + if (childJob.JobStateInfo.State != ChildJobState) + { + continue; + } matches.Add(childJob); } diff --git a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs index b60f3131f24..534362b0f17 100644 --- a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs @@ -198,7 +198,7 @@ public override string[] ComputerName ParameterSetName = InvokeCommandCommand.FilePathVMIdParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, Mandatory = true, ParameterSetName = InvokeCommandCommand.FilePathVMNameParameterSet)] - [Credential()] + [Credential] public override PSCredential Credential { get @@ -308,7 +308,7 @@ public override string ConfigurationName /// /// This parameters specifies the appname which identifies the connection /// end point on the remote machine. If this parameter is not specified - /// then the value specified in DEFAULTREMOTEAPPNAME will be used. If thats + /// then the value specified in DEFAULTREMOTEAPPNAME will be used. If that's /// not specified as well, then "WSMAN" will be used. /// [Parameter(ValueFromPipelineByPropertyName = true, @@ -702,7 +702,7 @@ public override SwitchParameter RunAsAdministrator ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] [Parameter(Mandatory = true, ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public override string[] HostName { get { return base.HostName; } @@ -715,7 +715,7 @@ public override string[] HostName /// [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public override string UserName { get { return base.UserName; } @@ -728,7 +728,7 @@ public override string UserName /// [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [Alias("IdentityFilePath")] public override string KeyFilePath { @@ -737,6 +737,30 @@ public override string KeyFilePath set { base.KeyFilePath = value; } } + /// + /// Gets and sets a value for the SSH subsystem to use for the remote connection. + /// + [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] + public override string Subsystem + { + get { return base.Subsystem; } + + set { base.Subsystem = value; } + } + + /// + /// Gets and sets a value in milliseconds that limits the time allowed for an SSH connection to be established. + /// + [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] + public override int ConnectingTimeout + { + get { return base.ConnectingTimeout; } + + set { base.ConnectingTimeout = value; } + } + /// /// This parameter specifies that SSH is used to establish the remote /// connection and act as the remoting transport. By default WinRM is used @@ -761,13 +785,32 @@ public override SwitchParameter SSHTransport /// [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostHashParameterSet, Mandatory = true)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostHashParameterSet, Mandatory = true)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public override Hashtable[] SSHConnection { get; set; } + /// + /// Hashtable containing options to be passed to OpenSSH. + /// + [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] + [ValidateNotNullOrEmpty] + public override Hashtable Options + { + get + { + return base.Options; + } + + set + { + base.Options = value; + } + } + #endregion #region Remote Debug Parameters @@ -1420,10 +1463,7 @@ private void HandleRunspaceDebugStop(object sender, StartRunspaceDebugProcessing operation.RunspaceDebugStop -= HandleRunspaceDebugStop; var hostDebugger = GetHostDebugger(); - if (hostDebugger != null) - { - hostDebugger.QueueRunspaceForDebug(args.Runspace); - } + hostDebugger?.QueueRunspaceForDebug(args.Runspace); } private void HandleJobStateChanged(object sender, JobStateEventArgs e) @@ -1440,10 +1480,7 @@ private void HandleJobStateChanged(object sender, JobStateEventArgs e) // Signal that this job has been disconnected, or has ended. lock (_jobSyncObject) { - if (_disconnectComplete != null) - { - _disconnectComplete.Set(); - } + _disconnectComplete?.Set(); } } } @@ -2047,11 +2084,8 @@ private void Dispose(bool disposing) if (!_asjob) { - if (_job != null) - { - // job will be null in the "InProcess" case - _job.Dispose(); - } + // job will be null in the "InProcess" case + _job?.Dispose(); _throttleManager.ThrottleComplete -= HandleThrottleComplete; _throttleManager.Dispose(); @@ -2133,10 +2167,7 @@ public void StartProgress( return; } - if (string.IsNullOrEmpty(computerName)) - { - throw new ArgumentNullException(nameof(computerName)); - } + ArgumentException.ThrowIfNullOrEmpty(computerName); lock (_syncObject) { diff --git a/src/System.Management.Automation/engine/remoting/commands/JobRepository.cs b/src/System.Management.Automation/engine/remoting/commands/JobRepository.cs index 7bb52abcb1e..29d107a074f 100644 --- a/src/System.Management.Automation/engine/remoting/commands/JobRepository.cs +++ b/src/System.Management.Automation/engine/remoting/commands/JobRepository.cs @@ -19,10 +19,7 @@ public abstract class Repository where T : class /// Object to add. public void Add(T item) { - if (item == null) - { - throw new ArgumentNullException(_identifier); - } + ArgumentNullException.ThrowIfNull(item, _identifier); lock (_syncObject) { @@ -45,10 +42,7 @@ public void Add(T item) /// Object to remove. public void Remove(T item) { - if (item == null) - { - throw new ArgumentNullException(_identifier); - } + ArgumentNullException.ThrowIfNull(item, _identifier); lock (_syncObject) { diff --git a/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationFile.cs b/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationFile.cs index 56bd72e90d8..4e669e891f2 100644 --- a/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationFile.cs +++ b/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationFile.cs @@ -15,7 +15,6 @@ namespace Microsoft.PowerShell.Commands { -#if !UNIX /// /// New-PSSessionConfigurationFile command implementation /// @@ -49,7 +48,7 @@ public string Path /// /// Configuration file schema version. /// - [Parameter()] + [Parameter] [ValidateNotNull] public Version SchemaVersion { @@ -69,7 +68,7 @@ public Version SchemaVersion /// /// Configuration file GUID. /// - [Parameter()] + [Parameter] public Guid Guid { get @@ -88,7 +87,7 @@ public Guid Guid /// /// Author of the configuration file. /// - [Parameter()] + [Parameter] public string Author { get @@ -107,7 +106,7 @@ public string Author /// /// Description. /// - [Parameter()] + [Parameter] public string Description { get @@ -126,7 +125,7 @@ public string Description /// /// Company name. /// - [Parameter()] + [Parameter] public string CompanyName { get @@ -145,7 +144,7 @@ public string CompanyName /// /// Copyright information. /// - [Parameter()] + [Parameter] public string Copyright { get @@ -164,7 +163,7 @@ public string Copyright /// /// Specifies type of initial session state to use. /// - [Parameter()] + [Parameter] public SessionType SessionType { get @@ -183,7 +182,7 @@ public SessionType SessionType /// /// Specifies the directory for transcripts to be placed. /// - [Parameter()] + [Parameter] public string TranscriptDirectory { get @@ -202,13 +201,13 @@ public string TranscriptDirectory /// /// Specifies whether to run this configuration under a virtual account. /// - [Parameter()] + [Parameter] public SwitchParameter RunAsVirtualAccount { get; set; } /// /// Specifies groups a virtual account is part of. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] RunAsVirtualAccountGroups { get; set; } @@ -217,7 +216,7 @@ public string TranscriptDirectory /// The User drive is used with Copy-Item for file transfer when the FileSystem provider is /// not visible in the session. /// - [Parameter()] + [Parameter] public SwitchParameter MountUserDrive { get; @@ -229,7 +228,7 @@ public SwitchParameter MountUserDrive /// MountUserDrive parameter. /// If no maximum size is specified then the default drive maximum size is 50MB. /// - [Parameter()] + [Parameter] public long UserDriveMaximumSize { get; set; } // Temporarily removed until script input parameter validation is implemented. @@ -240,7 +239,7 @@ public SwitchParameter MountUserDrive /// If a MountUserDrive is specified for the PSSession then input parameter validation will be /// enabled automatically. /// - [Parameter()] + [Parameter] public SwitchParameter EnforceInputParameterValidation { get; set; } */ @@ -248,13 +247,13 @@ public SwitchParameter MountUserDrive /// Optional parameter that specifies a Group Managed Service Account name in which the configuration /// is run. /// - [Parameter()] + [Parameter] public string GroupManagedServiceAccount { get; set; } /// /// Scripts to process. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] ScriptsToProcess { @@ -274,7 +273,7 @@ public string[] ScriptsToProcess /// /// Role definitions for this session configuration (Role name -> Role capability) /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public IDictionary RoleDefinitions { @@ -294,7 +293,7 @@ public IDictionary RoleDefinitions /// /// Specifies account groups that are membership requirements for this session. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public IDictionary RequiredGroups { @@ -308,7 +307,7 @@ public IDictionary RequiredGroups /// /// Language mode. /// - [Parameter()] + [Parameter] public PSLanguageMode LanguageMode { get @@ -329,7 +328,7 @@ public PSLanguageMode LanguageMode /// /// Execution policy. /// - [Parameter()] + [Parameter] public ExecutionPolicy ExecutionPolicy { get @@ -348,7 +347,7 @@ public ExecutionPolicy ExecutionPolicy /// /// PowerShell version. /// - [Parameter()] + [Parameter] public Version PowerShellVersion { get @@ -367,7 +366,7 @@ public Version PowerShellVersion /// /// A list of modules to import. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public object[] ModulesToImport { @@ -387,7 +386,7 @@ public object[] ModulesToImport /// /// A list of visible aliases. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] VisibleAliases { @@ -407,7 +406,7 @@ public string[] VisibleAliases /// /// A list of visible cmdlets. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public object[] VisibleCmdlets { @@ -427,7 +426,7 @@ public object[] VisibleCmdlets /// /// A list of visible functions. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public object[] VisibleFunctions { @@ -447,7 +446,7 @@ public object[] VisibleFunctions /// /// A list of visible external commands (scripts and applications) /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] VisibleExternalCommands { @@ -467,7 +466,7 @@ public string[] VisibleExternalCommands /// /// A list of providers. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] VisibleProviders { @@ -487,7 +486,7 @@ public string[] VisibleProviders /// /// A list of aliases. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public IDictionary[] AliasDefinitions { @@ -507,7 +506,7 @@ public IDictionary[] AliasDefinitions /// /// A list of functions. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public IDictionary[] FunctionDefinitions { @@ -527,7 +526,7 @@ public IDictionary[] FunctionDefinitions /// /// A list of variables. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public object VariableDefinitions { @@ -547,7 +546,7 @@ public object VariableDefinitions /// /// A list of environment variables. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public IDictionary EnvironmentVariables @@ -568,7 +567,7 @@ public IDictionary EnvironmentVariables /// /// A list of types to process. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] TypesToProcess { @@ -588,7 +587,7 @@ public string[] TypesToProcess /// /// A list of format data to process. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] FormatsToProcess { @@ -608,7 +607,7 @@ public string[] FormatsToProcess /// /// A list of assemblies to load. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] AssembliesToLoad { @@ -629,7 +628,7 @@ public string[] AssembliesToLoad /// Gets or sets whether to include a full expansion of all possible session configuration /// keys as comments when creating the session configuration file. /// - [Parameter()] + [Parameter] public SwitchParameter Full { get; set; } #endregion @@ -1126,7 +1125,6 @@ private bool ShouldGenerateConfigurationSnippet(string parameterName) #endregion } -#endif /// /// New-PSRoleCapabilityFile command implementation @@ -1161,7 +1159,7 @@ public string Path /// /// Configuration file GUID. /// - [Parameter()] + [Parameter] public Guid Guid { get @@ -1180,7 +1178,7 @@ public Guid Guid /// /// Author of the configuration file. /// - [Parameter()] + [Parameter] public string Author { get @@ -1199,7 +1197,7 @@ public string Author /// /// Description. /// - [Parameter()] + [Parameter] public string Description { get @@ -1218,7 +1216,7 @@ public string Description /// /// Company name. /// - [Parameter()] + [Parameter] public string CompanyName { get @@ -1237,7 +1235,7 @@ public string CompanyName /// /// Copyright information. /// - [Parameter()] + [Parameter] public string Copyright { get @@ -1256,7 +1254,7 @@ public string Copyright /// /// A list of modules to import. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public object[] ModulesToImport { @@ -1276,7 +1274,7 @@ public object[] ModulesToImport /// /// A list of visible aliases. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] VisibleAliases { @@ -1296,7 +1294,7 @@ public string[] VisibleAliases /// /// A list of visible cmdlets. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public object[] VisibleCmdlets { @@ -1316,7 +1314,7 @@ public object[] VisibleCmdlets /// /// A list of visible functions. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public object[] VisibleFunctions { @@ -1336,7 +1334,7 @@ public object[] VisibleFunctions /// /// A list of visible external commands (scripts and applications) /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] VisibleExternalCommands { @@ -1356,7 +1354,7 @@ public string[] VisibleExternalCommands /// /// A list of providers. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] VisibleProviders { @@ -1376,7 +1374,7 @@ public string[] VisibleProviders /// /// Scripts to process. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] ScriptsToProcess { @@ -1396,7 +1394,7 @@ public string[] ScriptsToProcess /// /// A list of aliases. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public IDictionary[] AliasDefinitions { @@ -1416,7 +1414,7 @@ public IDictionary[] AliasDefinitions /// /// A list of functions. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public IDictionary[] FunctionDefinitions { @@ -1436,7 +1434,7 @@ public IDictionary[] FunctionDefinitions /// /// A list of variables. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public object VariableDefinitions { @@ -1456,7 +1454,7 @@ public object VariableDefinitions /// /// A list of environment variables. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public IDictionary EnvironmentVariables @@ -1477,7 +1475,7 @@ public IDictionary EnvironmentVariables /// /// A list of types to process. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] TypesToProcess { @@ -1497,7 +1495,7 @@ public string[] TypesToProcess /// /// A list of format data to process. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] FormatsToProcess { @@ -1517,7 +1515,7 @@ public string[] FormatsToProcess /// /// A list of assemblies to load. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] AssembliesToLoad { @@ -1868,12 +1866,10 @@ internal static string ConfigFragment(string key, string resourceString, string if (isExample) { - return string.Format(CultureInfo.InvariantCulture, "# {0}{1}# {2:19} = {3}{4}{5}", - resourceString, nl, key, value, nl, nl); + return string.Format(CultureInfo.InvariantCulture, "# {0}{1}# {2:19} = {3}{4}{5}", resourceString, nl, key, value, nl, nl); } - return string.Format(CultureInfo.InvariantCulture, "# {0}{1}{2:19} = {3}{4}{5}", - resourceString, nl, key, value, nl, nl); + return string.Format(CultureInfo.InvariantCulture, "# {0}{1}{2:19} = {3}{4}{5}", resourceString, nl, key, value, nl, nl); } /// @@ -1884,7 +1880,10 @@ internal static string ConfigFragment(string key, string resourceString, string internal static string QuoteName(object name) { if (name == null) + { return "''"; + } + return "'" + System.Management.Automation.Language.CodeGeneration.EscapeSingleQuotedStringContent(name.ToString()) + "'"; } @@ -1944,7 +1943,7 @@ internal static string CombineHashtable(IDictionary table, StreamWriter writer, sb.Append("@{"); - var keys = table.Keys.Cast().OrderBy(x => x); + var keys = table.Keys.Cast().Order(); foreach (var key in keys) { sb.Append(writer.NewLine); diff --git a/src/System.Management.Automation/engine/remoting/commands/NewPSSessionOptionCommand.cs b/src/System.Management.Automation/engine/remoting/commands/NewPSSessionOptionCommand.cs index 2a6dbc30679..bb7641d2e5b 100644 --- a/src/System.Management.Automation/engine/remoting/commands/NewPSSessionOptionCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/NewPSSessionOptionCommand.cs @@ -136,11 +136,13 @@ public int OpenTimeout { get { - return _openTimeout.HasValue ? _openTimeout.Value : - RunspaceConnectionInfo.DefaultOpenTimeout; + return _openTimeout ?? RunspaceConnectionInfo.DefaultOpenTimeout; } - set { _openTimeout = value; } + set + { + _openTimeout = value; + } } private int? _openTimeout; @@ -161,11 +163,13 @@ public int CancelTimeout { get { - return _cancelTimeout.HasValue ? _cancelTimeout.Value : - BaseTransportManager.ClientCloseTimeoutMs; + return _cancelTimeout ?? BaseTransportManager.ClientCloseTimeoutMs; } - set { _cancelTimeout = value; } + set + { + _cancelTimeout = value; + } } private int? _cancelTimeout; @@ -183,11 +187,13 @@ public int IdleTimeout { get { - return _idleTimeout.HasValue ? _idleTimeout.Value - : RunspaceConnectionInfo.DefaultIdleTimeout; + return _idleTimeout ?? RunspaceConnectionInfo.DefaultIdleTimeout; } - set { _idleTimeout = value; } + set + { + _idleTimeout = value; + } } private int? _idleTimeout; @@ -290,11 +296,13 @@ public int OperationTimeout { get { - return (_operationtimeout.HasValue ? _operationtimeout.Value : - BaseTransportManager.ClientDefaultOperationTimeoutMs); + return _operationtimeout ?? BaseTransportManager.ClientDefaultOperationTimeoutMs; } - set { _operationtimeout = value; } + set + { + _operationtimeout = value; + } } private int? _operationtimeout; @@ -308,7 +316,10 @@ public int OperationTimeout [Parameter] public SwitchParameter NoEncryption { - get { return _noencryption; } + get + { + return _noencryption; + } set { @@ -327,7 +338,10 @@ public SwitchParameter NoEncryption [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "UTF")] public SwitchParameter UseUTF16 { - get { return _useutf16; } + get + { + return _useutf16; + } set { diff --git a/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs b/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs index 9fafd34eec4..5988884d16b 100644 --- a/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs +++ b/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs @@ -15,6 +15,7 @@ using System.Management.Automation.Remoting; using System.Management.Automation.Remoting.Client; using System.Management.Automation.Runspaces; +using System.Threading; using Dbg = System.Management.Automation.Diagnostics; @@ -202,7 +203,7 @@ internal string GetMessage(string resourceString, params object[] args) /// /// Default shellname. /// - protected const string DefaultPowerShellRemoteShellName = System.Management.Automation.Remoting.Client.WSManNativeApi.ResourceURIPrefix + "Microsoft.PowerShell"; + protected const string DefaultPowerShellRemoteShellName = WSManNativeApi.ResourceURIPrefix + "Microsoft.PowerShell"; /// /// Default application name for the connection uri. @@ -285,6 +286,8 @@ internal struct SSHConnection public string KeyFilePath; public int Port; public string Subsystem; + public int ConnectingTimeout; + public Hashtable Options; } /// @@ -442,6 +445,37 @@ internal enum VMState FastSavingCritical, } +#nullable enable + /// + /// Get the State property from Get-VM result. + /// + /// The raw PSObject as returned by Get-VM. + /// The VMState value of the State property if present and parsable, otherwise null. + internal VMState? GetVMStateProperty(PSObject value) + { + object? rawState = value.Properties["State"].Value; + if (rawState is Enum enumState) + { + // If the Hyper-V module was directly importable we have the VMState enum + // value which we can just cast to our VMState type. + return (VMState)enumState; + } + else if (rawState is string stringState && Enum.TryParse(stringState, true, out VMState result)) + { + // If the Hyper-V module was imported through implicit remoting on old + // Windows versions we get a string back which we will try and parse + // as the enum label. + return result; + } + + // Unknown scenario, this should not happen. + string message = PSRemotingErrorInvariants.FormatResourceString( + RemotingErrorIdStrings.HyperVFailedToGetStateUnknownType, + rawState?.GetType()?.FullName ?? "null"); + throw new InvalidOperationException(message); + } +#nullable disable + #endregion #region Tracer @@ -589,7 +623,7 @@ public virtual PSCredential Credential /// /// This parameters specifies the appname which identifies the connection /// end point on the remote machine. If this parameter is not specified - /// then the value specified in DEFAULTREMOTEAPPNAME will be used. If thats + /// then the value specified in DEFAULTREMOTEAPPNAME will be used. If that's /// not specified as well, then "WSMAN" will be used. /// [Parameter(ValueFromPipelineByPropertyName = true, @@ -761,6 +795,20 @@ public virtual string KeyFilePath set; } + /// + /// Gets or sets a value for the SSH subsystem to use for the remote connection. + /// + [Parameter(ValueFromPipelineByPropertyName = true, + ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)] + public virtual string Subsystem { get; set; } + + /// + /// Gets or sets a value in milliseconds that limits the time allowed for an SSH connection to be established. + /// Default timeout value is infinite. + /// + [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)] + public virtual int ConnectingTimeout { get; set; } = Timeout.Infinite; + /// /// This parameter specifies that SSH is used to establish the remote /// connection and act as the remoting transport. By default WinRM is used @@ -790,11 +838,11 @@ public virtual Hashtable[] SSHConnection } /// - /// This parameter specifies the SSH subsystem to use for the remote connection. + /// Gets or sets the Hashtable containing options to be passed to OpenSSH. /// - [Parameter(ValueFromPipelineByPropertyName = true, - ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] - public virtual string Subsystem { get; set; } + [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] + [ValidateNotNullOrEmpty] + public virtual Hashtable Options { get; set; } #endregion @@ -856,6 +904,8 @@ internal static void ValidateSpecifiedAuthentication(PSCredential credential, st private const string IdentityFilePathAlias = "IdentityFilePath"; private const string PortParameter = "Port"; private const string SubsystemParameter = "Subsystem"; + private const string ConnectingTimeoutParameter = "ConnectingTimeout"; + private const string OptionsParameter = "Options"; #endregion @@ -902,7 +952,7 @@ protected void ParseSshHostName(string hostname, out string host, out string use /// Array of SSHConnection objects. internal SSHConnection[] ParseSSHConnectionHashTable() { - List connections = new List(); + List connections = new(); foreach (var item in this.SSHConnection) { if (item.ContainsKey(ComputerNameParameter) && item.ContainsKey(HostNameAlias)) @@ -915,7 +965,7 @@ internal SSHConnection[] ParseSSHConnectionHashTable() throw new PSArgumentException(RemotingErrorIdStrings.SSHConnectionDuplicateKeyPath); } - SSHConnection connectionInfo = new SSHConnection(); + SSHConnection connectionInfo = new(); foreach (var key in item.Keys) { string paramName = key as string; @@ -955,6 +1005,14 @@ internal SSHConnection[] ParseSSHConnectionHashTable() { connectionInfo.Subsystem = GetSSHConnectionStringParameter(item[paramName]); } + else if (paramName.Equals(ConnectingTimeoutParameter, StringComparison.OrdinalIgnoreCase)) + { + connectionInfo.ConnectingTimeout = GetSSHConnectionIntParameter(item[paramName]); + } + else if (paramName.Equals(OptionsParameter, StringComparison.OrdinalIgnoreCase)) + { + connectionInfo.Options = item[paramName] as Hashtable; + } else { throw new PSArgumentException( @@ -1448,9 +1506,9 @@ protected void CreateHelpersForSpecifiedSSHComputerNames() { ParseSshHostName(computerName, out string host, out string userName, out int port); - var sshConnectionInfo = new SSHConnectionInfo(userName, host, this.KeyFilePath, port, this.Subsystem); + var sshConnectionInfo = new SSHConnectionInfo(userName, host, KeyFilePath, port, Subsystem, ConnectingTimeout, Options); var typeTable = TypeTable.LoadDefaultTypeFiles(); - var remoteRunspace = RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace; + var remoteRunspace = RunspaceFactory.CreateRunspace(sshConnectionInfo, Host, typeTable) as RemoteRunspace; var pipeline = CreatePipeline(remoteRunspace); var operation = new ExecutionCmdletHelperComputerName(remoteRunspace, pipeline); @@ -1471,7 +1529,8 @@ protected void CreateHelpersForSpecifiedSSHHashComputerNames() sshConnection.ComputerName, sshConnection.KeyFilePath, sshConnection.Port, - sshConnection.Subsystem); + sshConnection.Subsystem, + sshConnection.ConnectingTimeout); var typeTable = TypeTable.LoadDefaultTypeFiles(); var remoteRunspace = RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace; var pipeline = CreatePipeline(remoteRunspace); @@ -1630,7 +1689,7 @@ protected virtual void CreateHelpersForSpecifiedVMSession() { this.VMName[index] = (string)results[0].Properties["VMName"].Value; - if ((VMState)results[0].Properties["State"].Value == VMState.Running) + if (GetVMStateProperty(results[0]) == VMState.Running) { vmIsRunning[index] = true; } @@ -1679,7 +1738,7 @@ protected virtual void CreateHelpersForSpecifiedVMSession() this.VMId[index] = (Guid)results[0].Properties["VMId"].Value; this.VMName[index] = (string)results[0].Properties["VMName"].Value; - if ((VMState)results[0].Properties["State"].Value == VMState.Running) + if (GetVMStateProperty(results[0]) == VMState.Running) { vmIsRunning[index] = true; } @@ -1932,11 +1991,7 @@ private static string GetRemoteServerPsVersion(RemoteRunspace remoteRunspace) /// /// Adds forwarded events to the local queue. /// - internal void OnRunspacePSEventReceived(object sender, PSEventArgs e) - { - if (this.Events != null) - this.Events.AddForwardedEvent(e); - } + internal void OnRunspacePSEventReceived(object sender, PSEventArgs e) => this.Events?.AddForwardedEvent(e); #endregion Private Methods @@ -2254,7 +2309,7 @@ private System.Management.Automation.PowerShell GetPowerShellForPSv3OrLater(stri // Semantic checks on the using statement have already validated that there are no arbitrary expressions, // so we'll allow these expressions in everything but NoLanguage mode. - bool allowUsingExpressions = (Context.SessionState.LanguageMode != PSLanguageMode.NoLanguage); + bool allowUsingExpressions = Context.SessionState.LanguageMode != PSLanguageMode.NoLanguage; object[] usingValuesInArray = null; IDictionary usingValuesInDict = null; @@ -2308,7 +2363,7 @@ private System.Management.Automation.PowerShell ConvertToPowerShell() try { // This is trusted input as long as we're in FullLanguage mode - bool isTrustedInput = (Context.LanguageMode == PSLanguageMode.FullLanguage); + bool isTrustedInput = Context.LanguageMode == PSLanguageMode.FullLanguage; powershell = _scriptBlock.GetPowerShell(isTrustedInput, _args); } catch (ScriptBlockToPowerShellNotSupportedException) @@ -2359,7 +2414,8 @@ private string GetConvertedScript(out List newParameterNames, out List GetUsingVariableValues(List paramUsi // GetExpressionValue ensures that it only does variable access when supplied a VariableExpressionAst. // So, this is still safe to use in ConstrainedLanguage and will not result in arbitrary code // execution. - bool allowVariableAccess = (Context.SessionState.LanguageMode != PSLanguageMode.NoLanguage); + bool allowVariableAccess = Context.SessionState.LanguageMode != PSLanguageMode.NoLanguage; foreach (var varAst in paramUsingVars) { @@ -2435,13 +2491,10 @@ private List GetUsingVariableValues(List paramUsi /// A list of UsingExpressionAsts ordered by the StartOffset. private static List GetUsingVariables(ScriptBlock localScriptBlock) { - if (localScriptBlock == null) - { - throw new ArgumentNullException(nameof(localScriptBlock), "Caller needs to make sure the parameter value is not null"); - } + ArgumentNullException.ThrowIfNull(localScriptBlock, "Caller needs to make sure the parameter value is not null"); var allUsingExprs = UsingExpressionAstSearcher.FindAllUsingExpressions(localScriptBlock.Ast); - return allUsingExprs.Select(usingExpr => UsingExpressionAst.ExtractUsingVariable((UsingExpressionAst)usingExpr)).ToList(); + return allUsingExprs.Select(static usingExpr => UsingExpressionAst.ExtractUsingVariable((UsingExpressionAst)usingExpr)).ToList(); } #endregion "UsingExpression Utilities" @@ -3452,10 +3505,7 @@ private void RaiseOperationCompleteEvent(EventArgs baseEventArgs) OperationState.StopComplete; operationStateEventArgs.BaseEvent = baseEventArgs; - if (OperationComplete != null) - { - OperationComplete.SafeInvoke(this, operationStateEventArgs); - } + OperationComplete?.SafeInvoke(this, operationStateEventArgs); } } @@ -3650,11 +3700,7 @@ private void HandlePipelineStateChanged(object sender, case PipelineState.Completed: case PipelineState.Stopped: case PipelineState.Failed: - if (RemoteRunspace != null) - { - RemoteRunspace.CloseAsync(); - } - + RemoteRunspace?.CloseAsync(); break; } } @@ -3925,9 +3971,9 @@ internal Collection GetDisconnectedSessions(Collection [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public new string HostName { get; set; } + /// + /// Gets or sets the Hashtable containing options to be passed to OpenSSH. + /// + [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)] + [ValidateNotNullOrEmpty] + public override Hashtable Options + { + get + { + return base.Options; + } + + set + { + base.Options = value; + } + } + #endregion /// @@ -152,7 +170,7 @@ public class EnterPSSessionCommand : PSRemotingBaseCmdlet ParameterSetName = VMIdParameterSet)] [Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = VMNameParameterSet)] - [Credential()] + [Credential] public override PSCredential Credential { get { return base.Credential; } @@ -247,7 +265,7 @@ protected override void ProcessRecord() } // for the console host and Graphical PowerShell host - // we want to skip pushing into the the runspace if + // we want to skip pushing into the runspace if // the host is in a nested prompt System.Management.Automation.Internal.Host.InternalHost chost = this.Host as System.Management.Automation.Internal.Host.InternalHost; @@ -314,7 +332,10 @@ protected override void ProcessRecord() } // If runspace is null then the error record has already been written and we can exit. - if (remoteRunspace == null) { return; } + if (remoteRunspace == null) + { + return; + } // If the runspace is in a disconnected state try to connect. bool runspaceConnected = false; @@ -1004,7 +1025,7 @@ private RemoteRunspace GetRunspaceForVMSession() // // VM should be in running state. // - if ((VMState)results[0].Properties["State"].Value != VMState.Running) + if (GetVMStateProperty(results[0]) != VMState.Running) { WriteError( new ErrorRecord( @@ -1262,7 +1283,7 @@ private RemoteRunspace GetRunspaceForContainerSession() private RemoteRunspace GetRunspaceForSSHSession() { ParseSshHostName(HostName, out string host, out string userName, out int port); - var sshConnectionInfo = new SSHConnectionInfo(userName, host, this.KeyFilePath, port, this.Subsystem); + var sshConnectionInfo = new SSHConnectionInfo(userName, host, KeyFilePath, port, Subsystem, ConnectingTimeout, Options); var typeTable = TypeTable.LoadDefaultTypeFiles(); // Use the class _tempRunspace field while the runspace is being opened so that StopProcessing can be handled at that time. diff --git a/src/System.Management.Automation/engine/remoting/commands/ReceiveJob.cs b/src/System.Management.Automation/engine/remoting/commands/ReceiveJob.cs index 97585f1de1b..b1759d6a88e 100644 --- a/src/System.Management.Automation/engine/remoting/commands/ReceiveJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/ReceiveJob.cs @@ -171,7 +171,7 @@ public PSSession[] Session /// If the results need to be not removed from the store /// after being written. Default is results are removed. /// - [Parameter()] + [Parameter] public SwitchParameter Keep { get @@ -190,7 +190,7 @@ public SwitchParameter Keep /// /// - [Parameter()] + [Parameter] public SwitchParameter NoRecurse { get @@ -208,7 +208,7 @@ public SwitchParameter NoRecurse /// /// - [Parameter()] + [Parameter] public SwitchParameter Force { get; set; } @@ -245,7 +245,7 @@ public override string[] Command /// /// - [Parameter()] + [Parameter] public SwitchParameter Wait { get @@ -262,7 +262,7 @@ public SwitchParameter Wait /// /// - [Parameter()] + [Parameter] public SwitchParameter AutoRemoveJob { get @@ -278,7 +278,7 @@ public SwitchParameter AutoRemoveJob /// /// - [Parameter()] + [Parameter] public SwitchParameter WriteEvents { get @@ -294,7 +294,7 @@ public SwitchParameter WriteEvents /// /// - [Parameter()] + [Parameter] public SwitchParameter WriteJobInResults { get @@ -823,10 +823,7 @@ private void WriteJobResults(Job job) { if (v == null) continue; MshCommandRuntime mshCommandRuntime = CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteVerbose(v, true); - } + mshCommandRuntime?.WriteVerbose(v, true); } Collection debugRecords = ReadAll(job.Debug); @@ -835,10 +832,7 @@ private void WriteJobResults(Job job) { if (d == null) continue; MshCommandRuntime mshCommandRuntime = CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteDebug(d, true); - } + mshCommandRuntime?.WriteDebug(d, true); } Collection warningRecords = ReadAll(job.Warning); @@ -847,10 +841,7 @@ private void WriteJobResults(Job job) { if (w == null) continue; MshCommandRuntime mshCommandRuntime = CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteWarning(w, true); - } + mshCommandRuntime?.WriteWarning(w, true); } Collection progressRecords = ReadAll(job.Progress); @@ -859,10 +850,7 @@ private void WriteJobResults(Job job) { if (p == null) continue; MshCommandRuntime mshCommandRuntime = CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteProgress(p, true); - } + mshCommandRuntime?.WriteProgress(p, true); } Collection informationRecords = ReadAll(job.Information); @@ -871,10 +859,7 @@ private void WriteJobResults(Job job) { if (p == null) continue; MshCommandRuntime mshCommandRuntime = CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteInformation(p, true); - } + mshCommandRuntime?.WriteInformation(p, true); } } @@ -1065,10 +1050,7 @@ private void AggregateResultsFromJob(Job job) { lock (_syncObject) { - if (_outputProcessingNotification == null) - { - _outputProcessingNotification = new OutputProcessingState(); - } + _outputProcessingNotification ??= new OutputProcessingState(); } } @@ -1181,14 +1163,21 @@ private void Error_DataAdded(object sender, DataAddedEventArgs e) { lock (_syncObject) { - if (_isDisposed) return; + if (_isDisposed) + { + return; + } } _writeExistingData.WaitOne(); _resultsReaderWriterLock.EnterReadLock(); try { - if (!_results.IsOpen) return; + if (!_results.IsOpen) + { + return; + } + PSDataCollection errorRecords = sender as PSDataCollection; Diagnostics.Assert(errorRecords != null, "PSDataCollection is raising an inappropriate event"); ErrorRecord errorRecord = GetData(errorRecords, e.Index); diff --git a/src/System.Management.Automation/engine/remoting/commands/ReceivePSSession.cs b/src/System.Management.Automation/engine/remoting/commands/ReceivePSSession.cs index 12cced4ae91..b9cf33e4b1c 100644 --- a/src/System.Management.Automation/engine/remoting/commands/ReceivePSSession.cs +++ b/src/System.Management.Automation/engine/remoting/commands/ReceivePSSession.cs @@ -36,7 +36,7 @@ namespace Microsoft.PowerShell.Commands /// /// The user can specify how command output data is returned by using the public /// OutTarget enumeration (Host, Job). - /// The default actions of this cmdlet is to always direct ouput to host unless + /// The default actions of this cmdlet is to always direct output to host unless /// a job object already exists on the client that is associated with the running /// command. In this case the existing job object is connected to the running /// command and returned. @@ -116,7 +116,7 @@ public class ReceivePSSessionCommand : PSRemotingCmdlet /// /// This parameters specifies the appname which identifies the connection /// end point on the remote machine. If this parameter is not specified - /// then the value specified in DEFAULTREMOTEAPPNAME will be used. If thats + /// then the value specified in DEFAULTREMOTEAPPNAME will be used. If that's /// not specified as well, then "WSMAN" will be used. /// [Parameter(ValueFromPipelineByPropertyName = true, @@ -125,7 +125,10 @@ public class ReceivePSSessionCommand : PSRemotingCmdlet ParameterSetName = ReceivePSSessionCommand.ComputerInstanceIdParameterSet)] public string ApplicationName { - get { return _appName; } + get + { + return _appName; + } set { @@ -150,7 +153,10 @@ public string ApplicationName ParameterSetName = ReceivePSSessionCommand.ConnectionUriInstanceIdParameterSet)] public string ConfigurationName { - get { return _shell; } + get + { + return _shell; + } set { @@ -254,10 +260,13 @@ public SwitchParameter AllowRedirection [Parameter(ParameterSetName = ReceivePSSessionCommand.ComputerSessionNameParameterSet)] [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriSessionNameParameterSet)] [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriInstanceIdParameterSet)] - [Credential()] + [Credential] public PSCredential Credential { - get { return _psCredential; } + get + { + return _psCredential; + } set { @@ -278,7 +287,10 @@ public PSCredential Credential [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriInstanceIdParameterSet)] public AuthenticationMechanism Authentication { - get { return _authentication; } + get + { + return _authentication; + } set { @@ -300,7 +312,10 @@ public AuthenticationMechanism Authentication [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriInstanceIdParameterSet)] public string CertificateThumbprint { - get { return _thumbprint; } + get + { + return _thumbprint; + } set { @@ -389,15 +404,8 @@ protected override void StopProcessing() tmpJob = _job; } - if (tmpPipeline != null) - { - tmpPipeline.StopAsync(); - } - - if (tmpJob != null) - { - tmpJob.StopJob(); - } + tmpPipeline?.StopAsync(); + tmpJob?.StopJob(); } #endregion @@ -438,9 +446,9 @@ private void QueryForAndConnectCommands(string name, Guid instanceId) string shellUri = null; if (!string.IsNullOrEmpty(ConfigurationName)) { - shellUri = (ConfigurationName.IndexOf( - System.Management.Automation.Remoting.Client.WSManNativeApi.ResourceURIPrefix, StringComparison.OrdinalIgnoreCase) != -1) ? - ConfigurationName : System.Management.Automation.Remoting.Client.WSManNativeApi.ResourceURIPrefix + ConfigurationName; + shellUri = ConfigurationName.Contains(WSManNativeApi.ResourceURIPrefix, StringComparison.OrdinalIgnoreCase) + ? ConfigurationName + : WSManNativeApi.ResourceURIPrefix + ConfigurationName; } // Connect selected runspace/command and direct command output to host @@ -803,19 +811,13 @@ private void DisconnectAndStopRunningCmds(RemoteRunspace remoteRunspace) remoteRunspace.Disconnect(); - if (stopPipelineReceive != null) + try { - try - { - stopPipelineReceive.Set(); - } - catch (ObjectDisposedException) { } + stopPipelineReceive?.Set(); } + catch (ObjectDisposedException) { } - if (job != null) - { - job.StopJob(); - } + job?.StopJob(); } } @@ -849,7 +851,10 @@ private void ConnectSessionToHost(PSSession session, PSRemotingJob job = null) { Job childJob = job.ChildJobs[0]; job.ConnectJobs(); - if (CheckForDebugMode(session, true)) { return; } + if (CheckForDebugMode(session, true)) + { + return; + } do { @@ -861,10 +866,7 @@ private void ConnectSessionToHost(PSSession session, PSRemotingJob job = null) foreach (var result in childJob.ReadAll()) { - if (result != null) - { - result.WriteStreamObject(this); - } + result?.WriteStreamObject(this); } if (index == 0) @@ -922,7 +924,10 @@ private void ConnectSessionToHost(PSSession session, PSRemotingJob job = null) pipelineConnectedEvent = null; - if (CheckForDebugMode(session, true)) { return; } + if (CheckForDebugMode(session, true)) + { + return; + } // Wait for remote command to complete, while writing any available data. while (!_remotePipeline.Output.EndOfPipeline) @@ -1101,7 +1106,10 @@ private void ConnectSessionToJob(PSSession session, PSRemotingJob job = null) } } - if (CheckForDebugMode(session, true)) { return; } + if (CheckForDebugMode(session, true)) + { + return; + } // Write the job object to output. WriteObject(job); @@ -1325,7 +1333,7 @@ public enum OutTarget Host = 1, /// - /// Asynchronous mode. Receive-PSSession ouput data goes to returned job object. + /// Asynchronous mode. Receive-PSSession output data goes to returned job object. /// Job = 2 } diff --git a/src/System.Management.Automation/engine/remoting/commands/RemoveJob.cs b/src/System.Management.Automation/engine/remoting/commands/RemoveJob.cs index f8c0a71a0df..af98749c4e5 100644 --- a/src/System.Management.Automation/engine/remoting/commands/RemoveJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/RemoveJob.cs @@ -61,12 +61,17 @@ internal List FindJobsMatchingByName( List matches = new List(); Hashtable duplicateDetector = new Hashtable(); - if (_names == null) return matches; + if (_names == null) + { + return matches; + } foreach (string name in _names) { if (string.IsNullOrEmpty(name)) + { continue; + } // search all jobs in repository. bool jobFound = false; @@ -94,7 +99,10 @@ internal List FindJobsMatchingByName( jobFound = jobFound || job2Found; // if a match is not found, write an error) - if (jobFound || !writeErrorOnNoMatch || WildcardPattern.ContainsWildcardCharacters(name)) continue; + if (jobFound || !writeErrorOnNoMatch || WildcardPattern.ContainsWildcardCharacters(name)) + { + continue; + } Exception ex = PSTraceSource.NewArgumentException(NameParameter, RemotingErrorIdStrings.JobWithSpecifiedNameNotFound, name); WriteError(new ErrorRecord(ex, "JobWithSpecifiedNameNotFound", ErrorCategory.ObjectNotFound, name)); @@ -191,7 +199,10 @@ internal List FindJobsMatchingByInstanceId(bool recurse, bool writeobject, Hashtable duplicateDetector = new Hashtable(); - if (_instanceIds == null) return matches; + if (_instanceIds == null) + { + return matches; + } foreach (Guid id in _instanceIds) { @@ -217,7 +228,10 @@ internal List FindJobsMatchingByInstanceId(bool recurse, bool writeobject, jobFound = jobFound || job2Found; - if (jobFound || !writeErrorOnNoMatch) continue; + if (jobFound || !writeErrorOnNoMatch) + { + continue; + } Exception ex = PSTraceSource.NewArgumentException(InstanceIdParameter, RemotingErrorIdStrings.JobWithSpecifiedInstanceIdNotFound, @@ -306,7 +320,10 @@ internal List FindJobsMatchingBySessionId(bool recurse, bool writeobject, b { List matches = new List(); - if (_sessionIds == null) return matches; + if (_sessionIds == null) + { + return matches; + } Hashtable duplicateDetector = new Hashtable(); @@ -331,7 +348,10 @@ internal List FindJobsMatchingBySessionId(bool recurse, bool writeobject, b jobFound = jobFound || job2Found; - if (jobFound || !writeErrorOnNoMatch) continue; + if (jobFound || !writeErrorOnNoMatch) + { + continue; + } Exception ex = PSTraceSource.NewArgumentException(SessionIdParameter, RemotingErrorIdStrings.JobWithSpecifiedSessionIdNotFound, id); WriteError(new ErrorRecord(ex, "JobWithSpecifiedSessionNotFound", ErrorCategory.ObjectNotFound, id)); @@ -408,7 +428,10 @@ internal List FindJobsMatchingByCommand( { List matches = new List(); - if (_commands == null) return matches; + if (_commands == null) + { + return matches; + } List jobs = new List(); @@ -476,7 +499,10 @@ internal List FindJobsMatchingByState( foreach (Job job in jobs) { - if (job.JobStateInfo.State != _jobstate) continue; + if (job.JobStateInfo.State != _jobstate) + { + continue; + } if (writeobject) { @@ -558,7 +584,10 @@ private static bool FindJobsMatchingByFilterHelper(List matches, List internal List CopyJobsToList(Job[] jobs, bool writeobject, bool checkIfJobCanBeRemoved) { List matches = new List(); - if (jobs == null) return matches; + if (jobs == null) + { + return matches; + } foreach (Job job in jobs) { @@ -890,10 +919,12 @@ protected override void ProcessRecord() // Now actually remove the jobs foreach (Job job in listOfJobsToRemove) { - string message = GetMessage(RemotingErrorIdStrings.StopPSJobWhatIfTarget, - job.Command, job.Id); + string message = GetMessage(RemotingErrorIdStrings.StopPSJobWhatIfTarget, job.Command, job.Id); - if (!ShouldProcess(message, VerbsCommon.Remove)) continue; + if (!ShouldProcess(message, VerbsCommon.Remove)) + { + continue; + } Job2 job2 = job as Job2; if (!job.IsFinishedState(job.JobStateInfo.State)) @@ -1037,7 +1068,11 @@ public void Dispose() /// protected void Dispose(bool disposing) { - if (!disposing) return; + if (!disposing) + { + return; + } + foreach (var pair in _cleanUpActions) { pair.Key.StopJobCompleted -= pair.Value; diff --git a/src/System.Management.Automation/engine/remoting/commands/ResumeJob.cs b/src/System.Management.Automation/engine/remoting/commands/ResumeJob.cs index 1b5fcd7bbe4..0f9d302d6e1 100644 --- a/src/System.Management.Automation/engine/remoting/commands/ResumeJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/ResumeJob.cs @@ -185,7 +185,7 @@ private void HandleResumeJobCompleted(object sender, AsyncCompletedEventArgs eve { foreach ( var e in - parentJob.ExecutionError.Where(e => e.FullyQualifiedErrorId == "ContainerParentJobResumeAsyncError") + parentJob.ExecutionError.Where(static e => e.FullyQualifiedErrorId == "ContainerParentJobResumeAsyncError") ) { if (e.Exception is InvalidJobStateException) @@ -231,15 +231,31 @@ protected override void EndProcessing() { _needToCheckForWaitingJobs = true; if (_pendingJobs.Count > 0) + { jobsPending = true; + } } if (Wait && jobsPending) + { _waitForJobs.WaitOne(); + } + + if (_warnInvalidState) + { + WriteWarning(RemotingErrorIdStrings.ResumeJobInvalidJobState); + } + + foreach (var e in _errorsToWrite) + { + WriteError(e); + } + + foreach (var j in _allJobsToResume) + { + WriteObject(j); + } - if (_warnInvalidState) WriteWarning(RemotingErrorIdStrings.ResumeJobInvalidJobState); - foreach (var e in _errorsToWrite) WriteError(e); - foreach (var j in _allJobsToResume) WriteObject(j); base.EndProcessing(); } @@ -267,7 +283,11 @@ public void Dispose() /// protected void Dispose(bool disposing) { - if (!disposing) return; + if (!disposing) + { + return; + } + foreach (var pair in _cleanUpActions) { pair.Key.ResumeJobCompleted -= pair.Value; diff --git a/src/System.Management.Automation/engine/remoting/commands/StartJob.cs b/src/System.Management.Automation/engine/remoting/commands/StartJob.cs index 90e10bb8f44..05611d8c0ae 100644 --- a/src/System.Management.Automation/engine/remoting/commands/StartJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/StartJob.cs @@ -222,7 +222,7 @@ public override string Subsystem [Parameter(ParameterSetName = StartJobCommand.FilePathComputerNameParameterSet)] [Parameter(ParameterSetName = StartJobCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = StartJobCommand.LiteralFilePathComputerNameParameterSet)] - [Credential()] + [Credential] public override PSCredential Credential { get @@ -484,7 +484,7 @@ public virtual ScriptBlock InitializationScript /// Gets or sets an initial working directory for the powershell background job. /// [Parameter] - [ValidateNotNullOrEmpty] + [ValidateNotNullOrWhiteSpace] public string WorkingDirectory { get; set; } /// @@ -512,10 +512,12 @@ public virtual Version PSVersion set { - RemotingCommandUtil.CheckPSVersion(value); - - // Check if specified version of PowerShell is installed - RemotingCommandUtil.CheckIfPowerShellVersionIsInstalled(value); + // PSVersion value can only be 5.1 for Start-Job. + if (!(value.Major == 5 && value.Minor == 1)) + { + throw new ArgumentException( + StringUtil.Format(RemotingErrorIdStrings.PSVersionParameterOutOfRange, value, "PSVersion")); + } _psVersion = value; } @@ -599,7 +601,7 @@ protected override void BeginProcessing() ThrowTerminatingError(errorRecord); } - if (WorkingDirectory != null && !Directory.Exists(WorkingDirectory)) + if (WorkingDirectory != null && !InvokeProvider.Item.IsContainer(WorkingDirectory)) { string message = StringUtil.Format(RemotingErrorIdStrings.StartJobWorkingDirectoryNotFound, WorkingDirectory); var errorRecord = new ErrorRecord( @@ -644,7 +646,7 @@ protected override void CreateHelpersForSpecifiedComputerNames() { // If we're in ConstrainedLanguage mode and the system is in lockdown mode, // ensure that they haven't specified a ScriptBlock or InitScript - as - // we can't protect that boundary + // we can't protect that boundary. if ((Context.LanguageMode == PSLanguageMode.ConstrainedLanguage) && (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Enforce) && ((ScriptBlock != null) || (InitializationScript != null))) diff --git a/src/System.Management.Automation/engine/remoting/commands/StopJob.cs b/src/System.Management.Automation/engine/remoting/commands/StopJob.cs index 7890bdc54f0..c298b495bb9 100644 --- a/src/System.Management.Automation/engine/remoting/commands/StopJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/StopJob.cs @@ -137,7 +137,11 @@ protected override void ProcessRecord() foreach (Job job in jobsToStop) { - if (this.Stopping) return; + if (this.Stopping) + { + return; + } + if (job.IsFinishedState(job.JobStateInfo.State)) { continue; @@ -280,7 +284,11 @@ public void Dispose() /// protected void Dispose(bool disposing) { - if (!disposing) return; + if (!disposing) + { + return; + } + foreach (var pair in _cleanUpActions) { pair.Key.StopJobCompleted -= pair.Value; diff --git a/src/System.Management.Automation/engine/remoting/commands/SuspendJob.cs b/src/System.Management.Automation/engine/remoting/commands/SuspendJob.cs index 1c729d8df74..e454247ae9a 100644 --- a/src/System.Management.Automation/engine/remoting/commands/SuspendJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/SuspendJob.cs @@ -292,7 +292,7 @@ private void ProcessExecutionErrorsAndReleaseWaitHandle(Job job) { foreach ( var e in - parentJob.ExecutionError.Where(e => e.FullyQualifiedErrorId == "ContainerParentJobSuspendAsyncError") + parentJob.ExecutionError.Where(static e => e.FullyQualifiedErrorId == "ContainerParentJobSuspendAsyncError") ) { if (e.Exception is InvalidJobStateException) @@ -325,15 +325,31 @@ protected override void EndProcessing() { _needToCheckForWaitingJobs = true; if (_pendingJobs.Count > 0) + { haveToWait = true; + } } if (haveToWait) + { _waitForJobs.WaitOne(); + } + + if (_warnInvalidState) + { + WriteWarning(RemotingErrorIdStrings.SuspendJobInvalidJobState); + } + + foreach (var e in _errorsToWrite) + { + WriteError(e); + } + + foreach (var j in _allJobsToSuspend) + { + WriteObject(j); + } - if (_warnInvalidState) WriteWarning(RemotingErrorIdStrings.SuspendJobInvalidJobState); - foreach (var e in _errorsToWrite) WriteError(e); - foreach (var j in _allJobsToSuspend) WriteObject(j); base.EndProcessing(); } @@ -361,7 +377,11 @@ public void Dispose() /// protected void Dispose(bool disposing) { - if (!disposing) return; + if (!disposing) + { + return; + } + foreach (var pair in _cleanUpActions) { pair.Key.SuspendJobCompleted -= pair.Value; diff --git a/src/System.Management.Automation/engine/remoting/commands/WaitJob.cs b/src/System.Management.Automation/engine/remoting/commands/WaitJob.cs index 796e97513bd..3bbc4463fe8 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 /// [Parameter] [Alias("TimeoutSec")] - [ValidateRangeAttribute(-1, Int32.MaxValue)] + [ValidateRange(-1, Int32.MaxValue)] public int Timeout { get @@ -244,7 +244,7 @@ private Job GetOneBlockedJob() { lock (_jobTrackingLock) { - return _jobsToWaitFor.Find(j => j.JobStateInfo.State == JobState.Blocked); + return _jobsToWaitFor.Find(static j => j.JobStateInfo.State == JobState.Blocked); } } diff --git a/src/System.Management.Automation/engine/remoting/commands/getrunspacecommand.cs b/src/System.Management.Automation/engine/remoting/commands/getrunspacecommand.cs index d1fc8ef531a..fb6bf348aba 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; @@ -69,7 +70,7 @@ public class GetPSSessionCommand : PSRunspaceCmdlet, IDisposable /// /// This parameters specifies the appname which identifies the connection /// end point on the remote machine. If this parameter is not specified - /// then the value specified in DEFAULTREMOTEAPPNAME will be used. If thats + /// then the value specified in DEFAULTREMOTEAPPNAME will be used. If that's /// not specified as well, then "WSMAN" will be used. /// [Parameter(ValueFromPipelineByPropertyName = true, @@ -161,7 +162,7 @@ public SwitchParameter AllowRedirection [Parameter(ParameterSetName = GetPSSessionCommand.ContainerIdParameterSet)] [Parameter(ParameterSetName = GetPSSessionCommand.VMIdParameterSet)] [Parameter(ParameterSetName = GetPSSessionCommand.VMNameParameterSet)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public override string[] Name { get { return base.Name; } @@ -202,7 +203,7 @@ public override Guid[] InstanceId [Parameter(ParameterSetName = GetPSSessionCommand.ComputerInstanceIdParameterSet)] [Parameter(ParameterSetName = GetPSSessionCommand.ConnectionUriParameterSet)] [Parameter(ParameterSetName = GetPSSessionCommand.ConnectionUriInstanceIdParameterSet)] - [Credential()] + [Credential] public PSCredential Credential { get @@ -341,12 +342,23 @@ public string CertificateThumbprint /// protected override void BeginProcessing() { - base.BeginProcessing(); - - if (ConfigurationName == null) +#if UNIX + if (ComputerName?.Length > 0) { - ConfigurationName = string.Empty; + 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 625bb86fd64..8986def5a6d 100644 --- a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs @@ -88,7 +88,7 @@ public class NewPSSessionCommand : PSRemotingBaseCmdlet, IDisposable [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRemotingBaseCmdlet.VMNameParameterSet)] - [Credential()] + [Credential] public override PSCredential Credential { get @@ -129,7 +129,7 @@ public override PSSession[] Session /// /// Friendly names for the new PSSessions. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] Name { get; set; } @@ -385,11 +385,7 @@ public void Dispose() /// /// Adds forwarded events to the local queue. /// - private void OnRunspacePSEventReceived(object sender, PSEventArgs e) - { - if (this.Events != null) - this.Events.AddForwardedEvent(e); - } + private void OnRunspacePSEventReceived(object sender, PSEventArgs e) => this.Events?.AddForwardedEvent(e); /// /// When the client remote session reports a URI redirection, this method will report the @@ -524,10 +520,7 @@ private void HandleRunspaceStateChanged(object sender, OperationStateEventArgs s } } - if (reason == null) - { - reason = new RuntimeException(this.GetMessage(RemotingErrorIdStrings.RemoteRunspaceOpenUnknownState, state)); - } + reason ??= new RuntimeException(this.GetMessage(RemotingErrorIdStrings.RemoteRunspaceOpenUnknownState, state)); string fullyQualifiedErrorId = WSManTransportManagerUtils.GetFQEIDFromTransportError( transErrorCode, @@ -647,11 +640,11 @@ private List CreateRunspacesWhenRunspaceParameterSpecified() if (remoteRunspace.ConnectionInfo is VMConnectionInfo) { - newConnectionInfo = remoteRunspace.ConnectionInfo.InternalCopy(); + newConnectionInfo = remoteRunspace.ConnectionInfo.Clone(); } else if (remoteRunspace.ConnectionInfo is ContainerConnectionInfo) { - ContainerConnectionInfo newContainerConnectionInfo = remoteRunspace.ConnectionInfo.InternalCopy() as ContainerConnectionInfo; + ContainerConnectionInfo newContainerConnectionInfo = remoteRunspace.ConnectionInfo.Clone() as ContainerConnectionInfo; newContainerConnectionInfo.CreateContainerProcess(); newConnectionInfo = newContainerConnectionInfo; } @@ -939,7 +932,7 @@ private List CreateRunspacesWhenVMParameterSpecified() // // VM should be in running state. // - if ((VMState)results[0].Properties["State"].Value != VMState.Running) + if (GetVMStateProperty(results[0]) != VMState.Running) { WriteError( new ErrorRecord( @@ -1092,7 +1085,9 @@ private List CreateRunspacesForSSHHostParameterSet() host, this.KeyFilePath, port, - Subsystem); + Subsystem, + ConnectingTimeout, + Options); var typeTable = TypeTable.LoadDefaultTypeFiles(); string rsName = GetRunspaceName(index, out int rsIdUnused); index++; @@ -1118,7 +1113,9 @@ private List CreateRunspacesForSSHHostHashParameterSet() sshConnection.ComputerName, sshConnection.KeyFilePath, sshConnection.Port, - sshConnection.Subsystem); + sshConnection.Subsystem, + sshConnection.ConnectingTimeout, + sshConnection.Options); var typeTable = TypeTable.LoadDefaultTypeFiles(); string rsName = GetRunspaceName(index, out int rsIdUnused); index++; @@ -1388,7 +1385,7 @@ internal override event EventHandler OperationComplete /// /// There are two problems that need to be handled. /// 1) We need to make sure that the ThrottleManager StartComplete and StopComplete - /// operation events are called or the ThrottleManager will never end (will stop reponding). + /// operation events are called or the ThrottleManager will never end (will stop responding). /// 2) The HandleRunspaceStateChanged event handler remains in the Runspace /// StateChanged event call chain until this object is disposed. We have to /// disallow the HandleRunspaceStateChanged event from running and throwing diff --git a/src/System.Management.Automation/engine/remoting/commands/remotingcommandutil.cs b/src/System.Management.Automation/engine/remoting/commands/remotingcommandutil.cs index 094dcedc6ad..3e2603a36fe 100644 --- a/src/System.Management.Automation/engine/remoting/commands/remotingcommandutil.cs +++ b/src/System.Management.Automation/engine/remoting/commands/remotingcommandutil.cs @@ -93,7 +93,7 @@ internal static void CheckRemotingCmdletPrerequisites() try { - // the following registry key defines WSMan compatability + // the following registry key defines WSMan compatibility // HKLM\Software\Microsoft\Windows\CurrentVersion\WSMAN\ServiceStackVersion string wsManStackValue = null; RegistryKey wsManKey = Registry.LocalMachine.OpenSubKey(WSManKeyPath); @@ -165,60 +165,5 @@ internal static void CheckHostRemotingPrerequisites() throw new InvalidOperationException(errorRecord.ToString()); } } - - internal static void CheckPSVersion(Version version) - { - // PSVersion value can only be 2.0, 3.0, 4.0, 5.0, or 5.1 - if (version != null) - { - // PSVersion value can only be 2.0, 3.0, 4.0, 5.0, or 5.1 - if (!(version.Major >= 2 && version.Major <= 4 && version.Minor == 0) && - !(version.Major == 5 && version.Minor <= 1)) - { - throw new ArgumentException( - StringUtil.Format(RemotingErrorIdStrings.PSVersionParameterOutOfRange, version, "PSVersion")); - } - } - } - - /// - /// Checks if the specified version of PowerShell is installed. - /// - /// - internal static void CheckIfPowerShellVersionIsInstalled(Version version) - { - // Check if PowerShell 2.0 is installed - if (version != null && version.Major == 2) - { -#if CORECLR - // PowerShell 2.0 is not available for CoreCLR - throw new ArgumentException( - PSRemotingErrorInvariants.FormatResourceString( - RemotingErrorIdStrings.PowerShellNotInstalled, - version, "PSVersion")); -#else - // Because of app-compat issues, in Win8, we will have PS 2.0 installed by default but not .NET 2.0 - // In such a case, it is not enough if we check just PowerShell registry keys. We also need to check if .NET 2.0 is installed. - try - { - RegistryKey engineKey = PSSnapInReader.GetPSEngineKey(PSVersionInfo.RegistryVersion1Key); - // Also check for .NET 2.0 installation - if (!PsUtils.FrameworkRegistryInstallation.IsFrameworkInstalled(2, 0, 0)) - { - throw new ArgumentException( - PSRemotingErrorInvariants.FormatResourceString( - RemotingErrorIdStrings.NetFrameWorkV2NotInstalled)); - } - } - catch (PSArgumentException) - { - throw new ArgumentException( - PSRemotingErrorInvariants.FormatResourceString( - RemotingErrorIdStrings.PowerShellNotInstalled, - version, "PSVersion")); - } -#endif - } - } } } diff --git a/src/System.Management.Automation/engine/remoting/common/PSETWTracer.cs b/src/System.Management.Automation/engine/remoting/common/PSETWTracer.cs index 5eb9c5fc338..2fd2dc0a913 100644 --- a/src/System.Management.Automation/engine/remoting/common/PSETWTracer.cs +++ b/src/System.Management.Automation/engine/remoting/common/PSETWTracer.cs @@ -158,6 +158,9 @@ internal enum PSEventId : int Provider_Lifecycle = 0x1F03, Settings = 0x1F04, Engine_Trace = 0x1F06, + Amsi_Init = 0x4001, + WDAC_Query = 0x4002, + WDAC_Audit = 0x4003, // Experimental Features ExperimentalFeature_InvalidName = 0x3001, @@ -239,7 +242,10 @@ internal enum PSTask : int ExperimentalFeature = 0x6B, ScheduledJob = 0x6E, NamedPipe = 0x6F, - ISEOperation = 0x78 + ISEOperation = 0x78, + Amsi = 0X82, + WDAC = 0x83, + WDACAudit = 0x84 } /// diff --git a/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs b/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs index 42d04a9e23e..94ce5af0208 100644 --- a/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs +++ b/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs @@ -12,7 +12,7 @@ namespace System.Management.Automation.Remoting { - [SerializableAttribute] + [Serializable] internal class HyperVSocketEndPoint : EndPoint { #region Members @@ -53,7 +53,7 @@ public Guid ServiceId { get { return _serviceId; } - set { _vmId = value; } + set { _serviceId = value; } } #endregion @@ -295,7 +295,10 @@ public void Dispose() { lock (_syncObject) { - if (IsDisposed) { return; } + if (IsDisposed) + { + return; + } IsDisposed = true; } @@ -445,7 +448,10 @@ public void Dispose() { lock (_syncObject) { - if (IsDisposed) { return; } + if (IsDisposed) + { + return; + } IsDisposed = true; } @@ -551,13 +557,13 @@ public bool Connect( // if (emptyPassword) { - HyperVSocket.Send(Encoding.ASCII.GetBytes("EMPTYPW")); + HyperVSocket.Send("EMPTYPW"u8); HyperVSocket.Receive(response); responseString = Encoding.ASCII.GetString(response); } else { - HyperVSocket.Send(Encoding.ASCII.GetBytes("NONEMPTYPW")); + HyperVSocket.Send("NONEMPTYPW"u8); HyperVSocket.Receive(response); HyperVSocket.Send(password); @@ -590,11 +596,11 @@ public bool Connect( { if (emptyConfiguration) { - HyperVSocket.Send(Encoding.ASCII.GetBytes("EMPTYCF")); + HyperVSocket.Send("EMPTYCF"u8); } else { - HyperVSocket.Send(Encoding.ASCII.GetBytes("NONEMPTYCF")); + HyperVSocket.Send("NONEMPTYCF"u8); HyperVSocket.Receive(response); byte[] configName = Encoding.Unicode.GetBytes(configurationName); diff --git a/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs b/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs index 7593bbcc3b0..e80c51fb816 100644 --- a/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs +++ b/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs @@ -110,7 +110,7 @@ internal static string CreateProcessPipeName( // There is a limit of 104 characters in total including the temp path to the named pipe file // on non-Windows systems, so we'll convert the starttime to hex and just take the first 8 characters. #if UNIX - .Append(proc.StartTime.ToFileTime().ToString("X8").Substring(1,8)) + .Append(proc.StartTime.ToFileTime().ToString("X8").AsSpan(1, 8)) #else .Append(proc.StartTime.ToFileTime().ToString(CultureInfo.InvariantCulture)) #endif @@ -190,26 +190,6 @@ internal static class NamedPipeNative internal const uint ERROR_IO_INCOMPLETE = 996; internal const uint ERROR_IO_PENDING = 997; - // File function constants - internal const uint GENERIC_READ = 0x80000000; - internal const uint GENERIC_WRITE = 0x40000000; - internal const uint GENERIC_EXECUTE = 0x20000000; - internal const uint GENERIC_ALL = 0x10000000; - - internal const uint CREATE_NEW = 1; - internal const uint CREATE_ALWAYS = 2; - internal const uint OPEN_EXISTING = 3; - internal const uint OPEN_ALWAYS = 4; - internal const uint TRUNCATE_EXISTING = 5; - - internal const uint SECURITY_IMPERSONATIONLEVEL_ANONYMOUS = 0; - internal const uint SECURITY_IMPERSONATIONLEVEL_IDENTIFICATION = 1; - internal const uint SECURITY_IMPERSONATIONLEVEL_IMPERSONATION = 2; - internal const uint SECURITY_IMPERSONATIONLEVEL_DELEGATION = 3; - - // Infinite timeout - internal const uint INFINITE = 0xFFFFFFFF; - #endregion #region Data structures @@ -265,28 +245,6 @@ internal static SECURITY_ATTRIBUTES GetSecurityAttributes(GCHandle securityDescr return securityAttributes; } - [DllImport(PinvokeDllNames.CreateFileDllName, SetLastError = true, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)] - internal static extern SafePipeHandle CreateFile( - string lpFileName, - uint dwDesiredAccess, - uint dwShareMode, - IntPtr SecurityAttributes, - uint dwCreationDisposition, - uint dwFlagsAndAttributes, - IntPtr hTemplateFile); - - [DllImport(PinvokeDllNames.WaitNamedPipeDllName, SetLastError = true, CharSet = CharSet.Unicode)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool WaitNamedPipe(string lpNamedPipeName, uint nTimeOut); - - [DllImport(PinvokeDllNames.ImpersonateNamedPipeClientDllName, SetLastError = true, CharSet = CharSet.Unicode)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool ImpersonateNamedPipeClient(IntPtr hNamedPipe); - - [DllImport(PinvokeDllNames.RevertToSelfDllName, SetLastError = true, CharSet = CharSet.Unicode)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool RevertToSelf(); - #endregion } @@ -504,10 +462,7 @@ private static NamedPipeServerStream CreateNamedPipe( securityAttributes); int lastError = Marshal.GetLastWin32Error(); - if (securityDescHandle != null) - { - securityDescHandle.Value.Free(); - } + securityDescHandle?.Free(); if (pipeHandle.IsInvalid) { @@ -1009,8 +964,6 @@ internal class NamedPipeClientBase : IDisposable private NamedPipeClientStream _clientPipeStream; private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource(); - protected string _pipeName; - #endregion #region Properties @@ -1030,25 +983,30 @@ internal class NamedPipeClientBase : IDisposable /// public string PipeName { - get { return _pipeName; } + get; + internal set; } #endregion - #region Constructor - - public NamedPipeClientBase() - { } - - #endregion - #region IDisposable /// - /// Dispose. + /// Dispose object. /// public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!disposing) + { + return; + } + if (TextReader != null) { try { TextReader.Dispose(); } @@ -1093,23 +1051,23 @@ public void Connect( TextWriter.AutoFlush = true; _tracer.WriteMessage("NamedPipeClientBase", "Connect", Guid.Empty, - "Connection started on pipe: {0}", _pipeName); + "Connection started on pipe: {0}", PipeName); } /// /// Closes the named pipe. /// - public void Close() - { - if (_clientPipeStream != null) - { - _clientPipeStream.Dispose(); - } - } + public void Close() => _clientPipeStream?.Dispose(); + /// + /// Abort connection attempt. + /// public virtual void AbortConnect() { } + /// + /// Begin connection attempt. + /// protected virtual NamedPipeClientStream DoConnect(int timeout) { return null; @@ -1166,7 +1124,7 @@ internal RemoteSessionNamedPipeClient( throw new PSArgumentNullException(nameof(pipeName)); } - _pipeName = pipeName; + PipeName = pipeName; // Defer creating the .Net NamedPipeClientStream object until we connect. // _clientPipeStream == null. @@ -1189,7 +1147,7 @@ internal RemoteSessionNamedPipeClient( if (coreName == null) { throw new PSArgumentNullException(nameof(coreName)); } - _pipeName = @"\\" + serverName + @"\" + namespaceName + @"\" + coreName; + PipeName = @"\\" + serverName + @"\" + namespaceName + @"\" + coreName; // Defer creating the .Net NamedPipeClientStream object until we connect. // _clientPipeStream == null. @@ -1211,6 +1169,9 @@ public override void AbortConnect() #region Protected Methods + /// + /// Begin connection attempt. + /// protected override NamedPipeClientStream DoConnect(int timeout) { // Repeatedly attempt connection to pipe until timeout expires. @@ -1220,11 +1181,11 @@ protected override NamedPipeClientStream DoConnect(int timeout) NamedPipeClientStream namedPipeClientStream = new NamedPipeClientStream( serverName: ".", - pipeName: _pipeName, + pipeName: PipeName, direction: PipeDirection.InOut, options: PipeOptions.Asynchronous); - namedPipeClientStream.Connect(); + namedPipeClientStream.ConnectAsync(timeout); do { @@ -1275,7 +1236,7 @@ public ContainerSessionNamedPipeClient( // // Named pipe inside Windows Server container is under different name space. // - _pipeName = containerObRoot + @"\Device\NamedPipe\" + + PipeName = containerObRoot + @"\Device\NamedPipe\" + NamedPipeUtils.CreateProcessPipeName(procId, appDomainName); } @@ -1289,30 +1250,34 @@ public ContainerSessionNamedPipeClient( /// protected override NamedPipeClientStream DoConnect(int timeout) { +#if UNIX + // TODO: `CreateFileWithSafePipeHandle` pinvoke below clearly says + // that the code is only for Windows and we could exclude + // a lot of code from compilation on Unix. + throw new NotSupportedException(nameof(DoConnect)); +#else // // WaitNamedPipe API is not supported by Windows Server container now, so we need to repeatedly // attempt connection to pipe server until timeout expires. // int startTime = Environment.TickCount; int elapsedTime = 0; - SafePipeHandle pipeHandle = null; + nint handle; do { // Get handle to pipe. - pipeHandle = NamedPipeNative.CreateFile( - lpFileName: _pipeName, - dwDesiredAccess: NamedPipeNative.GENERIC_READ | NamedPipeNative.GENERIC_WRITE, - dwShareMode: 0, - SecurityAttributes: IntPtr.Zero, - dwCreationDisposition: NamedPipeNative.OPEN_EXISTING, - dwFlagsAndAttributes: NamedPipeNative.FILE_FLAG_OVERLAPPED, - hTemplateFile: IntPtr.Zero); - - int lastError = Marshal.GetLastWin32Error(); - if (pipeHandle.IsInvalid) + handle = Interop.Windows.CreateFileWithPipeHandle( + lpFileName: PipeName, + FileAccess.ReadWrite, + FileShare.None, + FileMode.Open, + Interop.Windows.FileAttributes.Overlapped); + + if (handle == nint.Zero || handle == (nint)(-1)) { - if (lastError == NamedPipeNative.ERROR_FILE_NOT_FOUND) + int lastError = Marshal.GetLastPInvokeError(); + if (lastError == Interop.Windows.ERROR_FILE_NOT_FOUND) { elapsedTime = unchecked(Environment.TickCount - startTime); Thread.Sleep(100); @@ -1330,19 +1295,22 @@ protected override NamedPipeClientStream DoConnect(int timeout) } } while (elapsedTime < timeout); + SafePipeHandle pipeHandle = null; try { + pipeHandle = new SafePipeHandle(handle, ownsHandle: true); return new NamedPipeClientStream( PipeDirection.InOut, - true, - true, + isAsync: true, + isConnected: true, pipeHandle); } catch (Exception) { - pipeHandle.Dispose(); + pipeHandle?.Dispose(); throw; } +#endif } #endregion diff --git a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs index ea8a9c1a9d0..9c221f01dbb 100644 --- a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs +++ b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Collections; using System.Collections.Generic; using System.ComponentModel; // Win32Exception using System.Diagnostics; @@ -17,6 +18,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Security.AccessControl; +using System.Text; using System.Threading; using Microsoft.Win32.SafeHandles; @@ -166,10 +168,7 @@ public CultureInfo Culture set { - if (value == null) - { - throw new ArgumentNullException("value"); - } + ArgumentNullException.ThrowIfNull(value); _culture = value; } @@ -189,10 +188,7 @@ public CultureInfo UICulture set { - if (value == null) - { - throw new ArgumentNullException("value"); - } + ArgumentNullException.ThrowIfNull(value); _uiCulture = value; } @@ -282,10 +278,7 @@ public int OpenTimeout /// public virtual void SetSessionOptions(PSSessionOption options) { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } + ArgumentNullException.ThrowIfNull(options); if (options.Culture != null) { @@ -325,13 +318,33 @@ internal int TimeSpanToTimeOutMs(TimeSpan t) } } + /// + /// Validates port number is in range. + /// + /// Port number to validate. + internal virtual void ValidatePortInRange(int port) + { + if ((port < MinPort || port > MaxPort)) + { + string message = + PSRemotingErrorInvariants.FormatResourceString( + RemotingErrorIdStrings.PortIsOutOfRange, port); + ArgumentException e = new ArgumentException(message); + throw e; + } + } + + #endregion + + #region Public methods + /// /// Creates the appropriate client session transportmanager. /// /// Runspace/Pool instance Id. /// Session name. /// PSRemotingCryptoHelper. - internal virtual BaseClientSessionTransportManager CreateClientSessionTransportManager( + public virtual BaseClientSessionTransportManager CreateClientSessionTransportManager( Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) @@ -343,27 +356,11 @@ internal virtual BaseClientSessionTransportManager CreateClientSessionTransportM /// Create a copy of the connection info object. /// /// Copy of the connection info object. - internal virtual RunspaceConnectionInfo InternalCopy() + public virtual RunspaceConnectionInfo Clone() { throw new PSNotImplementedException(); } - /// - /// Validates port number is in range. - /// - /// Port number to validate. - internal virtual void ValidatePortInRange(int port) - { - if ((port < MinPort || port > MaxPort)) - { - string message = - PSRemotingErrorInvariants.FormatResourceString( - RemotingErrorIdStrings.PortIsOutOfRange, port); - ArgumentException e = new ArgumentException(message); - throw e; - } - } - #endregion #region Constants @@ -1023,10 +1020,7 @@ public WSManConnectionInfo(Uri uri) /// public override void SetSessionOptions(PSSessionOption options) { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } + ArgumentNullException.ThrowIfNull(options); if ((options.ProxyAccessType == ProxyAccessType.None) && (options.ProxyCredential != null)) { @@ -1062,10 +1056,10 @@ public override void SetSessionOptions(PSSessionOption options) } /// - /// Shallow copy of the current instance. + /// Create a copy of the connection info object. /// - /// RunspaceConnectionInfo. - internal override RunspaceConnectionInfo InternalCopy() + /// Copy of the connection info object. + public override RunspaceConnectionInfo Clone() { return Copy(); } @@ -1133,7 +1127,14 @@ public WSManConnectionInfo Copy() #region Internal Methods - internal override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) + /// + /// Creates the appropriate client session transportmanager. + /// + /// Runspace/Pool instance Id. + /// Session name. + /// PSRemotingCryptoHelper instance. + /// Instance of WSManClientSessionTransportManager + public override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) { return new WSManClientSessionTransportManager( instanceId, @@ -1374,7 +1375,7 @@ private void UpdateUri(Uri uri) private string _appName = s_defaultAppName; private Uri _connectionUri = new Uri(LocalHostUriString); // uri of this connection private PSCredential _credential; // credentials to be used for this connection - private string _shellUri = DefaultShellUri; // shell thats specified by the user + private string _shellUri = DefaultShellUri; // shell that's specified by the user private string _thumbPrint; private AuthenticationMechanism _proxyAuthentication; private PSCredential _proxyCredential; @@ -1653,12 +1654,23 @@ public NewProcessConnectionInfo Copy() return result; } - internal override RunspaceConnectionInfo InternalCopy() + /// + /// Create a copy of the connection info object. + /// + /// Copy of the connection info object. + public override RunspaceConnectionInfo Clone() { return Copy(); } - internal override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) + /// + /// Creates the appropriate client session transportmanager. + /// + /// Runspace/Pool instance Id. + /// Session name. + /// PSRemotingCryptoHelper object. + /// Instance of OutOfProcessClientSessionTransportManager + public override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) { return new OutOfProcessClientSessionTransportManager( instanceId, @@ -1873,10 +1885,10 @@ public override string CertificateThumbprint } /// - /// Shallow copy of current instance. + /// Create a copy of the connection info object. /// - /// NamedPipeConnectionInfo. - internal override RunspaceConnectionInfo InternalCopy() + /// Copy of the connection info object. + public override RunspaceConnectionInfo Clone() { NamedPipeConnectionInfo newCopy = new NamedPipeConnectionInfo(); newCopy._authMechanism = this.AuthenticationMechanism; @@ -1889,7 +1901,14 @@ internal override RunspaceConnectionInfo InternalCopy() return newCopy; } - internal override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) + /// + /// Creates the appropriate client session transportmanager. + /// + /// Runspace/Pool instance Id. + /// Session name. + /// PSRemotingCryptoHelper object. + /// Instance of NamedPipeClientSessionTransportManager + public override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) { return new NamedPipeClientSessionTransportManager( this, @@ -1907,6 +1926,20 @@ internal override BaseClientSessionTransportManager CreateClientSessionTransport /// public sealed class SSHConnectionInfo : RunspaceConnectionInfo { + #region Constants + + /// + /// Default value for subsystem. + /// + private const string DefaultSubsystem = "powershell"; + + /// + /// Default value is infinite timeout. + /// + private const int DefaultConnectingTimeoutTime = Timeout.Infinite; + + #endregion + #region Properties /// @@ -1921,7 +1954,7 @@ public string UserName /// /// Key File Path. /// - private string KeyFilePath + public string KeyFilePath { get; set; @@ -1930,7 +1963,7 @@ private string KeyFilePath /// /// Port for connection. /// - private int Port + public int Port { get; set; @@ -1939,7 +1972,27 @@ private int Port /// /// Subsystem to use. /// - private string Subsystem + public string Subsystem + { + get; + set; + } + + /// + /// Gets or sets a time in milliseconds after which a connection attempt is terminated. + /// Default value (-1) never times out and a connection attempt waits indefinitely. + /// + public int ConnectingTimeout + { + get; + set; + } + + /// + /// The SSH options to pass to OpenSSH. + /// Gets or sets the SSH options to pass to OpenSSH. + /// + private Hashtable Options { get; set; @@ -1950,13 +2003,13 @@ private string Subsystem #region Constructors /// - /// Constructor. + /// Initializes a new instance of the class. /// private SSHConnectionInfo() { } /// - /// Constructor. + /// Initializes a new instance of the class. /// /// User Name. /// Computer Name. @@ -1966,17 +2019,21 @@ public SSHConnectionInfo( string computerName, string keyFilePath) { - if (computerName == null) { throw new PSArgumentNullException(nameof(computerName)); } + if (computerName == null) + { + throw new PSArgumentNullException(nameof(computerName)); + } - this.UserName = userName; - this.ComputerName = computerName; - this.KeyFilePath = keyFilePath; - this.Port = 0; - this.Subsystem = DefaultSubsystem; + UserName = userName; + ComputerName = computerName; + KeyFilePath = keyFilePath; + Port = 0; + Subsystem = DefaultSubsystem; + ConnectingTimeout = DefaultConnectingTimeoutTime; } /// - /// Constructor. + /// Initializes a new instance of the class. /// /// User Name. /// Computer Name. @@ -1989,12 +2046,11 @@ public SSHConnectionInfo( int port) : this(userName, computerName, keyFilePath) { ValidatePortInRange(port); - - this.Port = port; + Port = port; } /// - /// Constructor. + /// Initializes a new instance of the class. /// /// User Name. /// Computer Name. @@ -2006,12 +2062,51 @@ public SSHConnectionInfo( string computerName, string keyFilePath, int port, - string subsystem) : this(userName, computerName, keyFilePath) + string subsystem) : this(userName, computerName, keyFilePath, port) { - ValidatePortInRange(port); + Subsystem = string.IsNullOrEmpty(subsystem) ? DefaultSubsystem : subsystem; + } - this.Port = port; - this.Subsystem = (string.IsNullOrEmpty(subsystem)) ? DefaultSubsystem : subsystem; + /// + /// Initializes a new instance of SSHConnectionInfo. + /// + /// Name of user. + /// Name of computer. + /// Path of key file. + /// Port number for connection (default 22). + /// Subsystem to use (default 'powershell'). + /// Timeout time for terminating connection attempt. + public SSHConnectionInfo( + string userName, + string computerName, + string keyFilePath, + int port, + string subsystem, + int connectingTimeout) : this(userName, computerName, keyFilePath, port, subsystem) + { + ConnectingTimeout = connectingTimeout; + } + + /// + /// Initializes a new instance of the class. + /// + /// User Name. + /// Computer Name. + /// Key File Path. + /// Port number for connection (default 22). + /// Subsystem to use (default 'powershell'). + /// Timeout time for terminating connection attempt. + /// Options for the SSH connection. + public SSHConnectionInfo( + string userName, + string computerName, + string keyFilePath, + int port, + string subsystem, + int connectingTimeout, + Hashtable options) : this(userName, computerName, keyFilePath, port, subsystem, connectingTimeout) + { + Options = options; } #endregion @@ -2058,29 +2153,30 @@ public override string CertificateThumbprint } /// - /// Shallow copy of current instance. + /// Create a copy of the connection info object. /// - /// NamedPipeConnectionInfo. - internal override RunspaceConnectionInfo InternalCopy() + /// Copy of the connection info object. + public override RunspaceConnectionInfo Clone() { SSHConnectionInfo newCopy = new SSHConnectionInfo(); - newCopy.ComputerName = this.ComputerName; - newCopy.UserName = this.UserName; - newCopy.KeyFilePath = this.KeyFilePath; - newCopy.Port = this.Port; - newCopy.Subsystem = this.Subsystem; + newCopy.ComputerName = ComputerName; + newCopy.UserName = UserName; + newCopy.KeyFilePath = KeyFilePath; + newCopy.Port = Port; + newCopy.Subsystem = Subsystem; + newCopy.ConnectingTimeout = ConnectingTimeout; + newCopy.Options = Options; return newCopy; } /// - /// CreateClientSessionTransportManager. + /// Creates the appropriate client session transportmanager. /// - /// - /// - /// - /// - internal override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) + /// Runspace/Pool instance Id. + /// Session name. + /// PSRemotingCryptoHelper. + public override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) { return new SSHClientSessionTransportManager( this, @@ -2121,9 +2217,9 @@ internal int StartSSHProcess( // // Local ssh invoked as: // windows: - // ssh.exe [-i identity_file] [-l login_name] [-p port] -s + // ssh.exe [-i identity_file] [-l login_name] [-p port] [-o option] -s // linux|macos: - // ssh [-i identity_file] [-l login_name] [-p port] -s + // ssh [-i identity_file] [-l login_name] [-p port] [-o option] -s // where is interpreted as the subsystem due to the -s flag. // // Remote sshd configured for PowerShell Remoting Protocol (PSRP) over Secure Shell Protocol (SSH) @@ -2146,24 +2242,24 @@ internal int StartSSHProcess( StringUtil.Format(RemotingErrorIdStrings.KeyFileNotFound, this.KeyFilePath)); } - startInfo.ArgumentList.Add(string.Format(CultureInfo.InvariantCulture, @"-i ""{0}""", this.KeyFilePath)); + startInfo.ArgumentList.Add(string.Create(CultureInfo.InvariantCulture, $@"-i ""{this.KeyFilePath}""")); } - // pass "-l login_name" commmand line argument to ssh if UserName is set + // pass "-l login_name" command line argument to ssh if UserName is set // if UserName is not set, then ssh will use User from ssh_config if defined else the environment user by default if (!string.IsNullOrEmpty(this.UserName)) { - var parts = this.UserName.Split(Utils.Separators.Backslash); + var parts = this.UserName.Split('\\'); if (parts.Length == 2) { // convert DOMAIN\user to user@DOMAIN var domainName = parts[0]; var userName = parts[1]; - startInfo.ArgumentList.Add(string.Format(CultureInfo.InvariantCulture, @"-l {0}@{1}", userName, domainName)); + startInfo.ArgumentList.Add(string.Create(CultureInfo.InvariantCulture, $@"-l {userName}@{domainName}")); } else { - startInfo.ArgumentList.Add(string.Format(CultureInfo.InvariantCulture, @"-l {0}", this.UserName)); + startInfo.ArgumentList.Add(string.Create(CultureInfo.InvariantCulture, $@"-l {this.UserName}")); } } @@ -2171,12 +2267,21 @@ internal int StartSSHProcess( // if Port is not set, then ssh will use Port from ssh_config if defined else 22 by default if (this.Port != 0) { - startInfo.ArgumentList.Add(string.Format(CultureInfo.InvariantCulture, @"-p {0}", this.Port)); + startInfo.ArgumentList.Add(string.Create(CultureInfo.InvariantCulture, $@"-p {this.Port}")); + } + + // pass "-o option=value" command line argument to ssh if options are provided + if (this.Options != null) + { + foreach (DictionaryEntry pair in this.Options) + { + startInfo.ArgumentList.Add(string.Create(CultureInfo.InvariantCulture, $@"-o {pair.Key}={pair.Value}")); + } } // pass "-s destination command" command line arguments to ssh where command is the subsystem to invoke on the destination // note that ssh expects IPv6 addresses to not be enclosed in square brackets so trim them if present - startInfo.ArgumentList.Add(string.Format(CultureInfo.InvariantCulture, @"-s {0} {1}", this.ComputerName.TrimStart('[').TrimEnd(']'), this.Subsystem)); + startInfo.ArgumentList.Add(string.Create(CultureInfo.InvariantCulture, $@"-s {this.ComputerName.TrimStart('[').TrimEnd(']')} {this.Subsystem}")); startInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(filePath); startInfo.CreateNoWindow = true; @@ -2185,14 +2290,14 @@ internal int StartSSHProcess( return StartSSHProcessImpl(startInfo, out stdInWriterVar, out stdOutReaderVar, out stdErrReaderVar); } - #endregion - - #region Constants - /// - /// Default value for subsystem. + /// Terminates the SSH process by process Id. /// - private const string DefaultSubsystem = "powershell"; + /// Process id. + internal void KillSSHProcess(int pid) + { + KillSSHProcessImpl(pid); + } #endregion @@ -2232,6 +2337,16 @@ private static int StartSSHProcessImpl( return pid; } + private static void KillSSHProcessImpl(int pid) + { + // killing a zombie might or might not return ESRCH, so we ignore kill's return value + Platform.NonWindowsKillProcess(pid); + + // block while waiting for process to die + // shouldn't take long after SIGKILL + Platform.NonWindowsWaitPid(pid, false); + } + #region UNIX Create Process // @@ -2282,23 +2397,31 @@ internal static int StartSSHProcess( if (startInfo.RedirectStandardInput) { Debug.Assert(stdinFd >= 0, "Invalid Fd"); - standardInput = new StreamWriter(OpenStream(stdinFd, FileAccess.Write), - Utils.utf8NoBom, StreamBufferSize) + standardInput = new StreamWriter( + OpenStream(stdinFd, FileAccess.Write), + Encoding.Default, + StreamBufferSize) { AutoFlush = true }; } if (startInfo.RedirectStandardOutput) { Debug.Assert(stdoutFd >= 0, "Invalid Fd"); - standardOutput = new StreamReader(OpenStream(stdoutFd, FileAccess.Read), - startInfo.StandardOutputEncoding ?? Utils.utf8NoBom, true, StreamBufferSize); + standardOutput = new StreamReader( + OpenStream(stdoutFd, FileAccess.Read), + startInfo.StandardOutputEncoding ?? Encoding.Default, + detectEncodingFromByteOrderMarks: true, + StreamBufferSize); } if (startInfo.RedirectStandardError) { Debug.Assert(stderrFd >= 0, "Invalid Fd"); - standardError = new StreamReader(OpenStream(stderrFd, FileAccess.Read), - startInfo.StandardErrorEncoding ?? Utils.utf8NoBom, true, StreamBufferSize); + standardError = new StreamReader( + OpenStream(stderrFd, FileAccess.Read), + startInfo.StandardErrorEncoding ?? Encoding.Default, + detectEncodingFromByteOrderMarks: true, + StreamBufferSize); } return childPid; @@ -2339,7 +2462,7 @@ private static string[] ParseArgv(ProcessStartInfo psi) var argvList = new List(); 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,6 +2660,17 @@ private static int StartSSHProcessImpl( return sshProcess.Id; } + private static void KillSSHProcessImpl(int pid) + { + using (var sshProcess = System.Diagnostics.Process.GetProcessById(pid)) + { + if ((sshProcess != null) && (sshProcess.Handle != IntPtr.Zero) && !sshProcess.HasExited) + { + sshProcess.Kill(); + } + } + } + // Process creation flags private const int CREATE_NEW_PROCESS_GROUP = 0x00000200; private const int CREATE_SUSPENDED = 0x00000004; @@ -2556,9 +2690,9 @@ private static Process CreateProcessWithRedirectedStd( stdInPipeServer = null; stdOutPipeServer = null; stdErrPipeServer = null; - SafePipeHandle stdInPipeClient = null; - SafePipeHandle stdOutPipeClient = null; - SafePipeHandle stdErrPipeClient = null; + SafeFileHandle stdInPipeClient = null; + SafeFileHandle stdOutPipeClient = null; + SafeFileHandle stdErrPipeClient = null; string randomName = System.IO.Path.GetFileNameWithoutExtension(System.IO.Path.GetRandomFileName()); try @@ -2580,17 +2714,12 @@ private static Process CreateProcessWithRedirectedStd( } catch (Exception) { - if (stdInPipeServer != null) { stdInPipeServer.Dispose(); } - - if (stdInPipeClient != null) { stdInPipeClient.Dispose(); } - - if (stdOutPipeServer != null) { stdOutPipeServer.Dispose(); } - - if (stdOutPipeClient != null) { stdOutPipeClient.Dispose(); } - - if (stdErrPipeServer != null) { stdErrPipeServer.Dispose(); } - - if (stdErrPipeClient != null) { stdErrPipeClient.Dispose(); } + stdInPipeServer?.Dispose(); + stdInPipeClient?.Dispose(); + stdOutPipeServer?.Dispose(); + stdOutPipeClient?.Dispose(); + stdErrPipeServer?.Dispose(); + stdErrPipeClient?.Dispose(); throw; } @@ -2609,9 +2738,9 @@ private static Process CreateProcessWithRedirectedStd( startInfo.FileName, string.Join(' ', startInfo.ArgumentList)); - lpStartupInfo.hStdInput = new SafeFileHandle(stdInPipeClient.DangerousGetHandle(), false); - lpStartupInfo.hStdOutput = new SafeFileHandle(stdOutPipeClient.DangerousGetHandle(), false); - lpStartupInfo.hStdError = new SafeFileHandle(stdErrPipeClient.DangerousGetHandle(), false); + lpStartupInfo.hStdInput = stdInPipeClient; + lpStartupInfo.hStdOutput = stdOutPipeClient; + lpStartupInfo.hStdError = stdErrPipeClient; lpStartupInfo.dwFlags = 0x100; // No new window: Inherit the parent process's console window @@ -2656,17 +2785,12 @@ private static Process CreateProcessWithRedirectedStd( } catch (Exception) { - if (stdInPipeServer != null) { stdInPipeServer.Dispose(); } - - if (stdInPipeClient != null) { stdInPipeClient.Dispose(); } - - if (stdOutPipeServer != null) { stdOutPipeServer.Dispose(); } - - if (stdOutPipeClient != null) { stdOutPipeClient.Dispose(); } - - if (stdErrPipeServer != null) { stdErrPipeServer.Dispose(); } - - if (stdErrPipeClient != null) { stdErrPipeClient.Dispose(); } + stdInPipeServer?.Dispose(); + stdInPipeClient?.Dispose(); + stdOutPipeServer?.Dispose(); + stdOutPipeClient?.Dispose(); + stdErrPipeServer?.Dispose(); + stdErrPipeClient?.Dispose(); throw; } @@ -2676,25 +2800,10 @@ private static Process CreateProcessWithRedirectedStd( } } - private static SafePipeHandle GetNamedPipeHandle(string pipeName) + private static SafeFileHandle GetNamedPipeHandle(string pipeName) { - // Get handle to pipe. - var fileHandle = PlatformInvokes.CreateFileW( - lpFileName: pipeName, - dwDesiredAccess: NamedPipeNative.GENERIC_READ | NamedPipeNative.GENERIC_WRITE, - dwShareMode: 0, - lpSecurityAttributes: new PlatformInvokes.SECURITY_ATTRIBUTES(), // Create an inheritable handle. - dwCreationDisposition: NamedPipeNative.OPEN_EXISTING, - dwFlagsAndAttributes: NamedPipeNative.FILE_FLAG_OVERLAPPED, // Open in asynchronous mode. - hTemplateFile: IntPtr.Zero); - - int lastError = Marshal.GetLastWin32Error(); - if (fileHandle == PlatformInvokes.INVALID_HANDLE_VALUE) - { - throw new System.ComponentModel.Win32Exception(lastError); - } - - return new SafePipeHandle(fileHandle, true); + SafeFileHandle sf = File.OpenHandle(pipeName, FileMode.Open, FileAccess.ReadWrite, FileShare.Inheritable, FileOptions.Asynchronous); + return sf; } private static SafePipeHandle CreateNamedPipe( @@ -2724,10 +2833,7 @@ private static SafePipeHandle CreateNamedPipe( securityAttributes); int lastError = Marshal.GetLastWin32Error(); - if (securityDescHandle != null) - { - securityDescHandle.Value.Free(); - } + securityDescHandle?.Free(); if (pipeHandle.IsInvalid) { @@ -2831,13 +2937,24 @@ public override PSCredential Credential /// public override string ComputerName { get; set; } - internal override RunspaceConnectionInfo InternalCopy() + /// + /// Create a copy of the connection info object. + /// + /// Copy of the connection info object. + public override RunspaceConnectionInfo Clone() { VMConnectionInfo result = new VMConnectionInfo(Credential, VMGuid, ComputerName, ConfigurationName); return result; } - internal override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) + /// + /// Creates the appropriate client session transportmanager. + /// + /// Runspace/Pool instance Id. + /// Session name. + /// PSRemotingCryptoHelper instance. + /// Instance of VMHyperVSocketClientSessionTransportManager. + public override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) { return new VMHyperVSocketClientSessionTransportManager( this, @@ -2964,13 +3081,24 @@ public override string ComputerName set { throw new PSNotSupportedException(); } } - internal override RunspaceConnectionInfo InternalCopy() + /// + /// Create a copy of the connection info object. + /// + /// Copy of the connection info object. + public override RunspaceConnectionInfo Clone() { ContainerConnectionInfo newCopy = new ContainerConnectionInfo(ContainerProc); return newCopy; } - internal override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) + /// + /// Creates the appropriate client session transportmanager. + /// + /// Runspace/Pool instance Id. + /// Session name. + /// PSRemotingCryptoHelper object. + /// Instance of ContainerHyperVSocketClientSessionTransportManager + public override BaseClientSessionTransportManager CreateClientSessionTransportManager(Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) { if (ContainerProc.RuntimeId != Guid.Empty) { diff --git a/src/System.Management.Automation/engine/remoting/common/RunspacePoolStateInfo.cs b/src/System.Management.Automation/engine/remoting/common/RunspacePoolStateInfo.cs index e863a7c795e..fb4807351c3 100644 --- a/src/System.Management.Automation/engine/remoting/common/RunspacePoolStateInfo.cs +++ b/src/System.Management.Automation/engine/remoting/common/RunspacePoolStateInfo.cs @@ -17,7 +17,7 @@ namespace System.Management.Automation public sealed class RunspacePoolStateInfo { /// - /// State of the runspace pool when this event occured. + /// State of the runspace pool when this event occurred. /// public RunspacePoolState State { get; } 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 d99fbe21126..70693c4c3d9 100644 --- a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/EncodeAndDecode.cs +++ b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/EncodeAndDecode.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Generic; using System.Globalization; +using System.Management.Automation; using System.Management.Automation.Host; using System.Management.Automation.Internal; using System.Management.Automation.Remoting; @@ -73,7 +74,7 @@ public RemotingEncodingException(string message, Exception innerException, Error /// internal static class RemotingConstants { - internal static readonly Version HostVersion = new Version(1, 0, 0, 0); + 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); @@ -124,234 +125,6 @@ internal static class RemoteDataNameStrings // to client to let client know if the negotiation succeeded. internal const string IsNegotiationSucceeded = "IsNegotiationSucceeded"; - #region "PSv2 Tab Expansion Function" - - internal const string PSv2TabExpansionFunction = "TabExpansion"; - - /// - /// This is the PSv2 function for tab expansion. It's only for legacy purpose - used in - /// an interactive remote session from a win7 machine to a win8 machine (or later). - /// - internal const string PSv2TabExpansionFunctionText = @" - param($line, $lastWord) - & { - function Write-Members ($sep='.') - { - Invoke-Expression ('$_val=' + $_expression) - - $_method = [Management.Automation.PSMemberTypes] ` - 'Method,CodeMethod,ScriptMethod,ParameterizedProperty' - if ($sep -eq '.') - { - $params = @{view = 'extended','adapted','base'} - } - else - { - $params = @{static=$true} - } - - foreach ($_m in ,$_val | Get-Member @params $_pat | - Sort-Object membertype,name) - { - if ($_m.MemberType -band $_method) - { - # Return a method... - $_base + $_expression + $sep + $_m.name + '(' - } - else { - # Return a property... - $_base + $_expression + $sep + $_m.name - } - } - } - - # If a command name contains any of these chars, it needs to be quoted - $_charsRequiringQuotes = ('`&@''#{}()$,;|<> ' + ""`t"").ToCharArray() - - # If a variable name contains any of these characters it needs to be in braces - $_varsRequiringQuotes = ('-`&@''#{}()$,;|<> .\/' + ""`t"").ToCharArray() - - switch -regex ($lastWord) - { - # Handle property and method expansion rooted at variables... - # e.g. $a.b. - '(^.*)(\$(\w|:|\.)+)\.([*\w]*)$' { - $_base = $matches[1] - $_expression = $matches[2] - $_pat = $matches[4] + '*' - Write-Members - break; - } - - # Handle simple property and method expansion on static members... - # e.g. [datetime]::n - '(^.*)(\[(\w|\.|\+)+\])(\:\:|\.){0,1}([*\w]*)$' { - $_base = $matches[1] - $_expression = $matches[2] - $_pat = $matches[5] + '*' - Write-Members $(if (! $matches[4]) {'::'} else {$matches[4]}) - break; - } - - # Handle complex property and method expansion on static members - # where there are intermediate properties... - # e.g. [datetime]::now.d - '(^.*)(\[(\w|\.|\+)+\](\:\:|\.)(\w+\.)+)([*\w]*)$' { - $_base = $matches[1] # everything before the expression - $_expression = $matches[2].TrimEnd('.') # expression less trailing '.' - $_pat = $matches[6] + '*' # the member to look for... - Write-Members - break; - } - - # Handle variable name expansion... - '(^.*\$)([*\w:]+)$' { - $_prefix = $matches[1] - $_varName = $matches[2] - $_colonPos = $_varname.IndexOf(':') - if ($_colonPos -eq -1) - { - $_varName = 'variable:' + $_varName - $_provider = '' - } - else - { - $_provider = $_varname.Substring(0, $_colonPos+1) - } - - foreach ($_v in Get-ChildItem ($_varName + '*') | sort Name) - { - $_nameFound = $_v.name - $(if ($_nameFound.IndexOfAny($_varsRequiringQuotes) -eq -1) {'{0}{1}{2}'} - else {'{0}{{{1}{2}}}'}) -f $_prefix, $_provider, $_nameFound - } - - break; - } - - # Do completion on parameters... - '^-([*\w0-9]*)' { - $_pat = $matches[1] + '*' - - # extract the command name from the string - # first split the string into statements and pipeline elements - # This doesn't handle strings however. - $_command = [regex]::Split($line, '[|;=]')[-1] - - # Extract the trailing unclosed block e.g. ls | foreach { cp - if ($_command -match '\{([^\{\}]*)$') - { - $_command = $matches[1] - } - - # Extract the longest unclosed parenthetical expression... - if ($_command -match '\(([^()]*)$') - { - $_command = $matches[1] - } - - # take the first space separated token of the remaining string - # as the command to look up. Trim any leading or trailing spaces - # so you don't get leading empty elements. - $_command = $_command.TrimEnd('-') - $_command,$_arguments = $_command.Trim().Split() - - # now get the info object for it, -ArgumentList will force aliases to be resolved - # it also retrieves dynamic parameters - try - { - $_command = @(Get-Command -type 'Alias,Cmdlet,Function,Filter,ExternalScript' ` - -Name $_command -ArgumentList $_arguments)[0] - } - catch - { - # see if the command is an alias. If so, resolve it to the real command - if(Test-Path alias:\$_command) - { - $_command = @(Get-Command -Type Alias $_command)[0].Definition - } - - # If we were unsuccessful retrieving the command, try again without the parameters - $_command = @(Get-Command -type 'Cmdlet,Function,Filter,ExternalScript' ` - -Name $_command)[0] - } - - # remove errors generated by the command not being found, and break - if(-not $_command) { $error.RemoveAt(0); break; } - - # expand the parameter sets and emit the matching elements - # need to use psbase.Keys in case 'keys' is one of the parameters - # to the cmdlet - foreach ($_n in $_command.Parameters.psbase.Keys) - { - if ($_n -like $_pat) { '-' + $_n } - } - - break; - } - - # Tab complete against history either # or # - '^#(\w*)' { - $_pattern = $matches[1] - if ($_pattern -match '^[0-9]+$') - { - Get-History -ea SilentlyContinue -Id $_pattern | ForEach-Object { $_.CommandLine } - } - else - { - $_pattern = '*' + $_pattern + '*' - Get-History -Count 32767 | Sort-Object -Descending Id| ForEach-Object { $_.CommandLine } | where { $_ -like $_pattern } - } - - break; - } - - # try to find a matching command... - default { - # parse the script... - $_tokens = [System.Management.Automation.PSParser]::Tokenize($line, - [ref] $null) - - if ($_tokens) - { - $_lastToken = $_tokens[$_tokens.count - 1] - if ($_lastToken.Type -eq 'Command') - { - $_cmd = $_lastToken.Content - - # don't look for paths... - if ($_cmd.IndexOfAny('/\:') -eq -1) - { - # handle parsing errors - the last token string should be the last - # string in the line... - if ($lastword.Length -ge $_cmd.Length -and - $lastword.substring($lastword.length-$_cmd.length) -eq $_cmd) - { - $_pat = $_cmd + '*' - $_base = $lastword.substring(0, $lastword.length-$_cmd.length) - - # get files in current directory first, then look for commands... - $( try {Resolve-Path -ea SilentlyContinue -Relative $_pat } catch {} ; - try { $ExecutionContext.InvokeCommand.GetCommandName($_pat, $true, $false) | - Sort-Object -Unique } catch {} ) | - # If the command contains non-word characters (space, ) ] ; ) etc.) - # then it needs to be quoted and prefixed with & - ForEach-Object { - if ($_.IndexOfAny($_charsRequiringQuotes) -eq -1) { $_ } - elseif ($_.IndexOf('''') -ge 0) {'& ''{0}''' -f $_.Replace('''','''''') } - else { '& ''{0}''' -f $_ }} | - ForEach-Object {'{0}{1}' -f $_base,$_ } - } - } - } - } - } - } - } - "; - - #endregion "PSv2 Tab Expansion Function" - #region Host Related Strings internal const string CallId = "ci"; @@ -1600,8 +1373,6 @@ internal static RemoteDataObject GenerateClientSessionCapability(RemoteSessionCa Guid runspacePoolId) { PSObject temp = GenerateSessionCapability(capability); - temp.Properties.Add( - new PSNoteProperty(RemoteDataNameStrings.TimeZone, RemoteSessionCapability.GetCurrentTimeZoneInByteFormat())); return RemoteDataObject.CreateFrom(capability.RemotingDestination, RemotingDataType.SessionCapability, runspacePoolId, Guid.Empty, temp); } @@ -2372,24 +2143,6 @@ internal static RemoteSessionCapability GetSessionCapability(object data) RemotingDestination.InvalidDestination, protocolVersion, psVersion, serializationVersion); - if (dataAsPSObject.Properties[RemoteDataNameStrings.TimeZone] != null) - { - // Binary deserialization of timezone info via BinaryFormatter is unsafe, - // so don't deserialize any untrusted client data using this API. - // - // In addition, the binary data being sent by the client doesn't represent - // the client's current TimeZone unless they somehow accessed the - // StandardName and DaylightName. These properties are initialized lazily - // by the .NET Framework, and would be populated by the server with local - // values anyways. - // - // So just return the CurrentTimeZone. - -#if !CORECLR // TimeZone Not In CoreCLR - result.TimeZone = TimeZone.CurrentTimeZone; -#endif - } - return result; } diff --git a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteDebuggingCapability.cs b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteDebuggingCapability.cs index ee993615029..2c670b1eaaa 100644 --- a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteDebuggingCapability.cs +++ b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteDebuggingCapability.cs @@ -13,7 +13,7 @@ namespace System.Management.Automation.Remoting /// version on the server. These capabilities will be used in remote debugging sessions to /// determine what is supported by the server. /// - internal class RemoteDebuggingCapability + internal sealed class RemoteDebuggingCapability { private readonly HashSet _supportedCommands = new HashSet(); @@ -43,14 +43,14 @@ private RemoteDebuggingCapability(Version powerShellVersion) } // Commands added in v5 - if (PSVersion.Major >= PSVersionInfo.PSV5Version.Major) + if (PSVersion.Major >= 5) { _supportedCommands.Add(RemoteDebuggingCommands.SetDebuggerStepMode); _supportedCommands.Add(RemoteDebuggingCommands.SetUnhandledBreakpointMode); } // Commands added in v7 - if (PSVersion.Major >= PSVersionInfo.PSV7Version.Major) + if (PSVersion.Major >= 7) { _supportedCommands.Add(RemoteDebuggingCommands.GetBreakpoint); _supportedCommands.Add(RemoteDebuggingCommands.SetBreakpoint); diff --git a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteHost.cs b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteHost.cs index c29be13487b..79b920c22b5 100644 --- a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteHost.cs +++ b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteHost.cs @@ -188,10 +188,7 @@ internal void ExecuteVoidMethod(PSHost clientHost) } finally { - if (remoteRunspaceToClose != null) - { - remoteRunspaceToClose.Close(); - } + remoteRunspaceToClose?.Close(); } } diff --git a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteHostEncoder.cs b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteHostEncoder.cs index 3ffc278a817..cdaceda1610 100644 --- a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteHostEncoder.cs +++ b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteHostEncoder.cs @@ -7,6 +7,7 @@ using System.Globalization; using System.Management.Automation.Host; using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Security; @@ -87,7 +88,7 @@ private static PSObject EncodeClassOrStruct(object obj) /// private static object DecodeClassOrStruct(PSObject psObject, Type type) { - object obj = FormatterServices.GetUninitializedObject(type); + object obj = RuntimeHelpers.GetUninitializedObject(type); // Field values cannot be null - because for null fields we simply don't transport them. foreach (PSPropertyInfo propertyInfo in psObject.Properties) diff --git a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteSessionCapability.cs b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteSessionCapability.cs index 0cddf7d8524..add424b8703 100644 --- a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteSessionCapability.cs +++ b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteSessionCapability.cs @@ -5,7 +5,6 @@ using System.IO; using System.Management.Automation.Host; using System.Management.Automation.Internal.Host; -using System.Runtime.Serialization.Formatters.Binary; using Dbg = System.Management.Automation.Diagnostics; @@ -25,8 +24,6 @@ internal class RemoteSessionCapability private readonly Version _serversion; private Version _protocolVersion; private readonly RemotingDestination _remotingDestination; - private static byte[] _timeZoneInByteFormat; - private TimeZoneInfo _timeZone; #endregion @@ -91,64 +88,6 @@ internal static RemoteSessionCapability CreateServerCapability() { return new RemoteSessionCapability(RemotingDestination.Client); } - - /// - /// This is static property which gets Current TimeZone in byte format - /// by using ByteFormatter. - /// This is static to make client generate this only once. - /// - internal static byte[] GetCurrentTimeZoneInByteFormat() - { - if (_timeZoneInByteFormat == null) - { - Exception e = null; - try - { - BinaryFormatter formatter = new BinaryFormatter(); - using (MemoryStream stream = new MemoryStream()) - { -#pragma warning disable SYSLIB0011 - formatter.Serialize(stream, TimeZoneInfo.Local); -#pragma warning restore SYSLIB0011 - stream.Seek(0, SeekOrigin.Begin); - byte[] result = new byte[stream.Length]; - stream.Read(result, 0, (int)stream.Length); - _timeZoneInByteFormat = result; - } - } - catch (ArgumentNullException ane) - { - e = ane; - } - catch (System.Runtime.Serialization.SerializationException sre) - { - e = sre; - } - catch (System.Security.SecurityException se) - { - e = se; - } - - // if there is any exception serializing the timezone information - // ignore it and dont try to serialize again. - if (e != null) - { - _timeZoneInByteFormat = Array.Empty(); - } - } - - return _timeZoneInByteFormat; - } - - /// - /// Gets the TimeZone of the destination machine. This may be null. - /// - internal TimeZoneInfo TimeZone - { - get { return _timeZone; } - - set { _timeZone = value; } - } } /// @@ -171,7 +110,7 @@ internal enum HostDefaultDataId /// /// The HostDefaultData class. /// - internal class HostDefaultData + internal sealed class HostDefaultData { /// /// Data. @@ -448,12 +387,18 @@ private static void CheckHostChain(PSHost host, ref bool isHostNull, ref bool is isHostNull = false; // Verify that the UI is not null. - if (host.UI == null) { return; } + if (host.UI == null) + { + return; + } isHostUINull = false; // Verify that the raw UI is not null. - if (host.UI.RawUI == null) { return; } + if (host.UI.RawUI == null) + { + return; + } isHostRawUINull = false; } diff --git a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemotingDataObject.cs b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemotingDataObject.cs index 0fed6cc759d..14cad7613b8 100644 --- a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemotingDataObject.cs +++ b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemotingDataObject.cs @@ -7,11 +7,11 @@ namespace System.Management.Automation.Remoting { - /// + /// /// This is the object used by Runspace,pipeline,host to send data /// to remote end. Transport layer owns breaking this into fragments /// and sending to other end - /// + /// internal class RemoteDataObject { #region Private Members @@ -267,7 +267,7 @@ private static Guid DeserializeGuid(Stream serializedDataStream) #endregion } - internal class RemoteDataObject : RemoteDataObject + internal sealed class RemoteDataObject : RemoteDataObject { #region Constructors / Factory diff --git a/src/System.Management.Automation/engine/remoting/common/fragmentor.cs b/src/System.Management.Automation/engine/remoting/common/fragmentor.cs index 8517fb059f5..e0a82e46cf2 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) /// internal class SerializedDataStream : Stream, IDisposable { - [TraceSourceAttribute("SerializedDataStream", "SerializedDataStream")] + [TraceSource("SerializedDataStream", "SerializedDataStream")] private static readonly PSTraceSource s_trace = PSTraceSource.GetTracer("SerializedDataStream", "SerializedDataStream"); #region Global Constants diff --git a/src/System.Management.Automation/engine/remoting/common/psstreamobject.cs b/src/System.Management.Automation/engine/remoting/common/psstreamobject.cs index 4e755893886..f97b2b21d1f 100644 --- a/src/System.Management.Automation/engine/remoting/common/psstreamobject.cs +++ b/src/System.Management.Automation/engine/remoting/common/psstreamobject.cs @@ -120,10 +120,7 @@ public void WriteStreamObject(Cmdlet cmdlet, bool overrideInquire = false) ErrorRecord errorRecord = (ErrorRecord)this.Value; errorRecord.PreserveInvocationInfoOnce = true; MshCommandRuntime mshCommandRuntime = cmdlet.CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteError(errorRecord, overrideInquire); - } + mshCommandRuntime?.WriteError(errorRecord, overrideInquire); } break; @@ -133,10 +130,7 @@ public void WriteStreamObject(Cmdlet cmdlet, bool overrideInquire = false) string debug = (string)Value; DebugRecord debugRecord = new DebugRecord(debug); MshCommandRuntime mshCommandRuntime = cmdlet.CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteDebug(debugRecord, overrideInquire); - } + mshCommandRuntime?.WriteDebug(debugRecord, overrideInquire); } break; @@ -146,10 +140,7 @@ public void WriteStreamObject(Cmdlet cmdlet, bool overrideInquire = false) string warning = (string)Value; WarningRecord warningRecord = new WarningRecord(warning); MshCommandRuntime mshCommandRuntime = cmdlet.CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteWarning(warningRecord, overrideInquire); - } + mshCommandRuntime?.WriteWarning(warningRecord, overrideInquire); } break; @@ -159,10 +150,7 @@ public void WriteStreamObject(Cmdlet cmdlet, bool overrideInquire = false) string verbose = (string)Value; VerboseRecord verboseRecord = new VerboseRecord(verbose); MshCommandRuntime mshCommandRuntime = cmdlet.CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteVerbose(verboseRecord, overrideInquire); - } + mshCommandRuntime?.WriteVerbose(verboseRecord, overrideInquire); } break; @@ -170,10 +158,7 @@ public void WriteStreamObject(Cmdlet cmdlet, bool overrideInquire = false) case PSStreamObjectType.Progress: { MshCommandRuntime mshCommandRuntime = cmdlet.CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteProgress((ProgressRecord)Value, overrideInquire); - } + mshCommandRuntime?.WriteProgress((ProgressRecord)Value, overrideInquire); } break; @@ -181,10 +166,7 @@ public void WriteStreamObject(Cmdlet cmdlet, bool overrideInquire = false) case PSStreamObjectType.Information: { MshCommandRuntime mshCommandRuntime = cmdlet.CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteInformation((InformationRecord)Value, overrideInquire); - } + mshCommandRuntime?.WriteInformation((InformationRecord)Value, overrideInquire); } break; @@ -193,10 +175,7 @@ public void WriteStreamObject(Cmdlet cmdlet, bool overrideInquire = false) { WarningRecord warningRecord = (WarningRecord)Value; MshCommandRuntime mshCommandRuntime = cmdlet.CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.AppendWarningVarList(warningRecord); - } + mshCommandRuntime?.AppendWarningVarList(warningRecord); } break; @@ -246,13 +225,22 @@ private static void GetIdentifierInfo(string message, out Guid jobInstanceId, ou jobInstanceId = Guid.Empty; computerName = string.Empty; - if (message == null) return; - string[] parts = message.Split(Utils.Separators.Colon, 3); + if (message == null) + { + return; + } + + string[] parts = message.Split(':', 3); - if (parts.Length != 3) return; + if (parts.Length != 3) + { + return; + } if (!Guid.TryParse(parts[0], out jobInstanceId)) + { jobInstanceId = Guid.Empty; + } computerName = parts[1]; } @@ -311,10 +299,7 @@ internal void WriteStreamObject(Cmdlet cmdlet, Guid instanceId, bool overrideInq errorRecord.PreserveInvocationInfoOnce = true; MshCommandRuntime mshCommandRuntime = cmdlet.CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteError(errorRecord, overrideInquire); - } + mshCommandRuntime?.WriteError(errorRecord, overrideInquire); } break; @@ -324,10 +309,7 @@ internal void WriteStreamObject(Cmdlet cmdlet, Guid instanceId, bool overrideInq string warning = (string)Value; WarningRecord warningRecord = new WarningRecord(warning); MshCommandRuntime mshCommandRuntime = cmdlet.CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteWarning(warningRecord, overrideInquire); - } + mshCommandRuntime?.WriteWarning(warningRecord, overrideInquire); } break; @@ -337,10 +319,7 @@ internal void WriteStreamObject(Cmdlet cmdlet, Guid instanceId, bool overrideInq string verbose = (string)Value; VerboseRecord verboseRecord = new VerboseRecord(verbose); MshCommandRuntime mshCommandRuntime = cmdlet.CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteVerbose(verboseRecord, overrideInquire); - } + mshCommandRuntime?.WriteVerbose(verboseRecord, overrideInquire); } break; @@ -365,10 +344,7 @@ internal void WriteStreamObject(Cmdlet cmdlet, Guid instanceId, bool overrideInq } MshCommandRuntime mshCommandRuntime = cmdlet.CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteProgress(progressRecord, overrideInquire); - } + mshCommandRuntime?.WriteProgress(progressRecord, overrideInquire); } break; @@ -378,10 +354,7 @@ internal void WriteStreamObject(Cmdlet cmdlet, Guid instanceId, bool overrideInq string debug = (string)Value; DebugRecord debugRecord = new DebugRecord(debug); MshCommandRuntime mshCommandRuntime = cmdlet.CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteDebug(debugRecord, overrideInquire); - } + mshCommandRuntime?.WriteDebug(debugRecord, overrideInquire); } break; @@ -411,10 +384,7 @@ internal void WriteStreamObject(Cmdlet cmdlet, Guid instanceId, bool overrideInq } MshCommandRuntime mshCommandRuntime = cmdlet.CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteInformation(informationRecord, overrideInquire); - } + mshCommandRuntime?.WriteInformation(informationRecord, overrideInquire); } break; @@ -470,10 +440,7 @@ private static void InvokeCmdletMethodAndWaitForResults(CmdletMethodInvoker /// - /// Optional parameters required by the resource string formating information. + /// Optional parameters required by the resource string formatting information. /// /// /// The formatted localized string. @@ -282,7 +283,6 @@ internal static string FormatResourceString(string resourceString, params object /// /// This exception is used by remoting code to indicated a data structure handler related error. /// - [Serializable] public class PSRemotingDataStructureException : RuntimeException { #region Constructors @@ -297,7 +297,7 @@ public PSRemotingDataStructureException() } /// - /// This constuctor takes a localized string as the error message. + /// This constructor takes a localized string as the error message. /// /// /// A localized string as an error message. @@ -309,7 +309,7 @@ public PSRemotingDataStructureException(string message) } /// - /// This constuctor takes a localized string as the error message, and an inner exception. + /// This constructor takes a localized string as the error message, and an inner exception. /// /// /// A localized string as an error message. @@ -339,7 +339,7 @@ internal PSRemotingDataStructureException(string resourceString, params object[] } /// - /// This constuctor takes an inner exception and an error id. + /// This constructor takes an inner exception and an error id. /// /// /// Inner exception. @@ -361,9 +361,10 @@ internal PSRemotingDataStructureException(Exception innerException, string resou /// /// /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSRemotingDataStructureException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion Constructors @@ -381,7 +382,6 @@ private void SetDefaultErrorRecord() /// /// This exception is used by remoting code to indicate an error condition in network operations. /// - [Serializable] public class PSRemotingTransportException : RuntimeException { private int _errorCode; @@ -445,7 +445,7 @@ internal PSRemotingTransportException(PSRemotingErrorId errorId, string resource } /// - /// This constuctor takes an inner exception and an error id. + /// This constructor takes an inner exception and an error id. /// /// /// Inner exception. @@ -470,38 +470,14 @@ internal PSRemotingTransportException(Exception innerException, string resourceS /// /// 1. info is null. /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSRemotingTransportException(SerializationInfo info, StreamingContext context) - : base(info, context) { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - _errorCode = info.GetInt32("ErrorCode"); - _transportMessage = info.GetString("TransportMessage"); + throw new NotSupportedException(); } #endregion Constructors - /// - /// Serializes the exception data. - /// - /// Serialization information. - /// Streaming context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - // If there are simple fields, serialize them with info.AddValue - info.AddValue("ErrorCode", _errorCode); - info.AddValue("TransportMessage", _transportMessage); - } - /// /// Set the default ErrorRecord. /// @@ -548,7 +524,6 @@ public string TransportMessage /// This exception is used by PowerShell's remoting infrastructure to notify a URI redirection /// exception. /// - [Serializable] public class PSRemotingTransportRedirectException : PSRemotingTransportException { #region Constructor @@ -588,7 +563,7 @@ public PSRemotingTransportRedirectException(string message, Exception innerExcep } /// - /// This constuctor takes an inner exception and an error id. + /// This constructor takes an inner exception and an error id. /// /// /// Inner exception. @@ -612,15 +587,10 @@ internal PSRemotingTransportRedirectException(Exception innerException, string r /// /// 1. info is null. /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSRemotingTransportRedirectException(SerializationInfo info, StreamingContext context) - : base(info, context) { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - RedirectLocation = info.GetString("RedirectLocation"); + throw new NotSupportedException(); } /// @@ -646,27 +616,6 @@ internal PSRemotingTransportRedirectException(string redirectLocation, PSRemotin #endregion - #region Public overrides - - /// - /// Serializes the exception data. - /// - /// Serialization information. - /// Streaming context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - // If there are simple fields, serialize them with info.AddValue - info.AddValue("RedirectLocation", RedirectLocation); - } - - #endregion - #region Properties /// /// String specifying a redirect location. @@ -679,13 +628,12 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont /// /// This exception is used by PowerShell Direct errors. /// - [Serializable] public class PSDirectException : RuntimeException { #region Constructor /// - /// This constuctor takes a localized string as the error message. + /// This constructor takes a localized string as the error message. /// /// /// A localized string as an error message. diff --git a/src/System.Management.Automation/engine/remoting/common/throttlemanager.cs b/src/System.Management.Automation/engine/remoting/common/throttlemanager.cs index afccc28362a..7d4a88e98bd 100644 --- a/src/System.Management.Automation/engine/remoting/common/throttlemanager.cs +++ b/src/System.Management.Automation/engine/remoting/common/throttlemanager.cs @@ -117,7 +117,7 @@ internal abstract class IThrottleOperation /// need not be called on the operation (this can be when the /// operation has stop completed or stop has been called and is /// pending) - /// + /// internal bool IgnoreStop { get @@ -532,10 +532,7 @@ private void StartOneOperationFromQueue() } } - if (operation != null) - { - operation.StartOperation(); - } + operation?.StartOperation(); } /// diff --git a/src/System.Management.Automation/engine/remoting/fanin/BaseTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/BaseTransportManager.cs index 5d12914074c..0859a54965f 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/BaseTransportManager.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/BaseTransportManager.cs @@ -33,27 +33,81 @@ namespace System.Management.Automation.Remoting { #region TransportErrorOccuredEventArgs - internal enum TransportMethodEnum + /// + /// Transport method for error reporting. + /// + public enum TransportMethodEnum { + /// + /// CreateShellEx + /// CreateShellEx = 0, + + /// + /// RunShellCommandEx + /// RunShellCommandEx = 1, + + /// + /// SendShellInputEx + /// SendShellInputEx = 2, + + /// + /// ReceiveShellOutputEx + /// ReceiveShellOutputEx = 3, + + /// + /// CloseShellOperationEx + /// CloseShellOperationEx = 4, + + /// + /// CommandInputEx + /// CommandInputEx = 5, + + /// + /// ReceiveCommandOutputEx + /// ReceiveCommandOutputEx = 6, + + /// + /// DisconnectShellEx + /// DisconnectShellEx = 7, + + /// + /// ReconnectShellEx + /// ReconnectShellEx = 8, + + /// + /// ConnectShellEx + /// ConnectShellEx = 9, + + /// + /// ReconnectShellCommandEx + /// ReconnectShellCommandEx = 10, + + /// + /// ConnectShellCommandEx + /// ConnectShellCommandEx = 11, + + /// + /// Unknown + /// Unknown = 12, } /// - /// Event arguments passed to TransportErrorOccured handlers. + /// Event arguments passed to TransportErrorOccurred handlers. /// - internal class TransportErrorOccuredEventArgs : EventArgs + public sealed class TransportErrorOccuredEventArgs : EventArgs { /// /// Constructor. @@ -62,9 +116,10 @@ internal class TransportErrorOccuredEventArgs : EventArgs /// Error occurred. /// /// - /// The transport method that raised the error + /// The transport method that raised the error. /// - internal TransportErrorOccuredEventArgs(PSRemotingTransportException e, + public TransportErrorOccuredEventArgs( + PSRemotingTransportException e, TransportMethodEnum m) { Exception = e; @@ -136,11 +191,11 @@ internal CreateCompleteEventArgs( /// Contains implementation that is common to both client and server /// transport managers. /// - internal abstract class BaseTransportManager : IDisposable + 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 @@ -206,7 +261,7 @@ internal abstract class BaseTransportManager : IDisposable #region Constructor - protected BaseTransportManager(PSRemotingCryptoHelper cryptoHelper) + internal BaseTransportManager(PSRemotingCryptoHelper cryptoHelper) { CryptoHelper = cryptoHelper; // create a common fragmentor used by this transport manager to send and receive data. @@ -308,8 +363,9 @@ internal void ProcessRawData(byte[] data, if (!shouldProcess) { // we dont support this stream..so ignore the data - Dbg.Assert(false, - string.Format(CultureInfo.InvariantCulture, "Data should be from one of the streams : {0} or {1} or {2}", + Dbg.Assert(false, string.Format( + CultureInfo.InvariantCulture, + "Data should be from one of the streams : {0} or {1} or {2}", WSManNativeApi.WSMAN_STREAM_ID_STDIN, WSManNativeApi.WSMAN_STREAM_ID_STDOUT, WSManNativeApi.WSMAN_STREAM_ID_PROMPTRESPONSE)); @@ -361,7 +417,7 @@ public void MigrateDataReadyEventHandlers(BaseTransportManager transportManager) /// Raise the error handlers. /// /// - internal virtual void RaiseErrorHandler(TransportErrorOccuredEventArgs eventArgs) + public virtual void RaiseErrorHandler(TransportErrorOccuredEventArgs eventArgs) { WSManTransportErrorOccured.SafeInvoke(this, eventArgs); } @@ -393,7 +449,10 @@ public void Dispose() System.GC.SuppressFinalize(this); } - internal virtual void Dispose(bool isDisposing) + /// + /// Dispose resources. + /// + protected virtual void Dispose(bool isDisposing) { if (isDisposing) { @@ -407,18 +466,21 @@ internal virtual void Dispose(bool isDisposing) namespace System.Management.Automation.Remoting.Client { - internal abstract class BaseClientTransportManager : BaseTransportManager, IDisposable + /// + /// Remoting base client transport manager. + /// + public abstract class BaseClientTransportManager : BaseTransportManager, IDisposable { #region Tracer - [TraceSourceAttribute("ClientTransport", "Traces ClientTransportManager")] - protected static PSTraceSource tracer = PSTraceSource.GetTracer("ClientTransport", "Traces ClientTransportManager"); + [TraceSource("ClientTransport", "Traces ClientTransportManager")] + internal static PSTraceSource tracer = PSTraceSource.GetTracer("ClientTransport", "Traces ClientTransportManager"); #endregion #region Data - protected bool isClosed; - protected object syncObject = new object(); - protected PrioritySendDataCollection dataToBeSent; + internal bool isClosed; + internal object syncObject = new object(); + internal PrioritySendDataCollection dataToBeSent; // used to handle callbacks from the server..these are used to synchronize received callbacks private readonly Queue _callbackNotificationQueue; private readonly ReceiveDataCollection.OnDataAvailableCallback _onDataAvailableCallback; @@ -429,13 +491,13 @@ internal abstract class BaseClientTransportManager : BaseTransportManager, IDisp // this is used log crimson messages. // keeps track of whether a receive request has been placed on transport - protected bool receiveDataInitiated; + internal bool receiveDataInitiated; #endregion #region Constructors - protected BaseClientTransportManager(Guid runspaceId, PSRemotingCryptoHelper cryptoHelper) + internal BaseClientTransportManager(Guid runspaceId, PSRemotingCryptoHelper cryptoHelper) : base(cryptoHelper) { RunspacePoolInstanceId = runspaceId; @@ -933,14 +995,17 @@ internal class CallbackNotificationInformation #region Abstract / Virtual methods - internal abstract void CreateAsync(); + /// + /// Create the transport manager and initiate connection. + /// + public abstract void CreateAsync(); internal abstract void ConnectAsync(); /// /// The caller should make sure the call is synchronized. /// - internal virtual void CloseAsync() + public virtual void CloseAsync() { // Clear the send collection dataToBeSent.Clear(); @@ -998,7 +1063,10 @@ internal virtual void PrepareForConnect() } } - internal override void Dispose(bool isDisposing) + /// + /// Dispose resources. + /// + protected override void Dispose(bool isDisposing) { // clear event handlers this.CreateCompleted = null; @@ -1014,11 +1082,14 @@ internal override void Dispose(bool isDisposing) #endregion } - internal abstract class BaseClientSessionTransportManager : BaseClientTransportManager, IDisposable + /// + /// Remoting base client session transport manager. + /// + public abstract class BaseClientSessionTransportManager : BaseClientTransportManager, IDisposable { #region Constructors - protected BaseClientSessionTransportManager(Guid runspaceId, PSRemotingCryptoHelper cryptoHelper) + internal BaseClientSessionTransportManager(Guid runspaceId, PSRemotingCryptoHelper cryptoHelper) : base(runspaceId, cryptoHelper) { } @@ -1167,7 +1238,7 @@ internal void RaiseSignalCompleted() #region Overrides - internal override void Dispose(bool isDisposing) + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -1289,10 +1360,7 @@ internal void SendDataToClient(RemoteDataObject data, bool flush, bool rep data.Data); if (_isSerializing) { - if (_dataToBeSentQueue == null) - { - _dataToBeSentQueue = new Queue>(); - } + _dataToBeSentQueue ??= new Queue>(); _dataToBeSentQueue.Enqueue(new Tuple(dataToBeSent, flush, reportPending)); return; diff --git a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs index 6cde06cea36..94ffe3a68e6 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs @@ -22,6 +22,8 @@ namespace System.Management.Automation.Remoting { + #region WSMan endpoint configuration + /// /// This struct is used to represent contents from configuration xml. The /// XML is passed to plugins by WSMan API. @@ -56,6 +58,8 @@ internal class ConfigurationDataFromXML #endregion + #region Fields + internal string StartupScript; // this field is used only by an Out-Of-Process (IPC) server process internal string InitializationScriptForOutOfProcessRunspace; @@ -71,6 +75,10 @@ internal class ConfigurationDataFromXML internal PSSessionConfigurationData SessionConfigurationData; internal string ConfigFilePath; + #endregion + + #region Methods + /// /// Using optionName and optionValue updates the current object. /// @@ -278,15 +286,9 @@ internal static ConfigurationDataFromXML Create(string initializationParameters) } // assign defaults after parsing the xml content. - if (result.MaxReceivedObjectSizeMB == null) - { - result.MaxReceivedObjectSizeMB = BaseTransportManager.MaximumReceivedObjectSize; - } + result.MaxReceivedObjectSizeMB ??= BaseTransportManager.MaximumReceivedObjectSize; - if (result.MaxReceivedCommandSizeMB == null) - { - result.MaxReceivedCommandSizeMB = BaseTransportManager.MaximumReceivedDataSize; - } + result.MaxReceivedCommandSizeMB ??= BaseTransportManager.MaximumReceivedDataSize; return result; } @@ -324,6 +326,8 @@ internal PSSessionConfiguration CreateEndPointConfigurationInstance() throw PSTraceSource.NewArgumentException("typeToLoad", RemotingErrorIdStrings.UnableToLoadType, EndPointConfigurationTypeName, ConfigurationDataFromXML.INITPARAMETERSTOKEN); } + + #endregion } /// @@ -336,7 +340,7 @@ public abstract class PSSessionConfiguration : IDisposable /// /// Tracer for Server Remote session. /// - [TraceSourceAttribute("ServerRemoteSession", "ServerRemoteSession")] + [TraceSource("ServerRemoteSession", "ServerRemoteSession")] private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("ServerRemoteSession", "ServerRemoteSession"); #endregion tracer @@ -450,7 +454,8 @@ protected virtual void Dispose(bool isDisposing) ... */ - internal static ConfigurationDataFromXML LoadEndPointConfiguration(string shellId, + internal static ConfigurationDataFromXML LoadEndPointConfiguration( + string shellId, string initializationParameters) { ConfigurationDataFromXML configData = null; @@ -798,6 +803,8 @@ private static string /// internal sealed class DefaultRemotePowerShellConfiguration : PSSessionConfiguration { + #region Method overrides + /// /// /// @@ -805,22 +812,23 @@ internal sealed class DefaultRemotePowerShellConfiguration : PSSessionConfigurat public override InitialSessionState GetInitialSessionState(PSSenderInfo senderInfo) { InitialSessionState result = InitialSessionState.CreateDefault2(); + // TODO: Remove this after RDS moved to $using - if (senderInfo.ConnectionString != null && senderInfo.ConnectionString.Contains("MSP=7a83d074-bb86-4e52-aa3e-6cc73cc066c8")) { PSSessionConfigurationData.IsServerManager = true; } + if (senderInfo.ConnectionString != null && senderInfo.ConnectionString.Contains("MSP=7a83d074-bb86-4e52-aa3e-6cc73cc066c8")) + { + PSSessionConfigurationData.IsServerManager = true; + } return result; } public override InitialSessionState GetInitialSessionState(PSSessionConfigurationData sessionConfigurationData, PSSenderInfo senderInfo, string configProviderId) { - if (sessionConfigurationData == null) - throw new ArgumentNullException(nameof(sessionConfigurationData)); + ArgumentNullException.ThrowIfNull(sessionConfigurationData); - if (senderInfo == null) - throw new ArgumentNullException(nameof(senderInfo)); + ArgumentNullException.ThrowIfNull(senderInfo); - if (configProviderId == null) - throw new ArgumentNullException(nameof(configProviderId)); + ArgumentNullException.ThrowIfNull(configProviderId); InitialSessionState sessionState = InitialSessionState.CreateDefault2(); // now get all the modules in the specified path and import the same @@ -848,13 +856,22 @@ public override InitialSessionState GetInitialSessionState(PSSessionConfiguratio } // TODO: Remove this after RDS moved to $using - if (senderInfo.ConnectionString != null && senderInfo.ConnectionString.Contains("MSP=7a83d074-bb86-4e52-aa3e-6cc73cc066c8")) { PSSessionConfigurationData.IsServerManager = true; } + if (senderInfo.ConnectionString != null && senderInfo.ConnectionString.Contains("MSP=7a83d074-bb86-4e52-aa3e-6cc73cc066c8")) + { + PSSessionConfigurationData.IsServerManager = true; + } return sessionState; } + + #endregion } - #region Declarative Initial Session Configuration + #endregion + + #region Declarative InitialSession Configuration + + #region Supporting types /// /// Specifies type of initial session state to use. Valid values are Empty and Default. @@ -898,6 +915,10 @@ internal ConfigTypeEntry(string key, TypeValidationCallback callback) } } + #endregion + + #region ConfigFileConstants + /// /// Configuration file constants. /// @@ -949,41 +970,41 @@ internal static class ConfigFileConstants internal static readonly string VisibleExternalCommands = "VisibleExternalCommands"; internal static readonly ConfigTypeEntry[] ConfigFileKeys = new ConfigTypeEntry[] { - new ConfigTypeEntry(AliasDefinitions, new ConfigTypeEntry.TypeValidationCallback(AliasDefinitionsTypeValidationCallback)), - new ConfigTypeEntry(AssembliesToLoad, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), - new ConfigTypeEntry(Author, new ConfigTypeEntry.TypeValidationCallback(StringTypeValidationCallback)), - new ConfigTypeEntry(CompanyName, new ConfigTypeEntry.TypeValidationCallback(StringTypeValidationCallback)), - new ConfigTypeEntry(Copyright, new ConfigTypeEntry.TypeValidationCallback(StringTypeValidationCallback)), - new ConfigTypeEntry(Description, new ConfigTypeEntry.TypeValidationCallback(StringTypeValidationCallback)), - new ConfigTypeEntry(EnforceInputParameterValidation,new ConfigTypeEntry.TypeValidationCallback(BooleanTypeValidationCallback)), - new ConfigTypeEntry(EnvironmentVariables, new ConfigTypeEntry.TypeValidationCallback(HashtableTypeValidationCallback)), - new ConfigTypeEntry(ExecutionPolicy, new ConfigTypeEntry.TypeValidationCallback(ExecutionPolicyValidationCallback)), - new ConfigTypeEntry(FormatsToProcess, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), - new ConfigTypeEntry(FunctionDefinitions, new ConfigTypeEntry.TypeValidationCallback(FunctionDefinitionsTypeValidationCallback)), - new ConfigTypeEntry(GMSAAccount, new ConfigTypeEntry.TypeValidationCallback(StringTypeValidationCallback)), - new ConfigTypeEntry(Guid, new ConfigTypeEntry.TypeValidationCallback(StringTypeValidationCallback)), - new ConfigTypeEntry(LanguageMode, new ConfigTypeEntry.TypeValidationCallback(LanguageModeValidationCallback)), - new ConfigTypeEntry(ModulesToImport, new ConfigTypeEntry.TypeValidationCallback(StringOrHashtableArrayTypeValidationCallback)), - new ConfigTypeEntry(MountUserDrive, new ConfigTypeEntry.TypeValidationCallback(BooleanTypeValidationCallback)), - new ConfigTypeEntry(PowerShellVersion, new ConfigTypeEntry.TypeValidationCallback(StringTypeValidationCallback)), - new ConfigTypeEntry(RequiredGroups, new ConfigTypeEntry.TypeValidationCallback(HashtableTypeValidationCallback)), - new ConfigTypeEntry(RoleCapabilities, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), - new ConfigTypeEntry(RoleCapabilityFiles, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), - new ConfigTypeEntry(RoleDefinitions, new ConfigTypeEntry.TypeValidationCallback(HashtableTypeValidationCallback)), - new ConfigTypeEntry(RunAsVirtualAccount, new ConfigTypeEntry.TypeValidationCallback(BooleanTypeValidationCallback)), - new ConfigTypeEntry(RunAsVirtualAccountGroups, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), - new ConfigTypeEntry(SchemaVersion, new ConfigTypeEntry.TypeValidationCallback(StringTypeValidationCallback)), - new ConfigTypeEntry(ScriptsToProcess, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), - new ConfigTypeEntry(SessionType, new ConfigTypeEntry.TypeValidationCallback(ISSValidationCallback)), - new ConfigTypeEntry(TranscriptDirectory, new ConfigTypeEntry.TypeValidationCallback(StringTypeValidationCallback)), - new ConfigTypeEntry(TypesToProcess, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), - new ConfigTypeEntry(UserDriveMaxSize, new ConfigTypeEntry.TypeValidationCallback(IntegerTypeValidationCallback)), - new ConfigTypeEntry(VariableDefinitions, new ConfigTypeEntry.TypeValidationCallback(VariableDefinitionsTypeValidationCallback)), - new ConfigTypeEntry(VisibleAliases, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), - new ConfigTypeEntry(VisibleCmdlets, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), - new ConfigTypeEntry(VisibleFunctions, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), - new ConfigTypeEntry(VisibleProviders, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), - new ConfigTypeEntry(VisibleExternalCommands, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), + new ConfigTypeEntry(AliasDefinitions, new ConfigTypeEntry.TypeValidationCallback(AliasDefinitionsTypeValidationCallback)), + new ConfigTypeEntry(AssembliesToLoad, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), + new ConfigTypeEntry(Author, new ConfigTypeEntry.TypeValidationCallback(StringTypeValidationCallback)), + new ConfigTypeEntry(CompanyName, new ConfigTypeEntry.TypeValidationCallback(StringTypeValidationCallback)), + new ConfigTypeEntry(Copyright, new ConfigTypeEntry.TypeValidationCallback(StringTypeValidationCallback)), + new ConfigTypeEntry(Description, new ConfigTypeEntry.TypeValidationCallback(StringTypeValidationCallback)), + new ConfigTypeEntry(EnforceInputParameterValidation, new ConfigTypeEntry.TypeValidationCallback(BooleanTypeValidationCallback)), + new ConfigTypeEntry(EnvironmentVariables, new ConfigTypeEntry.TypeValidationCallback(HashtableTypeValidationCallback)), + new ConfigTypeEntry(ExecutionPolicy, new ConfigTypeEntry.TypeValidationCallback(ExecutionPolicyValidationCallback)), + new ConfigTypeEntry(FormatsToProcess, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), + new ConfigTypeEntry(FunctionDefinitions, new ConfigTypeEntry.TypeValidationCallback(FunctionDefinitionsTypeValidationCallback)), + new ConfigTypeEntry(GMSAAccount, new ConfigTypeEntry.TypeValidationCallback(StringTypeValidationCallback)), + new ConfigTypeEntry(Guid, new ConfigTypeEntry.TypeValidationCallback(StringTypeValidationCallback)), + new ConfigTypeEntry(LanguageMode, new ConfigTypeEntry.TypeValidationCallback(LanguageModeValidationCallback)), + new ConfigTypeEntry(ModulesToImport, new ConfigTypeEntry.TypeValidationCallback(StringOrHashtableArrayTypeValidationCallback)), + new ConfigTypeEntry(MountUserDrive, new ConfigTypeEntry.TypeValidationCallback(BooleanTypeValidationCallback)), + new ConfigTypeEntry(PowerShellVersion, new ConfigTypeEntry.TypeValidationCallback(StringTypeValidationCallback)), + new ConfigTypeEntry(RequiredGroups, new ConfigTypeEntry.TypeValidationCallback(HashtableTypeValidationCallback)), + new ConfigTypeEntry(RoleCapabilities, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), + new ConfigTypeEntry(RoleCapabilityFiles, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), + new ConfigTypeEntry(RoleDefinitions, new ConfigTypeEntry.TypeValidationCallback(HashtableTypeValidationCallback)), + new ConfigTypeEntry(RunAsVirtualAccount, new ConfigTypeEntry.TypeValidationCallback(BooleanTypeValidationCallback)), + new ConfigTypeEntry(RunAsVirtualAccountGroups, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), + new ConfigTypeEntry(SchemaVersion, new ConfigTypeEntry.TypeValidationCallback(StringTypeValidationCallback)), + new ConfigTypeEntry(ScriptsToProcess, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), + new ConfigTypeEntry(SessionType, new ConfigTypeEntry.TypeValidationCallback(ISSValidationCallback)), + new ConfigTypeEntry(TranscriptDirectory, new ConfigTypeEntry.TypeValidationCallback(StringTypeValidationCallback)), + new ConfigTypeEntry(TypesToProcess, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), + new ConfigTypeEntry(UserDriveMaxSize, new ConfigTypeEntry.TypeValidationCallback(IntegerTypeValidationCallback)), + new ConfigTypeEntry(VariableDefinitions, new ConfigTypeEntry.TypeValidationCallback(VariableDefinitionsTypeValidationCallback)), + new ConfigTypeEntry(VisibleAliases, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), + new ConfigTypeEntry(VisibleCmdlets, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), + new ConfigTypeEntry(VisibleFunctions, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), + new ConfigTypeEntry(VisibleProviders, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), + new ConfigTypeEntry(VisibleExternalCommands, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), }; /// @@ -1355,6 +1376,8 @@ private static bool StringOrHashtableArrayTypeValidationCallback(string key, obj } } + #endregion + #region DISC Utilities /// @@ -1681,6 +1704,8 @@ internal static void ValidateRoleDefinitions(IDictionary roleDefinitions) #endregion + #region DISCPowerShellConfiguration + /// /// Creates an initial session state based on the configuration language for PSSC files. /// @@ -1706,13 +1731,14 @@ internal Hashtable ConfigHash /// target session. If you have a WindowsPrincipal for a user, for example, create a Function that /// checks windowsPrincipal.IsInRole(). /// - internal DISCPowerShellConfiguration(string configFile, Func roleVerifier) + /// Validate file for supported configuration options. + internal DISCPowerShellConfiguration( + string configFile, + Func roleVerifier, + bool validateFile = false) { _configFile = configFile; - if (roleVerifier == null) - { - roleVerifier = (role) => false; - } + roleVerifier ??= static (role) => false; Runspace backupRunspace = Runspace.DefaultRunspace; @@ -1726,6 +1752,12 @@ internal DISCPowerShellConfiguration(string configFile, Func roleV configFile, out scriptName); _configHash = DISCUtils.LoadConfigFile(Runspace.DefaultRunspace.ExecutionContext, script); + + if (validateFile) + { + DISCFileValidation.ValidateContents(_configHash); + } + MergeRoleRulesIntoConfigHash(roleVerifier); MergeRoleCapabilitiesIntoConfigHash(); @@ -1901,13 +1933,13 @@ private static string GetRoleCapabilityPath(string roleCapability) string moduleName = "*"; if (roleCapability.Contains('\\')) { - string[] components = roleCapability.Split(Utils.Separators.Backslash, 2); + string[] components = roleCapability.Split('\\', 2); moduleName = components[0]; roleCapability = components[1]; } // Go through each directory in the module path - string[] modulePaths = ModuleIntrinsics.GetModulePath().Split(Utils.Separators.PathSeparator); + string[] modulePaths = ModuleIntrinsics.GetModulePath().Split(Path.PathSeparator); foreach (string path in modulePaths) { try @@ -2446,7 +2478,7 @@ private static void ProcessVisibleCommands(InitialSessionState iss, object[] com // Parameters = A dictionary of parameter names -> Modifications // Modifications = A dictionary of modification types (ValidatePattern, ValidateSet) to the interim value // for that attribute, as a HashSet of strings. For ValidateSet, this will be used as a collection of strings - // directly during proxy generation. For For ValidatePattern, it will be combined into a regex + // directly during proxy generation. For ValidatePattern, it will be combined into a regex // like: '^(Pattern1|Pattern2|Pattern3)$' during proxy generation. Dictionary commandModifications = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -2890,4 +2922,110 @@ internal static T[] TryGetObjectsOfType(object hashObj, IEnumerable typ } } #endregion + + #region DISCFileValidation + + internal static class DISCFileValidation + { + // Set of supported configuration options for a PowerShell InitialSessionState. +#if UNIX + private static readonly HashSet SupportedConfigOptions = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "AliasDefinitions", + "AssembliesToLoad", + "Author", + "CompanyName", + "Copyright", + "Description", + "EnvironmentVariables", + "FormatsToProcess", + "FunctionDefinitions", + "GUID", + "LanguageMode", + "ModulesToImport", + "MountUserDrive", + "SchemaVersion", + "ScriptsToProcess", + "SessionType", + "TranscriptDirectory", + "TypesToProcess", + "UserDriveMaximumSize", + "VisibleAliases", + "VisibleCmdlets", + "VariableDefinitions", + "VisibleExternalCommands", + "VisibleFunctions", + "VisibleProviders" + }; +#else + private static readonly HashSet SupportedConfigOptions = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "AliasDefinitions", + "AssembliesToLoad", + "Author", + "CompanyName", + "Copyright", + "Description", + "EnvironmentVariables", + "ExecutionPolicy", + "FormatsToProcess", + "FunctionDefinitions", + "GUID", + "LanguageMode", + "ModulesToImport", + "MountUserDrive", + "SchemaVersion", + "ScriptsToProcess", + "SessionType", + "TranscriptDirectory", + "TypesToProcess", + "UserDriveMaximumSize", + "VisibleAliases", + "VisibleCmdlets", + "VariableDefinitions", + "VisibleExternalCommands", + "VisibleFunctions", + "VisibleProviders" + }; +#endif + + // These are configuration options for WSMan (WinRM) endpoint configurations, that + // appear in .pssc files, but are not part of PowerShell InitialSessionState. + private static readonly HashSet UnsupportedConfigOptions = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "GroupManagedServiceAccount", + "PowerShellVersion", + "RequiredGroups", + "RoleDefinitions", + "RunAsVirtualAccount", + "RunAsVirtualAccountGroups" + }; + + internal static void ValidateContents(Hashtable configHash) + { + foreach (var key in configHash.Keys) + { + if (key is not string keyName) + { + throw new PSInvalidOperationException(RemotingErrorIdStrings.DISCInvalidConfigKeyType); + } + + if (UnsupportedConfigOptions.Contains(keyName)) + { + throw new PSInvalidOperationException( + StringUtil.Format(RemotingErrorIdStrings.DISCUnsupportedConfigName, keyName)); + } + + if (!SupportedConfigOptions.Contains(keyName)) + { + throw new PSInvalidOperationException( + StringUtil.Format(RemotingErrorIdStrings.DISCUnknownConfigName, keyName)); + } + } + } + } + + #endregion + + #endregion } diff --git a/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs index b8f6da13c17..96a8b833885 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs @@ -75,7 +75,8 @@ static OutOfProcessUtils() internal static string CreateDataPacket(byte[] data, DataPriorityType streamType, Guid psGuid) { - string result = string.Format(CultureInfo.InvariantCulture, + string result = string.Format( + CultureInfo.InvariantCulture, "<{0} {1}='{2}' {3}='{4}'>{5}", PS_OUT_OF_PROC_DATA_TAG, PS_OUT_OF_PROC_STREAM_ATTRIBUTE, @@ -131,7 +132,8 @@ internal static string CreateSignalAckPacket(Guid psGuid) /// private static string CreatePSGuidPacket(string element, Guid psGuid) { - string result = string.Format(CultureInfo.InvariantCulture, + string result = string.Format( + CultureInfo.InvariantCulture, "<{0} {1}='{2}' />", element, PS_OUT_OF_PROC_PSGUID_ATTRIBUTE, @@ -412,6 +414,19 @@ internal class OutOfProcessTextWriter private readonly TextWriter _writer; private bool _isStopped; private readonly object _syncObject = new object(); + private const string _errorPrepend = "__NamedPipeError__:"; + + #endregion + + #region Properties + + /// + /// Prefix for transport error message. + /// + public static string ErrorPrefix + { + get => _errorPrepend; + } #endregion @@ -421,9 +436,13 @@ internal class OutOfProcessTextWriter /// Constructs the wrapper. /// /// - internal OutOfProcessTextWriter(TextWriter writerToWrap) + public OutOfProcessTextWriter(TextWriter writerToWrap) { - Dbg.Assert(writerToWrap != null, "Cannot wrap a null writer."); + if (writerToWrap is null) + { + throw new PSArgumentNullException(nameof(writerToWrap)); + } + _writer = writerToWrap; } @@ -435,7 +454,7 @@ internal OutOfProcessTextWriter(TextWriter writerToWrap) /// Calls writer.WriteLine() with data. /// /// - internal virtual void WriteLine(string data) + public virtual void WriteLine(string data) { if (_isStopped) { @@ -467,7 +486,10 @@ internal void StopWriting() namespace System.Management.Automation.Remoting.Client { - internal abstract class OutOfProcessClientSessionTransportManagerBase : BaseClientSessionTransportManager + /// + /// Client session transport manager abstract base class. + /// + public abstract class ClientSessionTransportManagerBase : BaseClientSessionTransportManager { #region Data @@ -477,15 +499,17 @@ internal abstract class OutOfProcessClientSessionTransportManagerBase : BaseClie private OutOfProcessUtils.DataProcessingDelegates _dataProcessingCallbacks; private readonly Dictionary _cmdTransportManagers; private readonly Timer _closeTimeOutTimer; - - protected OutOfProcessTextWriter stdInWriter; - protected PowerShellTraceSource _tracer; + internal PowerShellTraceSource _tracer; + internal OutOfProcessTextWriter _messageWriter; #endregion #region Constructor - internal OutOfProcessClientSessionTransportManagerBase( + /// + /// Constructor. + /// + protected ClientSessionTransportManagerBase( Guid runspaceId, PSRemotingCryptoHelper cryptoHelper) : base(runspaceId, cryptoHelper) @@ -505,7 +529,7 @@ internal OutOfProcessClientSessionTransportManagerBase( _dataProcessingCallbacks.ClosePacketReceived += new OutOfProcessUtils.ClosePacketReceived(OnClosePacketReceived); _dataProcessingCallbacks.CloseAckPacketReceived += new OutOfProcessUtils.CloseAckPacketReceived(OnCloseAckReceived); - dataToBeSent.Fragmentor = base.Fragmentor; + dataToBeSent.Fragmentor = Fragmentor; // session transport manager can receive unlimited data..however each object is limited // by maxRecvdObjectSize. this is to allow clients to use a session for an unlimited time.. // also the messages that can be sent to a session are limited and very controlled. @@ -546,7 +570,7 @@ internal override void ConnectAsync() /// /// Closes the server process. /// - internal override void CloseAsync() + public override void CloseAsync() { bool shouldRaiseCloseCompleted = false; lock (syncObject) @@ -560,7 +584,7 @@ internal override void CloseAsync() // will know that we are closing. isClosed = true; - if (stdInWriter == null) + if (_messageWriter == null) { // this will happen if CloseAsync() is called // before ConnectAsync()..in which case we @@ -586,12 +610,12 @@ internal override void CloseAsync() try { // send Close signal to the server and let it die gracefully. - stdInWriter.WriteLine(OutOfProcessUtils.CreateClosePacket(Guid.Empty)); + _messageWriter.WriteLine(OutOfProcessUtils.CreateClosePacket(Guid.Empty)); // start the timer..so client can fail deterministically _closeTimeOutTimer.Change(60 * 1000, Timeout.Infinite); } - catch (IOException) + catch (Exception ex) when (ex is IOException || ex is ObjectDisposedException) { // Cannot communicate with server. Allow client to complete close operation. shouldRaiseCloseCompleted = true; @@ -618,7 +642,7 @@ internal override BaseClientCommandTransportManager CreateClientCommandTransport Dbg.Assert(cmd != null, "Cmd cannot be null"); OutOfProcessClientCommandTransportManager result = new - OutOfProcessClientCommandTransportManager(cmd, noInput, this, stdInWriter); + OutOfProcessClientCommandTransportManager(cmd, noInput, this, _messageWriter); AddCommandTransportManager(cmd.InstanceId, result); return result; @@ -628,37 +652,14 @@ internal override BaseClientCommandTransportManager CreateClientCommandTransport /// Terminates the server process and disposes other resources. /// /// - internal override void Dispose(bool isDisposing) + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); if (isDisposing) { _cmdTransportManagers.Clear(); _closeTimeOutTimer.Dispose(); - - // Stop session processing thread. - try - { - _sessionMessageQueue.CompleteAdding(); - } - catch (ObjectDisposedException) - { - // Object already disposed. - } - - _sessionMessageQueue.Dispose(); - - // Stop command processing thread. - try - { - _commandMessageQueue.CompleteAdding(); - } - catch (ObjectDisposedException) - { - // Object already disposed. - } - - _commandMessageQueue.Dispose(); + DisposeMessageQueue(); } } @@ -716,6 +717,9 @@ private void OnCloseSessionCompleted() CleanupConnection(); } + /// + /// Optional additional connection clean up after a connection is closed. + /// protected abstract void CleanupConnection(); private void ProcessMessageProc(object state) @@ -754,6 +758,9 @@ private void ProcessMessageProc(object state) private const string SESSIONDMESSAGETAG = "PSGuid='00000000-0000-0000-0000-000000000000'"; + /// + /// Handles protocol output data from a transport. + /// protected void HandleOutputDataReceived(string data) { if (string.IsNullOrEmpty(data)) @@ -785,6 +792,9 @@ protected void HandleOutputDataReceived(string data) } } + /// + /// Handles protocol error data. + /// protected void HandleErrorDataReceived(string data) { lock (syncObject) @@ -801,57 +811,13 @@ protected void HandleErrorDataReceived(string data) RaiseErrorHandler(new TransportErrorOccuredEventArgs(psrte, TransportMethodEnum.Unknown)); } - protected void OnExited(object sender, EventArgs e) - { - TransportMethodEnum transportMethod = TransportMethodEnum.Unknown; - lock (syncObject) - { - // There is no need to return when IsClosed==true here as in a legitimate case process exits - // after Close is called..In that legitimate case, Exit handler is removed before - // calling Exit..So, this Exit must have been called abnormally. - if (isClosed) - { - transportMethod = TransportMethodEnum.CloseShellOperationEx; - } - - // dont let the writer write new data as the process is exited. - // Not assigning null to stdInWriter to fix the race condition between OnExited() and CloseAsync() methods. - // - stdInWriter.StopWriting(); - } - - // Try to get details about why the process exited - // and if they're not available, give information as to why - string processDiagnosticMessage; - try - { - var jobProcess = (Process)sender; - processDiagnosticMessage = StringUtil.Format( - RemotingErrorIdStrings.ProcessExitInfo, - jobProcess.ExitCode, - jobProcess.StandardOutput.ReadToEnd(), - jobProcess.StandardError.ReadToEnd()); - } - catch (Exception exception) - { - processDiagnosticMessage = StringUtil.Format( - RemotingErrorIdStrings.ProcessInfoNotRecoverable, - exception.Message); - } - - string exitErrorMsg = StringUtil.Format( - RemotingErrorIdStrings.IPCServerProcessExited, - processDiagnosticMessage); - var psrte = new PSRemotingTransportException( - PSRemotingErrorId.IPCServerProcessExited, - exitErrorMsg); - RaiseErrorHandler(new TransportErrorOccuredEventArgs(psrte, transportMethod)); - } - #endregion #region Sending Data related Methods + /// + /// Send any data packet in the queue. + /// protected void SendOneItem() { DataPriorityType priorityType; @@ -888,7 +854,7 @@ private void SendData(byte[] data, DataPriorityType priorityType) return; } - stdInWriter.WriteLine(OutOfProcessUtils.CreateDataPacket(data, + _messageWriter.WriteLine(OutOfProcessUtils.CreateDataPacket(data, priorityType, Guid.Empty)); } @@ -931,13 +897,11 @@ private void OnDataPacketReceived(byte[] rawData, string stream, Guid psGuid) { // this is for a command OutOfProcessClientCommandTransportManager cmdTM = GetCommandTransportManager(psGuid); - if (cmdTM != null) - { - // not throwing the exception in null case as the command might have already - // closed. The RS data structure handler does not wait for the close ack before - // it clears the command transport manager..so this might happen. - cmdTM.OnRemoteCmdDataReceived(rawData, streamTemp); - } + + // not throwing the exception in null case as the command might have already + // closed. The RS data structure handler does not wait for the close ack before + // it clears the command transport manager..so this might happen. + cmdTM?.OnRemoteCmdDataReceived(rawData, streamTemp); } } @@ -952,13 +916,11 @@ private void OnDataAckPacketReceived(Guid psGuid) { // this is for a command OutOfProcessClientCommandTransportManager cmdTM = GetCommandTransportManager(psGuid); - if (cmdTM != null) - { - // not throwing the exception in null case as the command might have already - // closed. The RS data structure handler does not wait for the close ack before - // it clears the command transport manager..so this might happen. - cmdTM.OnRemoteCmdSendCompleted(); - } + + // not throwing the exception in null case as the command might have already + // closed. The RS data structure handler does not wait for the close ack before + // it clears the command transport manager..so this might happen. + cmdTM?.OnRemoteCmdSendCompleted(); } } @@ -986,7 +948,8 @@ private void OnCommandCreationAckReceived(Guid psGuid) private void OnSignalPacketReceived(Guid psGuid) { - throw new PSRemotingTransportException(PSRemotingErrorId.IPCUnknownElementReceived, + throw new PSRemotingTransportException( + PSRemotingErrorId.IPCUnknownElementReceived, RemotingErrorIdStrings.IPCUnknownElementReceived, OutOfProcessUtils.PS_OUT_OF_PROC_SIGNAL_TAG); } @@ -995,17 +958,15 @@ private void OnSignalAckPacketReceived(Guid psGuid) { if (psGuid == Guid.Empty) { - throw new PSRemotingTransportException(PSRemotingErrorId.IPCNoSignalForSession, + throw new PSRemotingTransportException( + PSRemotingErrorId.IPCNoSignalForSession, RemotingErrorIdStrings.IPCNoSignalForSession, OutOfProcessUtils.PS_OUT_OF_PROC_SIGNAL_ACK_TAG); } else { OutOfProcessClientCommandTransportManager cmdTM = GetCommandTransportManager(psGuid); - if (cmdTM != null) - { - cmdTM.OnRemoteCmdSignalCompleted(); - } + cmdTM?.OnRemoteCmdSignalCompleted(); } } @@ -1035,12 +996,10 @@ private void OnCloseAckReceived(Guid psGuid) _tracer.WriteMessage("OutOfProcessClientSessionTransportManager.OnCloseAckReceived, in progress command count should be greater than zero: " + commandCount + ", RunSpacePool Id : " + this.RunspacePoolInstanceId + ", psGuid : " + psGuid.ToString()); OutOfProcessClientCommandTransportManager cmdTM = GetCommandTransportManager(psGuid); - if (cmdTM != null) - { - // this might legitimately happen if cmd is already closed before we get an - // ACK back from server. - cmdTM.OnCloseCmdCompleted(); - } + + // this might legitimately happen if cmd is already closed before we get an + // ACK back from server. + cmdTM?.OnCloseCmdCompleted(); } } @@ -1055,9 +1014,71 @@ internal void OnCloseTimeOutTimerElapsed(object source) } #endregion + + #region Protected Methods + + /// + /// Standard handler for data received, to be used by custom transport implementations. + /// + /// Protocol text data received by custom transport. + protected void HandleDataReceived(string data) + { + if (data.StartsWith(OutOfProcessTextWriter.ErrorPrefix, StringComparison.OrdinalIgnoreCase)) + { + // Error message from the server. + string errorData = data.Substring(OutOfProcessTextWriter.ErrorPrefix.Length); + HandleErrorDataReceived(errorData); + } + else + { + // Normal output data. + HandleOutputDataReceived(data); + } + } + + /// + /// Creates the transport message writer from the provided TexWriter object. + /// + /// TextWriter object to be used in the message writer. + protected void SetMessageWriter(TextWriter textWriter) + { + _messageWriter = new OutOfProcessTextWriter(textWriter); + } + + /// + /// Disposes message queue components. + /// + protected void DisposeMessageQueue() + { + // Stop session processing thread. + try + { + _sessionMessageQueue.CompleteAdding(); + } + catch (ObjectDisposedException) + { + // Object already disposed. + } + + _sessionMessageQueue.Dispose(); + + // Stop command processing thread. + try + { + _commandMessageQueue.CompleteAdding(); + } + catch (ObjectDisposedException) + { + // Object already disposed. + } + + _commandMessageQueue.Dispose(); + } + + #endregion } - internal class OutOfProcessClientSessionTransportManager : OutOfProcessClientSessionTransportManagerBase + internal class OutOfProcessClientSessionTransportManager : ClientSessionTransportManagerBase { #region Private Data @@ -1085,14 +1106,14 @@ internal OutOfProcessClientSessionTransportManager(Guid runspaceId, /// /// Launch a new Process (pwsh -s) to perform remoting. This is used by *-Job cmdlets /// to support background jobs without depending on WinRM (WinRM has complex requirements like - /// elevation to support local machine remoting) + /// elevation to support local machine remoting). /// /// /// /// /// 1. There was an error in opening the associated file. /// - internal override void CreateAsync() + public override void CreateAsync() { if (_connectionInfo != null) { @@ -1133,8 +1154,8 @@ internal override void CreateAsync() _processInstance.Start(); StartRedirectionReaderThreads(_serverProcess); - stdInWriter = new OutOfProcessTextWriter(_serverProcess.StandardInput); - _processInstance.StdInWriter = stdInWriter; + SetMessageWriter(_serverProcess.StandardInput); + _processInstance.StdInWriter = _messageWriter; } } catch (System.ComponentModel.Win32Exception w32e) @@ -1244,7 +1265,7 @@ private void ProcessErrorData(object arg) /// Kills the server process and disposes other resources. /// /// - internal override void Dispose(bool isDisposing) + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); if (isDisposing) @@ -1308,10 +1329,57 @@ private void KillServerProcess() } } + private void OnExited(object sender, EventArgs e) + { + TransportMethodEnum transportMethod = TransportMethodEnum.Unknown; + lock (syncObject) + { + // There is no need to return when IsClosed==true here as in a legitimate case process exits + // after Close is called..In that legitimate case, Exit handler is removed before + // calling Exit..So, this Exit must have been called abnormally. + if (isClosed) + { + transportMethod = TransportMethodEnum.CloseShellOperationEx; + } + + // dont let the writer write new data as the process is exited. + // Not assigning null to stdInWriter to fix the race condition between OnExited() and CloseAsync() methods. + // + _messageWriter.StopWriting(); + } + + // Try to get details about why the process exited + // and if they're not available, give information as to why + string processDiagnosticMessage; + try + { + var jobProcess = (Process)sender; + processDiagnosticMessage = StringUtil.Format( + RemotingErrorIdStrings.ProcessExitInfo, + jobProcess.ExitCode, + jobProcess.StandardOutput.ReadToEnd(), + jobProcess.StandardError.ReadToEnd()); + } + catch (Exception exception) + { + processDiagnosticMessage = StringUtil.Format( + RemotingErrorIdStrings.ProcessInfoNotRecoverable, + exception.Message); + } + + string exitErrorMsg = StringUtil.Format( + RemotingErrorIdStrings.IPCServerProcessExited, + processDiagnosticMessage); + var psrte = new PSRemotingTransportException( + PSRemotingErrorId.IPCServerProcessExited, + exitErrorMsg); + RaiseErrorHandler(new TransportErrorOccuredEventArgs(psrte, transportMethod)); + } + #endregion } - internal abstract class HyperVSocketClientSessionTransportManagerBase : OutOfProcessClientSessionTransportManagerBase + internal abstract class HyperVSocketClientSessionTransportManagerBase : ClientSessionTransportManagerBase { #region Data @@ -1333,16 +1401,13 @@ internal HyperVSocketClientSessionTransportManagerBase( #region Overrides - internal override void Dispose(bool isDisposing) + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); if (isDisposing) { - if (_client != null) - { - _client.Dispose(); - } + _client?.Dispose(); } } @@ -1411,10 +1476,10 @@ protected void ProcessReaderThread(object state) { if (e is ArgumentOutOfRangeException) { - Dbg.Assert(false, "Need to adjust transport fragmentor to accomodate read buffer size."); + Dbg.Assert(false, "Need to adjust transport fragmentor to accommodate read buffer size."); } - string errorMsg = (e.Message != null) ? e.Message : string.Empty; + string errorMsg = e.Message ?? string.Empty; _tracer.WriteMessage("HyperVSocketClientSessionTransportManager", "StartReaderThread", Guid.Empty, "Transport manager reader thread ended with error: {0}", errorMsg); @@ -1477,7 +1542,7 @@ internal VMHyperVSocketClientSessionTransportManager( /// Create a Hyper-V socket connection to the target process and set up /// transport reader/writer. /// - internal override void CreateAsync() + public override void CreateAsync() { _client = new RemoteSessionHyperVSocketClient(_vmGuid, true); if (!_client.Connect(_networkCredential, _configurationName, true)) @@ -1506,7 +1571,7 @@ internal override void CreateAsync() } // Create writer for Hyper-V socket. - stdInWriter = new OutOfProcessTextWriter(_client.TextWriter); + SetMessageWriter(_client.TextWriter); // Create reader thread for Hyper-V socket. StartReaderThread(_client.TextReader); @@ -1550,7 +1615,7 @@ internal ContainerHyperVSocketClientSessionTransportManager( /// Create a Hyper-V socket connection to the target process and set up /// transport reader/writer. /// - internal override void CreateAsync() + public override void CreateAsync() { _client = new RemoteSessionHyperVSocketClient(_targetGuid, false, true); if (!_client.Connect(null, string.Empty, false)) @@ -1565,7 +1630,7 @@ internal override void CreateAsync() } // Create writer for Hyper-V socket. - stdInWriter = new OutOfProcessTextWriter(_client.TextWriter); + SetMessageWriter(_client.TextWriter); // Create reader thread for Hyper-V socket. StartReaderThread(_client.TextReader); @@ -1574,7 +1639,7 @@ internal override void CreateAsync() #endregion } - internal sealed class SSHClientSessionTransportManager : OutOfProcessClientSessionTransportManagerBase + internal sealed class SSHClientSessionTransportManager : ClientSessionTransportManagerBase { #region Data @@ -1584,6 +1649,7 @@ internal sealed class SSHClientSessionTransportManager : OutOfProcessClientSessi private StreamReader _stdOutReader; private StreamReader _stdErrReader; private bool _connectionEstablished; + private Timer _connectionTimer; private const string _threadName = "SSHTransport Reader Thread"; @@ -1606,7 +1672,7 @@ internal SSHClientSessionTransportManager( #region Overrides - internal override void Dispose(bool isDisposing) + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -1625,7 +1691,7 @@ protected override void CleanupConnection() /// Create an SSH connection to the target host and set up /// transport reader/writer. /// - internal override void CreateAsync() + public override void CreateAsync() { // Create the ssh client process with connection to host target. _sshProcessId = _connectionInfo.StartSSHProcess( @@ -1637,13 +1703,56 @@ internal override void CreateAsync() StartErrorThread(_stdErrReader); // Create writer for named pipe. - stdInWriter = new OutOfProcessTextWriter(_stdInWriter); + SetMessageWriter(_stdInWriter); // Create reader thread and send first PSRP message. StartReaderThread(_stdOutReader); + + if (_connectionInfo.ConnectingTimeout < 0) + { + return; + } + + // Start connection timeout timer if requested. + // Timer callback occurs only once after timeout time. + _connectionTimer = new Timer( + callback: (_) => + { + if (_connectionEstablished) + { + return; + } + + // Detect if SSH client process terminates prematurely. + bool sshTerminated = false; + try + { + using (var sshProcess = System.Diagnostics.Process.GetProcessById(_sshProcessId)) + { + sshTerminated = sshProcess == null || sshProcess.Handle == IntPtr.Zero || sshProcess.HasExited; + } + } + catch + { + sshTerminated = true; + } + + var errorMessage = StringUtil.Format(RemotingErrorIdStrings.SSHClientConnectTimeout, _connectionInfo.ConnectingTimeout / 1000); + if (sshTerminated) + { + errorMessage += RemotingErrorIdStrings.SSHClientConnectProcessTerminated; + } + + // Report error and terminate connection attempt. + HandleSSHError( + new PSRemotingTransportException(errorMessage)); + }, + state: null, + dueTime: _connectionInfo.ConnectingTimeout, + period: Timeout.Infinite); } - internal override void CloseAsync() + public override void CloseAsync() { base.CloseAsync(); @@ -1660,14 +1769,20 @@ internal override void CloseAsync() private void CloseConnection() { + // Ensure message queue is disposed. + DisposeMessageQueue(); + + var connectionTimer = Interlocked.Exchange(ref _connectionTimer, null); + connectionTimer?.Dispose(); + var stdInWriter = Interlocked.Exchange(ref _stdInWriter, null); - if (stdInWriter != null) { stdInWriter.Dispose(); } + stdInWriter?.Dispose(); var stdOutReader = Interlocked.Exchange(ref _stdOutReader, null); - if (stdOutReader != null) { stdOutReader.Dispose(); } + stdOutReader?.Dispose(); var stdErrReader = Interlocked.Exchange(ref _stdErrReader, null); - if (stdErrReader != null) { stdErrReader.Dispose(); } + stdErrReader?.Dispose(); // The CloseConnection() method can be called multiple times from multiple places. // Set the _sshProcessId to zero here so that we go through the work of finding @@ -1677,11 +1792,7 @@ private void CloseConnection() { try { - var sshProcess = System.Diagnostics.Process.GetProcessById(sshProcessId); - if ((sshProcess != null) && (sshProcess.Handle != IntPtr.Zero) && !sshProcess.HasExited) - { - sshProcess.Kill(); - } + _connectionInfo.KillSSHProcess(sshProcessId); } catch (ArgumentException) { } catch (InvalidOperationException) { } @@ -1708,7 +1819,16 @@ private void ProcessErrorThread(object state) while (true) { - string error = ReadError(reader); + string error; + + // Blocking read from StdError stream + error = reader.ReadLine(); + + if (error == null) + { + // Stream is closed unexpectedly. + throw new PSInvalidOperationException(RemotingErrorIdStrings.SSHAbruptlyTerminated); + } if (error.Length == 0) { @@ -1716,12 +1836,15 @@ private void ProcessErrorThread(object state) continue; } - // Any SSH client error results in a broken session. - PSRemotingTransportException psrte = new PSRemotingTransportException( - PSRemotingErrorId.IPCServerProcessReportedError, - RemotingErrorIdStrings.IPCServerProcessReportedError, - StringUtil.Format(RemotingErrorIdStrings.SSHClientEndWithErrorMessage, error)); - HandleSSHError(psrte); + try + { + // 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); + } + catch (IOException) + { } } } catch (ObjectDisposedException) @@ -1730,7 +1853,7 @@ private void ProcessErrorThread(object state) } catch (Exception e) { - string errorMsg = (e.Message != null) ? e.Message : string.Empty; + string errorMsg = e.Message ?? string.Empty; _tracer.WriteMessage("SSHClientSessionTransportManager", "ProcessErrorThread", Guid.Empty, "Transport manager error thread ended with error: {0}", errorMsg); @@ -1747,55 +1870,6 @@ private void HandleSSHError(PSRemotingTransportException psrte) CloseConnection(); } - private static string ReadError(StreamReader reader) - { - // Blocking read from StdError stream - string error = reader.ReadLine(); - - if (error == null) - { - // Stream is closed unexpectedly. - throw new PSInvalidOperationException(RemotingErrorIdStrings.SSHAbruptlyTerminated); - } - - if ((error.Length == 0) || - error.Contains("WARNING:", StringComparison.OrdinalIgnoreCase)) - { - // Handle as interactive warning message - Console.WriteLine(error); - return string.Empty; - } - - // SSH may return a multi-line error message. - // The StdError pipe stream is open ended causing StreamReader read operations to block - // if there is no incoming data. Since we don't know how many error message lines there - // will be we use an asynchronous read with timeout to prevent blocking indefinitely. - System.Text.StringBuilder sb = new Text.StringBuilder(error); - var running = true; - while (running) - { - try - { - var task = reader.ReadLineAsync(); - if (task.Wait(1000) && (task.Result != null)) - { - sb.Append(Environment.NewLine); - sb.Append(task.Result); - } - else - { - running = false; - } - } - catch (Exception) - { - running = false; - } - } - - return sb.ToString(); - } - private void StartReaderThread( StreamReader reader) { @@ -1827,10 +1901,10 @@ private void ProcessReaderThread(object state) break; } - if (data.StartsWith(System.Management.Automation.Remoting.Server.NamedPipeErrorTextWriter.ErrorPrepend, StringComparison.OrdinalIgnoreCase)) + if (data.StartsWith(System.Management.Automation.Remoting.Server.FormattedErrorTextWriter.ErrorPrefix, StringComparison.OrdinalIgnoreCase)) { // Error message from the server. - string errorData = data.Substring(System.Management.Automation.Remoting.Server.NamedPipeErrorTextWriter.ErrorPrepend.Length); + string errorData = data.Substring(System.Management.Automation.Remoting.Server.FormattedErrorTextWriter.ErrorPrefix.Length); HandleErrorDataReceived(errorData); } else @@ -1851,10 +1925,10 @@ private void ProcessReaderThread(object state) { if (e is ArgumentOutOfRangeException) { - Dbg.Assert(false, "Need to adjust transport fragmentor to accomodate read buffer size."); + Dbg.Assert(false, "Need to adjust transport fragmentor to accommodate read buffer size."); } - string errorMsg = (e.Message != null) ? e.Message : string.Empty; + string errorMsg = e.Message ?? string.Empty; _tracer.WriteMessage("SSHClientSessionTransportManager", "ProcessReaderThread", Guid.Empty, "Transport manager reader thread ended with error: {0}", errorMsg); } @@ -1863,7 +1937,7 @@ private void ProcessReaderThread(object state) #endregion } - internal abstract class NamedPipeClientSessionTransportManagerBase : OutOfProcessClientSessionTransportManagerBase + internal abstract class NamedPipeClientSessionTransportManagerBase : ClientSessionTransportManagerBase { #region Data @@ -1896,16 +1970,13 @@ internal NamedPipeClientSessionTransportManagerBase( #region Overrides - internal override void Dispose(bool isDisposing) + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); if (isDisposing) { - if (_clientPipe != null) - { - _clientPipe.Dispose(); - } + _clientPipe?.Dispose(); } } @@ -1957,17 +2028,7 @@ private void ProcessReaderThread(object state) break; } - if (data.StartsWith(System.Management.Automation.Remoting.Server.NamedPipeErrorTextWriter.ErrorPrepend, StringComparison.OrdinalIgnoreCase)) - { - // Error message from the server. - string errorData = data.Substring(System.Management.Automation.Remoting.Server.NamedPipeErrorTextWriter.ErrorPrepend.Length); - HandleErrorDataReceived(errorData); - } - else - { - // Normal output data. - HandleOutputDataReceived(data); - } + HandleDataReceived(data); } } catch (ObjectDisposedException) @@ -1981,7 +2042,7 @@ private void ProcessReaderThread(object state) Dbg.Assert(false, "Need to adjust transport fragmentor to accommodate read buffer size."); } - string errorMsg = (e.Message != null) ? e.Message : string.Empty; + string errorMsg = e.Message ?? string.Empty; _tracer.WriteMessage("NamedPipeClientSessionTransportManager", "StartReaderThread", Guid.Empty, "Transport manager reader thread ended with error: {0}", errorMsg); } @@ -2024,7 +2085,7 @@ internal NamedPipeClientSessionTransportManager( /// Create a named pipe connection to the target process and set up /// transport reader/writer. /// - internal override void CreateAsync() + public override void CreateAsync() { _clientPipe = string.IsNullOrEmpty(_connectionInfo.CustomPipeName) ? new RemoteSessionNamedPipeClient(_connectionInfo.ProcessId, _connectionInfo.AppDomainName) : @@ -2034,7 +2095,7 @@ internal override void CreateAsync() _clientPipe.Connect(_connectionInfo.OpenTimeout); // Create writer for named pipe. - stdInWriter = new OutOfProcessTextWriter(_clientPipe.TextWriter); + SetMessageWriter(_clientPipe.TextWriter); // Create reader thread for named pipe. StartReaderThread(_clientPipe.TextReader); @@ -2047,13 +2108,7 @@ internal override void CreateAsync() /// /// Aborts an existing connection attempt. /// - public void AbortConnect() - { - if (_clientPipe != null) - { - _clientPipe.AbortConnect(); - } - } + public void AbortConnect() => _clientPipe?.AbortConnect(); #endregion } @@ -2092,7 +2147,7 @@ internal ContainerNamedPipeClientSessionTransportManager( /// Create a named pipe connection to the target process in target container and set up /// transport reader/writer. /// - internal override void CreateAsync() + public override void CreateAsync() { _clientPipe = new ContainerSessionNamedPipeClient( _connectionInfo.ContainerProc.ProcessId, @@ -2103,7 +2158,7 @@ internal override void CreateAsync() _clientPipe.Connect(_connectionInfo.OpenTimeout); // Create writer for named pipe. - stdInWriter = new OutOfProcessTextWriter(_clientPipe.TextWriter); + SetMessageWriter(_clientPipe.TextWriter); // Create reader thread for named pipe. StartReaderThread(_clientPipe.TextReader); @@ -2142,7 +2197,7 @@ internal class OutOfProcessClientCommandTransportManager : BaseClientCommandTran internal OutOfProcessClientCommandTransportManager( ClientRemotePowerShell cmd, bool noInput, - OutOfProcessClientSessionTransportManagerBase sessnTM, + ClientSessionTransportManagerBase sessnTM, OutOfProcessTextWriter stdInWriter) : base(cmd, sessnTM.CryptoHelper, sessnTM) { _stdInWriter = stdInWriter; @@ -2160,7 +2215,7 @@ internal override void ConnectAsync() throw new NotImplementedException(RemotingErrorIdStrings.IPCTransportConnectError); } - internal override void CreateAsync() + public override void CreateAsync() { PSEtwLog.LogAnalyticInformational(PSEventId.WSManCreateCommand, PSOpcode.Connect, PSTask.CreateRunspace, @@ -2170,7 +2225,7 @@ internal override void CreateAsync() _stdInWriter.WriteLine(OutOfProcessUtils.CreateCommandPacket(powershellInstanceId)); } - internal override void CloseAsync() + public override void CloseAsync() { lock (syncObject) { @@ -2224,7 +2279,7 @@ internal override void SendStopSignal() _signalTimeOutTimer.Change(60 * 1000, Timeout.Infinite); } - internal override void Dispose(bool isDisposing) + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); if (isDisposing) @@ -2449,6 +2504,12 @@ internal OutOfProcessServerSessionTransportManager(OutOfProcessTextWriter outWri _stdOutWriter = outWriter; _stdErrWriter = errWriter; _cmdTransportManagers = new Dictionary(); + + this.WSManTransportErrorOccured += (object sender, TransportErrorOccuredEventArgs e) => + { + string msg = e.Exception.TransportMessage ?? e.Exception.InnerException?.Message ?? string.Empty; + _stdErrWriter.WriteLine(StringUtil.Format(RemotingErrorIdStrings.RemoteTransportError, msg)); + }; } #endregion diff --git a/src/System.Management.Automation/engine/remoting/fanin/PSPrincipal.cs b/src/System.Management.Automation/engine/remoting/fanin/PSPrincipal.cs index 1341c9e0a83..b6a3f212b33 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/PSPrincipal.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/PSPrincipal.cs @@ -18,11 +18,9 @@ namespace System.Management.Automation.Remoting /// /// This class is used in the server side remoting scenarios. This class /// holds information about the incoming connection like: - /// (a) Client's TimeZone - /// (b) Connecting User information - /// (c) Connection String used by the user to connect to the server. + /// (a) Connecting User information + /// (b) Connection String used by the user to connect to the server. /// - [Serializable] public sealed class PSSenderInfo : ISerializable { #region Private Data @@ -81,8 +79,6 @@ private PSSenderInfo(SerializationInfo info, StreamingContext context) UserInfo = senderInfo.UserInfo; ConnectionString = senderInfo.ConnectionString; _applicationArguments = senderInfo._applicationArguments; - - ClientTimeZone = senderInfo.ClientTimeZone; } catch (Exception) { @@ -129,11 +125,7 @@ public PSPrincipal UserInfo /// /// Contains the TimeZone information from the client machine. /// - public TimeZoneInfo ClientTimeZone - { - get; - internal set; - } + public TimeZoneInfo ClientTimeZone => null; /// /// Connection string used by the client to connect to the server. This is diff --git a/src/System.Management.Automation/engine/remoting/fanin/PSSessionConfigurationData.cs b/src/System.Management.Automation/engine/remoting/fanin/PSSessionConfigurationData.cs index 6da557cca40..1d5f6916981 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/PSSessionConfigurationData.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/PSSessionConfigurationData.cs @@ -80,7 +80,10 @@ internal static PSSessionConfigurationData Create(string configurationData) { PSSessionConfigurationData configuration = new PSSessionConfigurationData(); - if (string.IsNullOrEmpty(configurationData)) return configuration; + if (string.IsNullOrEmpty(configurationData)) + { + return configuration; + } configurationData = Unescape(configurationData); @@ -224,8 +227,8 @@ private void Update(string optionName, string optionValue) private void CreateCollectionIfNecessary() { - if (_modulesToImport == null) _modulesToImport = new List(); - if (_modulesToImportInternal == null) _modulesToImportInternal = new List(); + _modulesToImport ??= new List(); + _modulesToImportInternal ??= new List(); } private const string SessionConfigToken = "SessionConfigurationData"; diff --git a/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs b/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs index 7baebdc98ae..4d8535ca4a9 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs @@ -98,7 +98,7 @@ internal Fragmentor Fragmentor Dbg.Assert(value != null, "Fragmentor cannot be null."); _fragmentor = value; // create serialized streams using fragment size. - string[] names = Enum.GetNames(typeof(DataPriorityType)); + string[] names = Enum.GetNames(); _dataToBeSent = new SerializedDataStream[names.Length]; _dataSyncObjects = new object[names.Length]; for (int i = 0; i < names.Length; i++) @@ -220,21 +220,29 @@ internal byte[] ReadOrRegisterCallback(OnDataAvailableCallback callback, lock (_readSyncObject) { priorityType = DataPriorityType.Default; - - // send data from which ever stream that has data directly. + // Send data from which ever stream that has data directly. byte[] result = null; - result = _dataToBeSent[(int)DataPriorityType.PromptResponse].ReadOrRegisterCallback(_onSendCollectionDataAvailable); - priorityType = DataPriorityType.PromptResponse; + SerializedDataStream promptDataToBeSent = _dataToBeSent[(int)DataPriorityType.PromptResponse]; + if (promptDataToBeSent is not null) + { + result = promptDataToBeSent.ReadOrRegisterCallback(_onSendCollectionDataAvailable); + priorityType = DataPriorityType.PromptResponse; + } if (result == null) { - result = _dataToBeSent[(int)DataPriorityType.Default].ReadOrRegisterCallback(_onSendCollectionDataAvailable); - priorityType = DataPriorityType.Default; + SerializedDataStream defaultDataToBeSent = _dataToBeSent[(int)DataPriorityType.Default]; + if (defaultDataToBeSent is not null) + { + result = defaultDataToBeSent.ReadOrRegisterCallback(_onSendCollectionDataAvailable); + priorityType = DataPriorityType.Default; + } } - // no data to return..so register the callback. + + // No data to return..so register the callback. if (result == null) { - // register callback. + // Register callback. _onDataAvailableCallback = callback; } @@ -293,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 @@ -676,10 +684,7 @@ internal void ProcessRawData(byte[] data, OnDataAvailableCallback callback) private void ResetReceiveData() { // reset resources used to store incoming data (for a single object) - if (_dataToProcessStream != null) - { - _dataToProcessStream.Dispose(); - } + _dataToProcessStream?.Dispose(); _currentObjectId = 0; _currentFrgId = 0; @@ -760,7 +765,7 @@ internal class PriorityReceiveDataCollection : IDisposable internal PriorityReceiveDataCollection(Fragmentor defragmentor, bool createdByClientTM) { _defragmentor = defragmentor; - string[] names = Enum.GetNames(typeof(DataPriorityType)); + string[] names = Enum.GetNames(); _recvdData = new ReceiveDataCollection[names.Length]; for (int index = 0; index < names.Length; index++) { diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManNativeAPI.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManNativeAPI.cs index 6dd3614a62f..289f88c576f 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManNativeAPI.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManNativeAPI.cs @@ -1404,7 +1404,7 @@ internal struct WSManShellStartupInfoStruct /// /// Managed to unmanaged representation of WSMAN_SHELL_STARTUP_INFO. /// It converts managed values into an unmanaged compatible WSManShellStartupInfoStruct that - /// is marshaled into unmanaged memory. + /// is marshalled into unmanaged memory. /// internal struct WSManShellStartupInfo_ManToUn : IDisposable { @@ -2006,13 +2006,13 @@ private struct WSManBinaryDataInternal internal class WSManPluginRequest { /// - /// Unmarshaled WSMAN_SENDER_DETAILS struct. + /// Unmarshalled WSMAN_SENDER_DETAILS struct. /// internal WSManSenderDetails senderDetails; internal string locale; internal string resourceUri; /// - /// Unmarshaled WSMAN_OPERATION_INFO struct. + /// Unmarshalled WSMAN_OPERATION_INFO struct. /// internal WSManOperationInfo operationInfo; @@ -2100,7 +2100,7 @@ internal class WSManSenderDetails internal string senderName; internal string authenticationMechanism; internal WSManCertificateDetails certificateDetails; - internal IntPtr clientToken; // TODO: How should this be marshaled????? + internal IntPtr clientToken; // TODO: How should this be marshalled????? internal string httpUrl; /// diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManPlugin.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManPlugin.cs index 9382beb9b31..79d583e063f 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManPlugin.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManPlugin.cs @@ -306,10 +306,16 @@ internal void CreateShell( PSOpcode.Connect, PSTask.None, PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, requestDetails.ToString(), senderInfo.UserInfo.Identity.Name, requestDetails.resourceUri); - ServerRemoteSession remoteShellSession = ServerRemoteSession.CreateServerRemoteSession(senderInfo, - requestDetails.resourceUri, - extraInfo, - serverTransportMgr); + + ServerRemoteSession remoteShellSession = ServerRemoteSession.CreateServerRemoteSession( + senderInfo: senderInfo, + configurationProviderId: requestDetails.resourceUri, + initializationParameters: extraInfo, + transportManager: serverTransportMgr, + initialCommand: null, // Not used by WinRM endpoint. + configurationName: null, // Not used by WinRM endpoint, which has its own configuration. + configurationFile: null, // Same. + initialLocation: null); // Same. if (remoteShellSession == null) { diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs index e2679fcdf44..7fbc236a4dd 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs @@ -33,7 +33,7 @@ namespace System.Management.Automation.Remoting /// PCWSTR. /// WSMAN_SHELL_STARTUP_INFO*. /// WSMAN_DATA*. - internal delegate void WSMPluginShellDelegate( // TODO: Rename to WSManPluginShellDelegate once I remove the MC++ module. + internal delegate void WSManPluginShellDelegate( IntPtr pluginContext, IntPtr requestDetails, int flags, @@ -45,7 +45,7 @@ internal delegate void WSMPluginShellDelegate( // TODO: Rename to WSManPluginShe /// /// PVOID. /// PVOID. - internal delegate void WSMPluginReleaseShellContextDelegate( + internal delegate void WSManPluginReleaseShellContextDelegate( IntPtr pluginContext, IntPtr shellContext); @@ -57,7 +57,7 @@ internal delegate void WSMPluginReleaseShellContextDelegate( /// PVOID. /// PVOID optional. /// WSMAN_DATA* optional. - internal delegate void WSMPluginConnectDelegate( + internal delegate void WSManPluginConnectDelegate( IntPtr pluginContext, IntPtr requestDetails, int flags, @@ -73,7 +73,7 @@ internal delegate void WSMPluginConnectDelegate( /// PVOID. /// PCWSTR. /// WSMAN_COMMAND_ARG_SET*. - internal delegate void WSMPluginCommandDelegate( + internal delegate void WSManPluginCommandDelegate( IntPtr pluginContext, IntPtr requestDetails, int flags, @@ -85,7 +85,7 @@ internal delegate void WSMPluginCommandDelegate( /// Delegate that is passed to native layer for callback on operation shutdown notifications. /// /// IntPtr. - internal delegate void WSMPluginOperationShutdownDelegate( + internal delegate void WSManPluginOperationShutdownDelegate( IntPtr shutdownContext); /// @@ -93,7 +93,7 @@ internal delegate void WSMPluginOperationShutdownDelegate( /// PVOID. /// PVOID. /// PVOID. - internal delegate void WSMPluginReleaseCommandContextDelegate( + internal delegate void WSManPluginReleaseCommandContextDelegate( IntPtr pluginContext, IntPtr shellContext, IntPtr commandContext); @@ -107,7 +107,7 @@ internal delegate void WSMPluginReleaseCommandContextDelegate( /// PVOID. /// PCWSTR. /// WSMAN_DATA*. - internal delegate void WSMPluginSendDelegate( + internal delegate void WSManPluginSendDelegate( IntPtr pluginContext, IntPtr requestDetails, int flags, @@ -124,7 +124,7 @@ internal delegate void WSMPluginSendDelegate( /// PVOID. /// PVOID optional. /// WSMAN_STREAM_ID_SET* optional. - internal delegate void WSMPluginReceiveDelegate( + internal delegate void WSManPluginReceiveDelegate( IntPtr pluginContext, IntPtr requestDetails, int flags, @@ -140,7 +140,7 @@ internal delegate void WSMPluginReceiveDelegate( /// PVOID. /// PVOID optional. /// PCWSTR. - internal delegate void WSMPluginSignalDelegate( + internal delegate void WSManPluginSignalDelegate( IntPtr pluginContext, IntPtr requestDetails, int flags, @@ -160,7 +160,7 @@ internal delegate void WaitOrTimerCallbackDelegate( /// /// /// PVOID. - internal delegate void WSMShutdownPluginDelegate( + internal delegate void WSManShutdownPluginDelegate( IntPtr pluginContext); /// @@ -192,7 +192,7 @@ internal WSManPluginEntryDelegatesInternal UnmanagedStruct private GCHandle _pluginSignalGCHandle; private GCHandle _pluginConnectGCHandle; private GCHandle _shutdownPluginGCHandle; - private GCHandle _WSMPluginOperationShutdownGCHandle; + private GCHandle _WSManPluginOperationShutdownGCHandle; #endregion @@ -255,57 +255,57 @@ private void populateDelegates() // disposal. Using GCHandle without pinning reduces fragmentation potential // of the managed heap. { - WSMPluginShellDelegate pluginShell = new WSMPluginShellDelegate(WSManPluginManagedEntryWrapper.WSManPluginShell); + WSManPluginShellDelegate pluginShell = new WSManPluginShellDelegate(WSManPluginManagedEntryWrapper.WSManPluginShell); _pluginShellGCHandle = GCHandle.Alloc(pluginShell); // marshal the delegate to a unmanaged function pointer so that AppDomain reference is stored correctly. // Populate the outgoing structure so the caller has access to the entry points _unmanagedStruct.wsManPluginShellCallbackNative = Marshal.GetFunctionPointerForDelegate(pluginShell); } { - WSMPluginReleaseShellContextDelegate pluginReleaseShellContext = new WSMPluginReleaseShellContextDelegate(WSManPluginManagedEntryWrapper.WSManPluginReleaseShellContext); + WSManPluginReleaseShellContextDelegate pluginReleaseShellContext = new WSManPluginReleaseShellContextDelegate(WSManPluginManagedEntryWrapper.WSManPluginReleaseShellContext); _pluginReleaseShellContextGCHandle = GCHandle.Alloc(pluginReleaseShellContext); _unmanagedStruct.wsManPluginReleaseShellContextCallbackNative = Marshal.GetFunctionPointerForDelegate(pluginReleaseShellContext); } { - WSMPluginCommandDelegate pluginCommand = new WSMPluginCommandDelegate(WSManPluginManagedEntryWrapper.WSManPluginCommand); + WSManPluginCommandDelegate pluginCommand = new WSManPluginCommandDelegate(WSManPluginManagedEntryWrapper.WSManPluginCommand); _pluginCommandGCHandle = GCHandle.Alloc(pluginCommand); _unmanagedStruct.wsManPluginCommandCallbackNative = Marshal.GetFunctionPointerForDelegate(pluginCommand); } { - WSMPluginReleaseCommandContextDelegate pluginReleaseCommandContext = new WSMPluginReleaseCommandContextDelegate(WSManPluginManagedEntryWrapper.WSManPluginReleaseCommandContext); + WSManPluginReleaseCommandContextDelegate pluginReleaseCommandContext = new WSManPluginReleaseCommandContextDelegate(WSManPluginManagedEntryWrapper.WSManPluginReleaseCommandContext); _pluginReleaseCommandContextGCHandle = GCHandle.Alloc(pluginReleaseCommandContext); _unmanagedStruct.wsManPluginReleaseCommandContextCallbackNative = Marshal.GetFunctionPointerForDelegate(pluginReleaseCommandContext); } { - WSMPluginSendDelegate pluginSend = new WSMPluginSendDelegate(WSManPluginManagedEntryWrapper.WSManPluginSend); + WSManPluginSendDelegate pluginSend = new WSManPluginSendDelegate(WSManPluginManagedEntryWrapper.WSManPluginSend); _pluginSendGCHandle = GCHandle.Alloc(pluginSend); _unmanagedStruct.wsManPluginSendCallbackNative = Marshal.GetFunctionPointerForDelegate(pluginSend); } { - WSMPluginReceiveDelegate pluginReceive = new WSMPluginReceiveDelegate(WSManPluginManagedEntryWrapper.WSManPluginReceive); + WSManPluginReceiveDelegate pluginReceive = new WSManPluginReceiveDelegate(WSManPluginManagedEntryWrapper.WSManPluginReceive); _pluginReceiveGCHandle = GCHandle.Alloc(pluginReceive); _unmanagedStruct.wsManPluginReceiveCallbackNative = Marshal.GetFunctionPointerForDelegate(pluginReceive); } { - WSMPluginSignalDelegate pluginSignal = new WSMPluginSignalDelegate(WSManPluginManagedEntryWrapper.WSManPluginSignal); + WSManPluginSignalDelegate pluginSignal = new WSManPluginSignalDelegate(WSManPluginManagedEntryWrapper.WSManPluginSignal); _pluginSignalGCHandle = GCHandle.Alloc(pluginSignal); _unmanagedStruct.wsManPluginSignalCallbackNative = Marshal.GetFunctionPointerForDelegate(pluginSignal); } { - WSMPluginConnectDelegate pluginConnect = new WSMPluginConnectDelegate(WSManPluginManagedEntryWrapper.WSManPluginConnect); + WSManPluginConnectDelegate pluginConnect = new WSManPluginConnectDelegate(WSManPluginManagedEntryWrapper.WSManPluginConnect); _pluginConnectGCHandle = GCHandle.Alloc(pluginConnect); _unmanagedStruct.wsManPluginConnectCallbackNative = Marshal.GetFunctionPointerForDelegate(pluginConnect); } { - WSMShutdownPluginDelegate shutdownPlugin = new WSMShutdownPluginDelegate(WSManPluginManagedEntryWrapper.ShutdownPlugin); + WSManShutdownPluginDelegate shutdownPlugin = new WSManShutdownPluginDelegate(WSManPluginManagedEntryWrapper.ShutdownPlugin); _shutdownPluginGCHandle = GCHandle.Alloc(shutdownPlugin); _unmanagedStruct.wsManPluginShutdownPluginCallbackNative = Marshal.GetFunctionPointerForDelegate(shutdownPlugin); } if (!Platform.IsWindows) { - WSMPluginOperationShutdownDelegate pluginShutDownDelegate = new WSMPluginOperationShutdownDelegate(WSManPluginManagedEntryWrapper.WSManPSShutdown); - _WSMPluginOperationShutdownGCHandle = GCHandle.Alloc(pluginShutDownDelegate); + WSManPluginOperationShutdownDelegate pluginShutDownDelegate = new WSManPluginOperationShutdownDelegate(WSManPluginManagedEntryWrapper.WSManPSShutdown); + _WSManPluginOperationShutdownGCHandle = GCHandle.Alloc(pluginShutDownDelegate); _unmanagedStruct.wsManPluginShutdownCallbackNative = Marshal.GetFunctionPointerForDelegate(pluginShutDownDelegate); } } @@ -328,7 +328,7 @@ private void CleanUpDelegates() _shutdownPluginGCHandle.Free(); if (!Platform.IsWindows) { - _WSMPluginOperationShutdownGCHandle.Free(); + _WSManPluginOperationShutdownGCHandle.Free(); } } } @@ -446,11 +446,7 @@ public static void ShutdownPlugin( IntPtr pluginContext) { WSManPluginInstance.PerformShutdown(pluginContext); - - if (workerPtrs != null) - { - workerPtrs.Dispose(); - } + workerPtrs?.Dispose(); } /// diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManPluginShellSession.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginShellSession.cs index 51635922e0b..54a7b66d0c1 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManPluginShellSession.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginShellSession.cs @@ -468,11 +468,12 @@ internal override void ExecuteConnect( _remoteSession.ExecuteConnect(inputData, out outputData); // construct Xml to send back - string responseData = string.Format(System.Globalization.CultureInfo.InvariantCulture, - "<{0} xmlns=\"{1}\">{2}", - WSManNativeApi.PS_CONNECTRESPONSE_XML_TAG, - WSManNativeApi.PS_XML_NAMESPACE, - Convert.ToBase64String(outputData)); + string responseData = string.Format( + System.Globalization.CultureInfo.InvariantCulture, + "<{0} xmlns=\"{1}\">{2}", + WSManNativeApi.PS_CONNECTRESPONSE_XML_TAG, + WSManNativeApi.PS_XML_NAMESPACE, + Convert.ToBase64String(outputData)); // TODO: currently using OperationComplete to report back the responseXml. This will need to change to use WSManReportObject // that is currently internal. @@ -769,7 +770,7 @@ internal bool ProcessArguments( internal void Stop( WSManNativeApi.WSManPluginRequest requestDetails) { - // stop the command..command will be stoped if we raise ClosingEvent on + // stop the command..command will be stopped if we raise ClosingEvent on // transport manager. transportMgr.PerformStop(); WSManPluginInstance.ReportWSManOperationComplete(requestDetails, null); diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs index 99b2d4848bd..6ff9187d3bf 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs @@ -202,8 +202,7 @@ internal static string ParseEscapeWSManErrorMessage(string errorMessage) Collection tokens = PSParser.Tokenize(errorMessage, out parserErrors); if (parserErrors.Count > 0) { - tracer.WriteLine(string.Format(CultureInfo.InvariantCulture, - "There were errors parsing string '{0}'", errorMessage); + tracer.WriteLine(string.Create(CultureInfo.InvariantCulture, $"There were errors parsing string '{errorMessage}'"); return errorMessage; } @@ -305,7 +304,7 @@ private enum CompletionNotification #region CompletionEventArgs - private class CompletionEventArgs : EventArgs + private sealed class CompletionEventArgs : EventArgs { internal CompletionEventArgs(CompletionNotification notification) { @@ -926,7 +925,9 @@ internal override void ConnectAsync() { // WSMan expects the data to be in XML format (which is text + xml tags) // so convert byte[] into base64 encoded format - string base64EncodedDataInXml = string.Format(CultureInfo.InvariantCulture, "<{0} xmlns=\"{1}\">{2}", + string base64EncodedDataInXml = string.Format( + CultureInfo.InvariantCulture, + "<{0} xmlns=\"{1}\">{2}", WSManNativeApi.PS_CONNECT_XML_TAG, WSManNativeApi.PS_XML_NAMESPACE, Convert.ToBase64String(additionalData)); @@ -1034,7 +1035,7 @@ internal override void StartReceivingData() /// /// WSManCreateShellEx failed. /// - internal override void CreateAsync() + public override void CreateAsync() { Dbg.Assert(!isClosed, "object already disposed"); Dbg.Assert(!string.IsNullOrEmpty(ConnectionInfo.ShellUri), "shell uri cannot be null or empty."); @@ -1097,7 +1098,9 @@ internal override void CreateAsync() { // WSMan expects the data to be in XML format (which is text + xml tags) // so convert byte[] into base64 encoded format - string base64EncodedDataInXml = string.Format(CultureInfo.InvariantCulture, "<{0} xmlns=\"{1}\">{2}", + string base64EncodedDataInXml = string.Format( + CultureInfo.InvariantCulture, + "<{0} xmlns=\"{1}\">{2}", WSManNativeApi.PS_CREATION_XML_TAG, WSManNativeApi.PS_XML_NAMESPACE, Convert.ToBase64String(additionalData)); @@ -1189,7 +1192,7 @@ internal override void CreateAsync() /// Closes the pending Create,Send,Receive operations and then closes the shell and release all the resources. /// The caller should make sure this method is called only after calling ConnectAsync. /// - internal override void CloseAsync() + public override void CloseAsync() { bool shouldRaiseCloseCompleted = false; // let other threads release the lock before we clean up the resources. @@ -1399,7 +1402,8 @@ private void Initialize(Uri connectionUri, WSManConnectionInfo connectionInfo) if (string.IsNullOrEmpty(connectionUri.Query)) { // if there is no query string already, create one..see RFC 3986 - connectionStr = string.Format(CultureInfo.InvariantCulture, + connectionStr = string.Format( + CultureInfo.InvariantCulture, "{0}?PSVersion={1}{2}", // Trimming the last '/' as this will allow WSMan to // properly apply URLPrefix. @@ -1413,11 +1417,12 @@ private void Initialize(Uri connectionUri, WSManConnectionInfo connectionInfo) else { // if there is already a query string, append using & .. see RFC 3986 - connectionStr = string.Format(CultureInfo.InvariantCulture, - "{0};PSVersion={1}{2}", - connectionStr, - PSVersionInfo.PSVersion, - additionalUriSuffixString); + connectionStr = string.Format( + CultureInfo.InvariantCulture, + "{0};PSVersion={1}{2}", + connectionStr, + PSVersionInfo.PSVersion, + additionalUriSuffixString); } WSManNativeApi.BaseWSManAuthenticationCredentials authCredentials; @@ -1492,20 +1497,9 @@ private void Initialize(Uri connectionUri, WSManConnectionInfo connectionInfo) finally { // release resources - if (proxyAuthCredentials != null) - { - proxyAuthCredentials.Dispose(); - } - - if (proxyInfo != null) - { - proxyInfo.Dispose(); - } - - if (authCredentials != null) - { - authCredentials.Dispose(); - } + proxyAuthCredentials?.Dispose(); + proxyInfo?.Dispose(); + authCredentials?.Dispose(); } if (result != 0) @@ -1640,7 +1634,7 @@ internal void ProcessWSManTransportError(TransportErrorOccuredEventArgs eventArg /// Log the error message in the Crimson logger and raise error handler. /// /// - internal override void RaiseErrorHandler(TransportErrorOccuredEventArgs eventArgs) + public override void RaiseErrorHandler(TransportErrorOccuredEventArgs eventArgs) { // Look for a valid stack trace. string stackTrace; @@ -1790,7 +1784,10 @@ internal IntPtr SessionHandle /// True if a session create retry has been started. private bool RetrySessionCreation(int sessionCreateErrorCode) { - if (_connectionRetryCount >= ConnectionInfo.MaxConnectionRetryCount) { return false; } + if (_connectionRetryCount >= ConnectionInfo.MaxConnectionRetryCount) + { + return false; + } bool retryConnect; switch (sessionCreateErrorCode) @@ -2543,7 +2540,7 @@ private void SendData(byte[] data, DataPriorityType priorityType) #region Dispose / Destructor pattern [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed")] - internal override void Dispose(bool isDisposing) + protected override void Dispose(bool isDisposing) { tracer.WriteLine("Disposing session with session context: {0} Operation Context: {1}", _sessionContextID, _wsManShellOperationHandle); @@ -2623,7 +2620,10 @@ private void CloseSessionAndClearResources() private void DisposeWSManAPIDataAsync() { WSManAPIDataCommon tempWSManApiData = WSManAPIData; - if (tempWSManApiData == null) { return; } + if (tempWSManApiData == null) + { + return; + } WSManAPIData = null; @@ -2726,7 +2726,10 @@ public void Dispose() { lock (_syncObject) { - if (_isDisposed) { return; } + if (_isDisposed) + { + return; + } _isDisposed = true; } @@ -2833,7 +2836,7 @@ internal sealed class WSManClientCommandTransportManager : BaseClientCommandTran private readonly string _cmdLine; private readonly WSManClientSessionTransportManager _sessnTm; - private class SendDataChunk + private sealed class SendDataChunk { public SendDataChunk(byte[] data, DataPriorityType type) { @@ -3013,11 +3016,12 @@ internal override void ConnectAsync() } /// + /// Begin connection creation. /// /// /// WSManRunShellCommandEx failed. /// - internal override void CreateAsync() + public override void CreateAsync() { byte[] cmdPart1 = serializedPipeline.ReadOrRegisterCallback(null); if (cmdPart1 != null) @@ -3146,7 +3150,7 @@ internal override void SendStopSignal() /// /// Closes the pending Create,Send,Receive operations and then closes the shell and release all the resources. /// - internal override void CloseAsync() + public override void CloseAsync() { tracer.WriteLine("Closing command with command context: {0} Operation Context {1}", _cmdContextId, _wsManCmdOperationHandle); @@ -3213,7 +3217,7 @@ internal void ProcessWSManTransportError(TransportErrorOccuredEventArgs eventArg /// Log the error message in the Crimson logger and raise error handler. /// /// - internal override void RaiseErrorHandler(TransportErrorOccuredEventArgs eventArgs) + public override void RaiseErrorHandler(TransportErrorOccuredEventArgs eventArgs) { // Look for a valid stack trace. string stackTrace; @@ -4086,7 +4090,7 @@ internal override void StartReceivingData() #region Dispose / Destructor pattern - internal override void Dispose(bool isDisposing) + protected override void Dispose(bool isDisposing) { tracer.WriteLine("Disposing command with command context: {0} Operation Context: {1}", _cmdContextId, _wsManCmdOperationHandle); base.Dispose(isDisposing); diff --git a/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs b/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs index 1c2728cc6d6..14b0240858b 100644 --- a/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs +++ b/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs @@ -8,7 +8,6 @@ #if !UNIX using System.Security.Principal; #endif -using Microsoft.Win32.SafeHandles; using Dbg = System.Management.Automation.Diagnostics; @@ -73,14 +72,6 @@ protected void ProcessingThreadStart(object state) { try { -#if !CORECLR - // CurrentUICulture is not available in Thread Class in CSS - // WinBlue: 621775. Thread culture is not properly set - // for local background jobs causing experience differences - // between local console and local background jobs. - Thread.CurrentThread.CurrentUICulture = Microsoft.PowerShell.NativeCultureResolver.UICulture; - Thread.CurrentThread.CurrentCulture = Microsoft.PowerShell.NativeCultureResolver.Culture; -#endif string data = state as string; OutOfProcessUtils.ProcessData(data, callbacks); } @@ -209,10 +200,7 @@ protected void OnSignalPacketReceived(Guid psGuid) } // dont throw if there is no cmdTM as it might have legitimately closed - if (cmdTM != null) - { - cmdTM.Close(null); - } + cmdTM?.Close(null); } finally { @@ -253,12 +241,9 @@ protected void OnClosePacketReceived(Guid psGuid) { tracer.WriteMessage("OnClosePacketReceived, in progress commands count should be zero : " + _inProgressCommandsCount + ", psGuid : " + psGuid.ToString()); - if (sessionTM != null) - { - // it appears that when closing PowerShell ISE, therefore closing OutOfProcServerMediator, there are 2 Close command requests - // changing PSRP/IPC at this point is too risky, therefore protecting about this duplication - sessionTM.Close(null); - } + // it appears that when closing PowerShell ISE, therefore closing OutOfProcServerMediator, there are 2 Close command requests + // changing PSRP/IPC at this point is too risky, therefore protecting about this duplication + sessionTM?.Close(null); tracer.WriteMessage("END calling close on session transport manager"); sessionTM = null; @@ -277,10 +262,7 @@ protected void OnClosePacketReceived(Guid psGuid) } // dont throw if there is no cmdTM as it might have legitimately closed - if (cmdTM != null) - { - cmdTM.Close(null); - } + cmdTM?.Close(null); lock (_syncObject) { @@ -307,7 +289,11 @@ protected void OnCloseAckPacketReceived(Guid psGuid) #region Methods - protected OutOfProcessServerSessionTransportManager CreateSessionTransportManager(string configurationName, PSRemotingCryptoHelperServer cryptoHelper, string workingDirectory) + protected OutOfProcessServerSessionTransportManager CreateSessionTransportManager( + string configurationName, + string configurationFile, + PSRemotingCryptoHelperServer cryptoHelper, + string workingDirectory) { PSSenderInfo senderInfo; #if !UNIX @@ -323,23 +309,38 @@ protected OutOfProcessServerSessionTransportManager CreateSessionTransportManage senderInfo = new PSSenderInfo(userPrincipal, "http://localhost"); #endif - OutOfProcessServerSessionTransportManager tm = new OutOfProcessServerSessionTransportManager(originalStdOut, originalStdErr, cryptoHelper); + var tm = new OutOfProcessServerSessionTransportManager( + originalStdOut, + originalStdErr, + cryptoHelper); ServerRemoteSession.CreateServerRemoteSession( - senderInfo, - _initialCommand, - tm, - configurationName, - workingDirectory); + senderInfo: senderInfo, + configurationProviderId: "Microsoft.PowerShell", + initializationParameters: string.Empty, + transportManager: tm, + initialCommand: _initialCommand, + configurationName: configurationName, + configurationFile: configurationFile, + initialLocation: workingDirectory); return tm; } - protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoHelper, string workingDirectory = null, string configurationName = null) + protected void Start( + string initialCommand, + PSRemotingCryptoHelperServer cryptoHelper, + string workingDirectory, + string configurationName, + string configurationFile) { _initialCommand = initialCommand; - sessionTM = CreateSessionTransportManager(configurationName, cryptoHelper, workingDirectory); + sessionTM = CreateSessionTransportManager( + configurationName: configurationName, + configurationFile: configurationFile, + cryptoHelper: cryptoHelper, + workingDirectory: workingDirectory); try { @@ -348,10 +349,11 @@ protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoH string data = originalStdIn.ReadLine(); lock (_syncObject) { - if (sessionTM == null) - { - sessionTM = CreateSessionTransportManager(configurationName, cryptoHelper, workingDirectory); - } + sessionTM ??= CreateSessionTransportManager( + configurationName: configurationName, + configurationFile: configurationFile, + cryptoHelper: cryptoHelper, + workingDirectory: workingDirectory); } if (string.IsNullOrEmpty(data)) @@ -417,35 +419,13 @@ protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoH } #endregion - - #region Static Methods - - internal static void AppDomainUnhandledException(object sender, UnhandledExceptionEventArgs args) - { - // args can never be null. - Exception exception = (Exception)args.ExceptionObject; - // log the exception to crimson event logs - PSEtwLog.LogOperationalError(PSEventId.AppDomainUnhandledException, - PSOpcode.Close, PSTask.None, - PSKeyword.UseAlwaysOperational, - exception.GetType().ToString(), exception.Message, - exception.StackTrace); - - PSEtwLog.LogAnalyticError(PSEventId.AppDomainUnhandledException_Analytic, - PSOpcode.Close, PSTask.None, - PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, - exception.GetType().ToString(), exception.Message, - exception.StackTrace); - } - - #endregion } - internal sealed class OutOfProcessMediator : OutOfProcessMediatorBase + internal sealed class StdIOProcessMediator : OutOfProcessMediatorBase { #region Private Data - private static OutOfProcessMediator s_singletonInstance; + private static StdIOProcessMediator s_singletonInstance; #endregion @@ -453,10 +433,11 @@ internal sealed class OutOfProcessMediator : OutOfProcessMediatorBase /// /// The mediator will take actions from the StdIn stream and responds to them. - /// It will replace StdIn,StdOut and StdErr stream with TextWriter.Null's. This is + /// It will replace StdIn,StdOut and StdErr stream with TextWriter.Null. This is /// to make sure these streams are totally used by our Mediator. /// - private OutOfProcessMediator() : base(true) + /// Redirects remoting errors to the Out stream. + private StdIOProcessMediator(bool combineErrOutStream) : base(exitProcessOnError: true) { // Create input stream reader from Console standard input stream. // We don't use the provided Console.In TextReader because it can have @@ -466,18 +447,22 @@ private OutOfProcessMediator() : base(true) // stream BOM as needed. originalStdIn = new StreamReader(Console.OpenStandardInput(), true); - // replacing StdIn with Null so that no other app messes with the - // original stream. - Console.SetIn(TextReader.Null); - - // replacing StdOut with Null so that no other app messes with the - // original stream + // Remoting errors can optionally be written to stdErr or stdOut with + // special formatting. originalStdOut = new OutOfProcessTextWriter(Console.Out); - Console.SetOut(TextWriter.Null); + if (combineErrOutStream) + { + originalStdErr = new FormattedErrorTextWriter(Console.Out); + } + else + { + originalStdErr = new OutOfProcessTextWriter(Console.Error); + } - // replacing StdErr with Null so that no other app messes with the - // original stream - originalStdErr = new OutOfProcessTextWriter(Console.Error); + // Replacing StdIn, StdOut, StdErr with Null so that no other app messes with the + // original streams. + Console.SetIn(TextReader.Null); + Console.SetOut(TextWriter.Null); Console.SetError(TextWriter.Null); } @@ -490,72 +475,15 @@ private OutOfProcessMediator() : base(true) /// /// Specifies the initialization script. /// Specifies the initial working directory. The working directory is set before the initial command. - internal static void Run(string initialCommand, string workingDirectory) - { - lock (SyncObject) - { - if (s_singletonInstance != null) - { - Dbg.Assert(false, "Run should not be called multiple times"); - return; - } - - s_singletonInstance = new OutOfProcessMediator(); - } - -#if !CORECLR // AppDomain is not available in CoreCLR - // Setup unhandled exception to log events - AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomainUnhandledException); -#endif - s_singletonInstance.Start(initialCommand, new PSRemotingCryptoHelperServer(), workingDirectory); - } - - #endregion - } - - internal sealed class SSHProcessMediator : OutOfProcessMediatorBase - { - #region Private Data - - private static SSHProcessMediator s_singletonInstance; - - #endregion - - #region Constructors - - private SSHProcessMediator() : base(true) - { -#if !UNIX - var inputHandle = PlatformInvokes.GetStdHandle((uint)PlatformInvokes.StandardHandleId.Input); - originalStdIn = new StreamReader( - new FileStream(new SafeFileHandle(inputHandle, false), FileAccess.Read)); - - var outputHandle = PlatformInvokes.GetStdHandle((uint)PlatformInvokes.StandardHandleId.Output); - originalStdOut = new OutOfProcessTextWriter( - new StreamWriter( - new FileStream(new SafeFileHandle(outputHandle, false), FileAccess.Write))); - - var errorHandle = PlatformInvokes.GetStdHandle((uint)PlatformInvokes.StandardHandleId.Error); - originalStdErr = new OutOfProcessTextWriter( - new StreamWriter( - new FileStream(new SafeFileHandle(errorHandle, false), FileAccess.Write))); -#else - originalStdIn = new StreamReader(Console.OpenStandardInput(), true); - originalStdOut = new OutOfProcessTextWriter( - new StreamWriter(Console.OpenStandardOutput())); - originalStdErr = new OutOfProcessTextWriter( - new StreamWriter(Console.OpenStandardError())); -#endif - } - - #endregion - - #region Static Methods - - /// - /// - /// - internal static void Run(string initialCommand) + /// Specifies an optional configuration name that configures the endpoint session. + /// Specifies an optional path to a configuration (.pssc) file for the session. + /// Specifies the option to write remoting errors to stdOut stream, with special formatting. + internal static void Run( + string initialCommand, + string workingDirectory, + string configurationName, + string configurationFile, + bool combineErrOutStream) { lock (SyncObject) { @@ -565,10 +493,15 @@ internal static void Run(string initialCommand) return; } - s_singletonInstance = new SSHProcessMediator(); + s_singletonInstance = new StdIOProcessMediator(combineErrOutStream); } - s_singletonInstance.Start(initialCommand, new PSRemotingCryptoHelperServer()); + s_singletonInstance.Start( + initialCommand: initialCommand, + cryptoHelper: new PSRemotingCryptoHelperServer(), + workingDirectory: workingDirectory, + configurationName: configurationName, + configurationFile: configurationFile); } #endregion @@ -610,7 +543,7 @@ private NamedPipeProcessMediator( // Create transport reader/writers from named pipe. originalStdIn = namedPipeServer.TextReader; originalStdOut = new OutOfProcessTextWriter(namedPipeServer.TextWriter); - originalStdErr = new NamedPipeErrorTextWriter(namedPipeServer.TextWriter); + originalStdErr = new FormattedErrorTextWriter(namedPipeServer.TextWriter); #if !UNIX // Flow impersonation as needed. @@ -637,36 +570,22 @@ internal static void Run( s_singletonInstance = new NamedPipeProcessMediator(namedPipeServer); } -#if !CORECLR - // AppDomain is not available in CoreCLR - AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomainUnhandledException); -#endif - s_singletonInstance.Start(initialCommand, new PSRemotingCryptoHelperServer(), namedPipeServer.ConfigurationName); + s_singletonInstance.Start( + initialCommand: initialCommand, + cryptoHelper: new PSRemotingCryptoHelperServer(), + workingDirectory: null, + configurationName: namedPipeServer.ConfigurationName, + configurationFile: null); } #endregion } - internal sealed class NamedPipeErrorTextWriter : OutOfProcessTextWriter + internal sealed class FormattedErrorTextWriter : OutOfProcessTextWriter { - #region Private Members - - private const string _errorPrepend = "__NamedPipeError__:"; - - #endregion - - #region Properties - - internal static string ErrorPrepend - { - get { return _errorPrepend; } - } - - #endregion - #region Constructors - internal NamedPipeErrorTextWriter( + internal FormattedErrorTextWriter( TextWriter textWriter) : base(textWriter) { } @@ -674,9 +593,11 @@ internal NamedPipeErrorTextWriter( #region Base class overrides - internal override void WriteLine(string data) + // Write error data to stream with 'ErrorPrefix' prefix that will + // be interpreted by the client. + public override void WriteLine(string data) { - string dataToWrite = (data != null) ? _errorPrepend + data : null; + string dataToWrite = (data != null) ? ErrorPrefix + data : null; base.WriteLine(dataToWrite); } @@ -727,12 +648,12 @@ internal static void Run( s_instance = new HyperVSocketMediator(); } -#if !CORECLR - // AppDomain is not available in CoreCLR - AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomainUnhandledException); -#endif - - s_instance.Start(initialCommand, new PSRemotingCryptoHelperServer(), configurationName); + s_instance.Start( + initialCommand: initialCommand, + cryptoHelper: new PSRemotingCryptoHelperServer(), + workingDirectory: null, + configurationName: configurationName, + configurationFile: null); } #endregion @@ -766,7 +687,7 @@ internal HyperVSocketErrorTextWriter( #region Base class overrides - internal override void WriteLine(string data) + public override void WriteLine(string data) { string dataToWrite = (data != null) ? _errorPrepend + data : null; base.WriteLine(dataToWrite); diff --git a/src/System.Management.Automation/engine/remoting/server/ServerPowerShellDriver.cs b/src/System.Management.Automation/engine/remoting/server/ServerPowerShellDriver.cs index 6a49d6f3a4a..ddeb81aae17 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerPowerShellDriver.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerPowerShellDriver.cs @@ -427,7 +427,10 @@ private void HandlePowerShellInvocationStateChanged(object sender, if (LocalPowerShell.RunningExtraCommands) { // If completed successfully then allow extra commands to run. - if (state == PSInvocationState.Completed) { return; } + if (state == PSInvocationState.Completed) + { + return; + } // For failed or stopped state, extra commands cannot run and // we allow this command invocation to finish. @@ -798,11 +801,9 @@ private void HandleSessionConnected(object sender, EventArgs eventArgs) { // Close input if its active. no need to synchronize as input stream would have already been processed // when connect call came into PS plugin - if (InputCollection != null) - { - // TODO: Post an ETW event - InputCollection.Complete(); - } + + // TODO: Post an ETW event + InputCollection?.Complete(); } /// diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs index ca7ff805c02..7e7aa767d5c 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs @@ -352,10 +352,7 @@ public override void PopRunspace() { if (_pushedRunspace != null) { - if (_debugger != null) - { - _debugger.PopDebugger(); - } + _debugger?.PopDebugger(); if (_hostSupportsPSEdit) { @@ -410,7 +407,10 @@ internal bool PropagatePop private void AddPSEditForRunspace(RemoteRunspace remoteRunspace) { - if (remoteRunspace.Events == null) { return; } + if (remoteRunspace.Events == null) + { + return; + } // Add event handler. remoteRunspace.Events.ReceivedEvents.PSEventReceived += HandleRemoteSessionForwardedEvent; @@ -430,7 +430,10 @@ private void AddPSEditForRunspace(RemoteRunspace remoteRunspace) private void RemovePSEditFromRunspace(RemoteRunspace remoteRunspace) { - if (remoteRunspace.Events == null) { return; } + if (remoteRunspace.Events == null) + { + return; + } // It is possible for the popped runspace to be in a bad state after an error. if ((remoteRunspace.RunspaceStateInfo.State != RunspaceState.Opened) || (remoteRunspace.RunspaceAvailability != RunspaceAvailability.Available)) @@ -456,7 +459,10 @@ private void RemovePSEditFromRunspace(RemoteRunspace remoteRunspace) private void HandleRemoteSessionForwardedEvent(object sender, PSEventArgs args) { - if ((Runspace == null) || (Runspace.Events == null)) { return; } + if ((Runspace == null) || (Runspace.Events == null)) + { + return; + } // Forward events from nested pushed session to parent session. try diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostRawUserInterface.cs b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostRawUserInterface.cs index 05448d02d9b..34c10e0a9a0 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostRawUserInterface.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostRawUserInterface.cs @@ -343,10 +343,7 @@ public override void SetBufferContents(Coordinates origin, BufferCell[,] content // to keep the other overload in sync: LengthInBufferCells(string, int) public override int LengthInBufferCells(string source) { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } + ArgumentNullException.ThrowIfNull(source); return source.Length; } @@ -354,10 +351,7 @@ public override int LengthInBufferCells(string source) // more performant than the default implementation provided by PSHostRawUserInterface public override int LengthInBufferCells(string source, int offset) { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } + ArgumentNullException.ThrowIfNull(source); Dbg.Assert(offset >= 0, "offset >= 0"); Dbg.Assert(string.IsNullOrEmpty(source) || (offset < source.Length), "offset < source.Length"); diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostUserInterface.cs b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostUserInterface.cs index 314fc25587f..76265089677 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostUserInterface.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostUserInterface.cs @@ -122,6 +122,7 @@ public override Dictionary Prompt(string caption, string messa /// public override void Write(string message) { + message = GetOutputString(message, supportsVirtualTerminal: true); _serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.Write1, new object[] { message }); } @@ -130,6 +131,7 @@ public override void Write(string message) /// public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string message) { + message = GetOutputString(message, supportsVirtualTerminal: true); _serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.Write2, new object[] { foregroundColor, backgroundColor, message }); } @@ -146,6 +148,7 @@ public override void WriteLine() /// public override void WriteLine(string message) { + message = GetOutputString(message, supportsVirtualTerminal: true); _serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.WriteLine2, new object[] { message }); } @@ -154,6 +157,7 @@ public override void WriteLine(string message) /// public override void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string message) { + message = GetOutputString(message, supportsVirtualTerminal: true); _serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.WriteLine3, new object[] { foregroundColor, backgroundColor, message }); } @@ -162,6 +166,7 @@ public override void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgr /// public override void WriteErrorLine(string message) { + message = GetOutputString(message, supportsVirtualTerminal: true); _serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.WriteErrorLine, new object[] { message }); } @@ -170,6 +175,7 @@ public override void WriteErrorLine(string message) /// public override void WriteDebugLine(string message) { + message = GetOutputString(message, supportsVirtualTerminal: true); _serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.WriteDebugLine, new object[] { message }); } @@ -186,6 +192,7 @@ public override void WriteProgress(long sourceId, ProgressRecord record) /// public override void WriteVerboseLine(string message) { + message = GetOutputString(message, supportsVirtualTerminal: true); _serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.WriteVerboseLine, new object[] { message }); } @@ -194,6 +201,7 @@ public override void WriteVerboseLine(string message) /// public override void WriteWarningLine(string message) { + message = GetOutputString(message, supportsVirtualTerminal: true); _serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.WriteWarningLine, new object[] { message }); } diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRemotingProtocol2.cs b/src/System.Management.Automation/engine/remoting/server/ServerRemotingProtocol2.cs index f87f89b5bac..11761c25af8 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRemotingProtocol2.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRemotingProtocol2.cs @@ -266,10 +266,7 @@ internal void DispatchMessageToPowerShell(RemoteDataObject rcvdData) // if data structure handler is not found, then association has already been // removed, discard message - if (dsHandler != null) - { - dsHandler.ProcessReceivedData(rcvdData); - } + dsHandler?.ProcessReceivedData(rcvdData); } /// @@ -351,7 +348,7 @@ internal TypeTable TypeTable /// /// Data to send. /// This overload takes a RemoteDataObject and should - /// be the one thats used to send data from within this + /// be the one that's used to send data from within this /// data structure handler class private void SendDataAsync(RemoteDataObject data) { @@ -788,7 +785,7 @@ internal Runspace RunspaceUsedToInvokePowerShell /// /// Data to send. /// This overload takes a RemoteDataObject and should - /// be the one thats used to send data from within this + /// be the one that's used to send data from within this /// data structure handler class private void SendDataAsync(RemoteDataObject data) { diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs index 2f2675e61d0..ba0728790e7 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs @@ -168,7 +168,7 @@ internal ServerRunspacePoolDriver( // The default server settings is to make new commands execute in the calling thread...this saves // thread switching time and thread pool pressure on the service. // Users can override the server settings only if they are administrators - PSThreadOptions serverThreadOptions = configData.ShellThreadOptions.HasValue ? configData.ShellThreadOptions.Value : PSThreadOptions.UseCurrentThread; + PSThreadOptions serverThreadOptions = configData.ShellThreadOptions ?? PSThreadOptions.UseCurrentThread; if (threadOptions == PSThreadOptions.Default || threadOptions == serverThreadOptions) { RunspacePool.ThreadOptions = serverThreadOptions; @@ -184,7 +184,7 @@ internal ServerRunspacePoolDriver( } // Set Thread ApartmentState for this RunspacePool - ApartmentState serverApartmentState = configData.ShellThreadApartmentState.HasValue ? configData.ShellThreadApartmentState.Value : Runspace.DefaultApartmentState; + ApartmentState serverApartmentState = configData.ShellThreadApartmentState ?? Runspace.DefaultApartmentState; if (apartmentState == ApartmentState.Unknown || apartmentState == serverApartmentState) { @@ -271,10 +271,7 @@ internal void Start() internal void SendApplicationPrivateDataToClient() { // Include Debug mode information. - if (_applicationPrivateData == null) - { - _applicationPrivateData = new PSPrimitiveDictionary(); - } + _applicationPrivateData ??= new PSPrimitiveDictionary(); if (_serverRemoteDebugger != null) { @@ -350,10 +347,7 @@ internal void Close() { Runspace runspaceToDispose = _remoteHost.PushedRunspace; _remoteHost.PopRunspace(); - if (runspaceToDispose != null) - { - runspaceToDispose.Dispose(); - } + runspaceToDispose?.Dispose(); } DisposeRemoteDebugger(); @@ -478,7 +472,7 @@ private void SetupRemoteDebugger(Runspace runspace) // Remote debugger is created only when client version is PSVersion (4.0) // or greater, and remote session supports debugging. if ((_driverNestedInvoker != null) && - (_clientPSVersion != null && _clientPSVersion >= PSVersionInfo.PSV4Version) && + (_clientPSVersion != null && _clientPSVersion.Major >= 4) && (runspace != null && runspace.Debugger != null)) { _serverRemoteDebugger = new ServerRemoteDebugger(this, runspace, runspace.Debugger); @@ -486,13 +480,7 @@ private void SetupRemoteDebugger(Runspace runspace) } } - private void DisposeRemoteDebugger() - { - if (_serverRemoteDebugger != null) - { - _serverRemoteDebugger.Dispose(); - } - } + private void DisposeRemoteDebugger() => _serverRemoteDebugger?.Dispose(); /// /// Invokes a script. @@ -561,7 +549,7 @@ private PSDataCollection InvokePowerShell(PowerShell powershell, Runsp Exception lastException = errorList[0] as Exception; if (lastException != null) { - exceptionThrown = (lastException.Message != null) ? lastException.Message : string.Empty; + exceptionThrown = lastException.Message ?? string.Empty; } else { @@ -812,10 +800,7 @@ private void HandleCreateAndInvokePowerShell(object _, RemoteDataEventArgs publicGetCommandEntries = iss .Commands["Get-Command"] - .Where(entry => entry.Visibility == SessionStateEntryVisibility.Public); + .Where(static entry => entry.Visibility == SessionStateEntryVisibility.Public); SessionStateFunctionEntry getCommandProxy = publicGetCommandEntries.OfType().FirstOrDefault(); if (getCommandProxy != null) { @@ -1252,7 +1234,7 @@ private enum PreProcessCommandResult BreakpointManagement, } - private class DebuggerCommandArgument + private sealed class DebuggerCommandArgument { public DebugModes? Mode { get; set; } @@ -2004,10 +1986,7 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC StringUtil.Format(DebuggerStrings.CannotProcessDebuggerCommandNotStopped)); } - if (_processCommandCompleteEvent == null) - { - _processCommandCompleteEvent = new ManualResetEventSlim(false); - } + _processCommandCompleteEvent ??= new ManualResetEventSlim(false); _threadCommandProcessing = new ThreadCommandProcessing(command, output, _wrappedDebugger.Value, _processCommandCompleteEvent); try @@ -2031,10 +2010,7 @@ public override void StopProcessCommand() } ThreadCommandProcessing threadCommandProcessing = _threadCommandProcessing; - if (threadCommandProcessing != null) - { - threadCommandProcessing.Stop(); - } + threadCommandProcessing?.Stop(); } /// @@ -2232,15 +2208,8 @@ public void Dispose() ExitDebugMode(DebuggerResumeAction.Stop); } - if (_nestedDebugStopCompleteEvent != null) - { - _nestedDebugStopCompleteEvent.Dispose(); - } - - if (_processCommandCompleteEvent != null) - { - _processCommandCompleteEvent.Dispose(); - } + _nestedDebugStopCompleteEvent?.Dispose(); + _processCommandCompleteEvent?.Dispose(); } #endregion @@ -2312,10 +2281,7 @@ public DebuggerCommandResults Invoke(ManualResetEventSlim startInvokeEvent) public void Stop() { Debugger debugger = _wrappedDebugger; - if (debugger != null) - { - debugger.StopProcessCommand(); - } + debugger?.StopProcessCommand(); } internal void DoInvoke() @@ -2420,7 +2386,10 @@ private void RemoveDebuggerCallbacks() private void HandleDebuggerStop(object sender, DebuggerStopEventArgs e) { // Ignore if we are in restricted mode. - if (!IsDebuggingSupported()) { return; } + if (!IsDebuggingSupported()) + { + return; + } if (LocalDebugMode) { @@ -2476,7 +2445,10 @@ private void HandleDebuggerStop(object sender, DebuggerStopEventArgs e) private void HandleBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) { // Ignore if we are in restricted mode. - if (!IsDebuggingSupported()) { return; } + if (!IsDebuggingSupported()) + { + return; + } if (LocalDebugMode) { @@ -2527,10 +2499,7 @@ private void EnterDebugMode(bool isNestedStop) { // Blocking call for nested debugger execution (Debug-Runspace) stop events. // The root debugger never makes two EnterDebugMode calls without an ExitDebugMode. - if (_nestedDebugStopCompleteEvent == null) - { - _nestedDebugStopCompleteEvent = new ManualResetEventSlim(false); - } + _nestedDebugStopCompleteEvent ??= new ManualResetEventSlim(false); _nestedDebugging = true; OnEnterDebugMode(_nestedDebugStopCompleteEvent); @@ -2799,7 +2768,10 @@ internal void PushDebugger(Debugger debugger) internal void PopDebugger() { - if (!_wrappedDebugger.IsOverridden) { return; } + if (!_wrappedDebugger.IsOverridden) + { + return; + } // Swap wrapped debugger. UnsubscribeWrappedDebugger(_wrappedDebugger.Value); diff --git a/src/System.Management.Automation/engine/remoting/server/ServerSteppablePipelineDriver.cs b/src/System.Management.Automation/engine/remoting/server/ServerSteppablePipelineDriver.cs index 66fe9d92730..3a9388625e6 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerSteppablePipelineDriver.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerSteppablePipelineDriver.cs @@ -14,7 +14,7 @@ namespace System.Management.Automation /// /// Execution context used for stepping. /// - internal class ExecutionContextForStepping : IDisposable + internal sealed class ExecutionContextForStepping : IDisposable { private readonly ExecutionContext _executionContext; private PSInformationalBuffers _originalInformationalBuffers; @@ -241,10 +241,7 @@ internal void Start() _eventSubscriber.FireStartSteppablePipeline(this); - if (_powershellInput != null) - { - _powershellInput.Pulse(); - } + _powershellInput?.Pulse(); } #endregion Internal Methods @@ -262,20 +259,14 @@ internal void HandleInputEndReceived(object sender, EventArgs eventArgs) CheckAndPulseForProcessing(true); - if (_powershellInput != null) - { - _powershellInput.Pulse(); - } + _powershellInput?.Pulse(); } private void HandleSessionConnected(object sender, EventArgs eventArgs) { // Close input if its active. no need to synchronize as input stream would have already been processed // when connect call came into PS plugin - if (Input != null) - { - Input.Complete(); - } + Input?.Complete(); } /// @@ -302,10 +293,7 @@ private void HandleStopReceived(object sender, EventArgs eventArgs) PerformStop(); - if (_powershellInput != null) - { - _powershellInput.Pulse(); - } + _powershellInput?.Pulse(); } /// @@ -326,10 +314,7 @@ private void HandleInputReceived(object sender, RemoteDataEventArgs even CheckAndPulseForProcessing(false); - if (_powershellInput != null) - { - _powershellInput.Pulse(); - } + _powershellInput?.Pulse(); } } diff --git a/src/System.Management.Automation/engine/remoting/server/ServerSteppablePipelineSubscriber.cs b/src/System.Management.Automation/engine/remoting/server/ServerSteppablePipelineSubscriber.cs index 328571647cb..cce8b0bbcd3 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerSteppablePipelineSubscriber.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerSteppablePipelineSubscriber.cs @@ -162,7 +162,7 @@ private void HandleProcessRecord(object sender, PSEventArgs args) if (!driver.NoInput || isProcessCalled) { // if there is noInput then we - // need to call process atleast once + // need to call process at least once break; } } @@ -274,11 +274,8 @@ internal void FireStartSteppablePipeline(ServerSteppablePipelineDriver driver) { lock (_syncObject) { - if (_eventManager != null) - { - _eventManager.GenerateEvent(_startSubscriber.SourceIdentifier, this, - new object[1] { new ServerSteppablePipelineDriverEventArg(driver) }, null, true, false); - } + _eventManager?.GenerateEvent(_startSubscriber.SourceIdentifier, this, + new object[1] { new ServerSteppablePipelineDriverEventArg(driver) }, null, true, false); } } @@ -290,11 +287,8 @@ internal void FireHandleProcessRecord(ServerSteppablePipelineDriver driver) { lock (_syncObject) { - if (_eventManager != null) - { - _eventManager.GenerateEvent(_processSubscriber.SourceIdentifier, this, - new object[1] { new ServerSteppablePipelineDriverEventArg(driver) }, null, true, false); - } + _eventManager?.GenerateEvent(_processSubscriber.SourceIdentifier, this, + new object[1] { new ServerSteppablePipelineDriverEventArg(driver) }, null, true, false); } } diff --git a/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs b/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs index 7b08c674301..d9e85e94e7a 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() /// internal class ServerRemoteSession : RemoteSession { - [TraceSourceAttribute("ServerRemoteSession", "ServerRemoteSession")] + [TraceSource("ServerRemoteSession", "ServerRemoteSession")] private static readonly PSTraceSource s_trace = PSTraceSource.GetTracer("ServerRemoteSession", "ServerRemoteSession"); private readonly PSSenderInfo _senderInfo; @@ -89,6 +89,10 @@ internal class ServerRemoteSession : RemoteSession // Creates a pushed remote runspace session created with this configuration name. private string _configurationName; + // Specifies an optional .pssc configuration file path for out-of-proc session use. + // The .pssc file is used to configure the runspace for the endpoint session. + private string _configurationFile; + // Specifies an initial location of the powershell session. private string _initialLocation; @@ -173,7 +177,9 @@ internal ServerRemoteSession(PSSenderInfo senderInfo, /// xml. /// /// + /// Optional initial command used for OutOfProc sessions. /// Optional configuration endpoint name for OutOfProc sessions. + /// Optional configuration file (.pssc) path for OutOfProc sessions. /// Optional configuration initial location of the powershell session. /// /// @@ -192,8 +198,10 @@ internal static ServerRemoteSession CreateServerRemoteSession( string configurationProviderId, string initializationParameters, AbstractServerSessionTransportManager transportManager, - string configurationName = null, - string initialLocation = null) + string initialCommand, + string configurationName, + string configurationFile, + string initialLocation) { Dbg.Assert( (senderInfo != null) && (senderInfo.UserInfo != null), @@ -215,7 +223,9 @@ internal static ServerRemoteSession CreateServerRemoteSession( initializationParameters, transportManager) { + _initScriptForOutOfProcRS = initialCommand, _configurationName = configurationName, + _configurationFile = configurationFile, _initialLocation = initialLocation }; @@ -226,33 +236,6 @@ internal static ServerRemoteSession CreateServerRemoteSession( return result; } - /// - /// Used by OutOfProcessServerMediator to create a remote session. - /// - /// - /// - /// - /// - /// - /// - internal static ServerRemoteSession CreateServerRemoteSession( - PSSenderInfo senderInfo, - string initializationScriptForOutOfProcessRunspace, - AbstractServerSessionTransportManager transportManager, - string configurationName, - string initialLocation) - { - ServerRemoteSession result = CreateServerRemoteSession( - senderInfo, - "Microsoft.PowerShell", - string.Empty, - transportManager, - configurationName: configurationName, - initialLocation: initialLocation); - result._initScriptForOutOfProcRS = initializationScriptForOutOfProcessRunspace; - return result; - } - #endregion #region Overrides @@ -723,13 +706,7 @@ internal void ExecuteConnect(byte[] connectData, out byte[] connectResponseData) } // pass on application private data when session is connected from new client - internal void HandlePostConnect() - { - if (_runspacePoolDriver != null) - { - _runspacePoolDriver.SendApplicationPrivateDataToClient(); - } - } + internal void HandlePostConnect() => _runspacePoolDriver?.SendApplicationPrivateDataToClient(); /// /// @@ -749,20 +726,12 @@ private void HandleCreateRunspacePool(object sender, RemoteDataEventArgs createR RemoteDataObject rcvdData = createRunspaceEventArg.ReceivedData; Dbg.Assert(rcvdData != null, "rcvdData must be non-null"); - // set the PSSenderInfo sent in the first packets - // This is used by the initial session state configuration providers like Exchange. - if (Context != null) - { - _senderInfo.ClientTimeZone = Context.ClientCapability.TimeZone; - } - _senderInfo.ApplicationArguments = RemotingDecoder.GetApplicationArguments(rcvdData.Data); // Get Initial Session State from custom session config suppliers // like Exchange. ConfigurationDataFromXML configurationData = - PSSessionConfiguration.LoadEndPointConfiguration(_configProviderId, - _initParameters); + PSSessionConfiguration.LoadEndPointConfiguration(_configProviderId, _initParameters); // used by Out-Of-Proc (IPC) runspace. configurationData.InitializationScriptForOutOfProcessRunspace = _initScriptForOutOfProcRS; // start with data from configuration XML and then override with data @@ -770,8 +739,6 @@ private void HandleCreateRunspacePool(object sender, RemoteDataEventArgs createR _maxRecvdObjectSize = configurationData.MaxReceivedObjectSizeMB; _maxRecvdDataSizeCommand = configurationData.MaxReceivedCommandSizeMB; - DISCPowerShellConfiguration discProvider = null; - if (string.IsNullOrEmpty(configurationData.ConfigFilePath)) { _sessionConfigProvider = configurationData.CreateEndPointConfigurationInstance(); @@ -779,11 +746,8 @@ private void HandleCreateRunspacePool(object sender, RemoteDataEventArgs createR else { System.Security.Principal.WindowsPrincipal windowsPrincipal = new System.Security.Principal.WindowsPrincipal(_senderInfo.UserInfo.WindowsIdentity); - Func validator = (role) => windowsPrincipal.IsInRole(role); - - discProvider = new DISCPowerShellConfiguration(configurationData.ConfigFilePath, validator); - _sessionConfigProvider = discProvider; + _sessionConfigProvider = new DISCPowerShellConfiguration(configurationData.ConfigFilePath, validator); } // exchange of ApplicationArguments and ApplicationPrivateData is be done as early as possible @@ -794,6 +758,7 @@ private void HandleCreateRunspacePool(object sender, RemoteDataEventArgs createR if (configurationData.SessionConfigurationData != null) { + // Use the provided WinRM endpoint runspace configuration information. try { rsSessionStateToUse = @@ -804,8 +769,21 @@ private void HandleCreateRunspacePool(object sender, RemoteDataEventArgs createR rsSessionStateToUse = _sessionConfigProvider.GetInitialSessionState(_senderInfo); } } + else if (!string.IsNullOrEmpty(_configurationFile)) + { + // Use the optional _configurationFile parameter to create the endpoint runspace configuration. + // This parameter is only used by Out-Of-Proc transports (not WinRM transports). + var discConfiguration = new Remoting.DISCPowerShellConfiguration( + configFile: _configurationFile, + roleVerifier: null, + validateFile: true); + rsSessionStateToUse = discConfiguration.GetInitialSessionState(_senderInfo); + } else { + // Create a runspace configuration based on the provided PSSessionConfiguration provider. + // This can be either a 'default' configuration, or third party configuration PSSessionConfiguration provider object. + // So far, only Exchange provides a custom PSSessionConfiguration provider implementation. rsSessionStateToUse = _sessionConfigProvider.GetInitialSessionState(_senderInfo); } @@ -824,32 +802,14 @@ private void HandleCreateRunspacePool(object sender, RemoteDataEventArgs createR RemotingErrorIdStrings.PSSenderInfoDescription), ScopedItemOptions.ReadOnly)); - // check if the current scenario is Win7(client) to Win8(server). Add back the PSv2 version TabExpansion - // function if necessary. + // Get client PS version from PSSenderInfo. Version psClientVersion = null; if (_senderInfo.ApplicationArguments != null && _senderInfo.ApplicationArguments.ContainsKey("PSversionTable")) { var value = PSObject.Base(_senderInfo.ApplicationArguments["PSversionTable"]) as PSPrimitiveDictionary; - if (value != null) + if (value != null && value.ContainsKey("PSVersion")) { - if (value.ContainsKey("WSManStackVersion")) - { - var wsmanStackVersion = PSObject.Base(value["WSManStackVersion"]) as Version; - if (wsmanStackVersion != null && wsmanStackVersion.Major < 3) - { - // The client side is PSv2. This is the Win7 to Win8 scenario. We need to add the PSv2 - // TabExpansion function back in to keep the tab expansion functionable on the client side. - rsSessionStateToUse.Commands.Add( - new SessionStateFunctionEntry( - RemoteDataNameStrings.PSv2TabExpansionFunction, - RemoteDataNameStrings.PSv2TabExpansionFunctionText)); - } - } - - if (value.ContainsKey("PSVersion")) - { - psClientVersion = PSObject.Base(value["PSVersion"]) as Version; - } + psClientVersion = PSObject.Base(value["PSVersion"]) as Version; } } @@ -907,7 +867,7 @@ private void HandleCreateRunspacePool(object sender, RemoteDataEventArgs createR } /// - /// This handler method runs the negotiation algorithm. It decides if the negotiation is succesful, + /// This handler method runs the negotiation algorithm. It decides if the negotiation is successful, /// or fails. /// /// @@ -961,10 +921,7 @@ private void HandleNegotiationReceived(object sender, RemoteSessionNegotiationEv /// private void HandleSessionDSHandlerClosing(object sender, EventArgs eventArgs) { - if (_runspacePoolDriver != null) - { - _runspacePoolDriver.Close(); - } + _runspacePoolDriver?.Close(); // dispose the session configuration object..this will let them // clean their resources. diff --git a/src/System.Management.Automation/engine/remoting/server/serverremotesessionstatemachine.cs b/src/System.Management.Automation/engine/remoting/server/serverremotesessionstatemachine.cs index 732e5a836ab..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 /// internal class ServerRemoteSessionDSHandlerStateMachine { - [TraceSourceAttribute("ServerRemoteSessionDSHandlerStateMachine", "ServerRemoteSessionDSHandlerStateMachine")] + [TraceSource("ServerRemoteSessionDSHandlerStateMachine", "ServerRemoteSessionDSHandlerStateMachine")] private static readonly PSTraceSource s_trace = PSTraceSource.GetTracer("ServerRemoteSessionDSHandlerStateMachine", "ServerRemoteSessionDSHandlerStateMachine"); private readonly ServerRemoteSession _session; @@ -913,10 +913,7 @@ private void DoKeyExchange(object sender, RemoteSessionStateMachineEventArgs eve { // reset the timer Timer tmp = Interlocked.Exchange(ref _keyExchangeTimer, null); - if (tmp != null) - { - tmp.Dispose(); - } + tmp?.Dispose(); } // the key import would have been done @@ -984,10 +981,7 @@ private void HandleKeyExchangeTimeout(object sender) Dbg.Assert(_state == RemoteSessionState.EstablishedAndKeyRequested, "timeout should only happen when waiting for a key"); Timer tmp = Interlocked.Exchange(ref _keyExchangeTimer, null); - if (tmp != null) - { - tmp.Dispose(); - } + tmp?.Dispose(); PSRemotingDataStructureException exception = new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerKeyExchangeFailed); diff --git a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs index 275529b7b3f..5913c98829b 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; @@ -12,6 +13,7 @@ using System.Linq.Expressions; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; +using System.Management.Automation.Security; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -94,7 +96,7 @@ internal static BindingRestrictions PSGetMethodArgumentRestriction(this DynamicM { var effectiveArgType = Adapter.EffectiveArgumentType(obj.Value); var methodInfo = effectiveArgType != typeof(object[]) - ? CachedReflectionInfo.PSInvokeMemberBinder_IsHomogenousArray.MakeGenericMethod(effectiveArgType.GetElementType()) + ? CachedReflectionInfo.PSInvokeMemberBinder_IsHomogeneousArray.MakeGenericMethod(effectiveArgType.GetElementType()) : CachedReflectionInfo.PSInvokeMemberBinder_IsHeterogeneousArray; BindingRestrictions restrictions; @@ -513,7 +515,7 @@ internal static BindingRestrictions GetOptionalVersionAndLanguageCheckForType(Dy /// The standard interop ConvertBinder is used to allow third party dynamic objects to get the first chance /// at the conversion in case they do support enumeration, but do not implement IEnumerable directly. /// - internal class PSEnumerableBinder : ConvertBinder + internal sealed class PSEnumerableBinder : ConvertBinder { private static readonly PSEnumerableBinder s_binder = new PSEnumerableBinder(); @@ -550,7 +552,7 @@ private DynamicMetaObject NullResult(DynamicMetaObject target) // The object is not enumerable from PowerShell's perspective. Rather than raise an exception, we let the // caller check for null and take the appropriate action. return new DynamicMetaObject( - MaybeDebase(this, e => ExpressionCache.NullEnumerator, target), + MaybeDebase(this, static e => ExpressionCache.NullEnumerator, target), GetRestrictions(target)); } @@ -599,7 +601,7 @@ public override DynamicMetaObject FallbackConvert(DynamicMetaObject target, Dyna if (targetValue.GetType().IsArray) { return (new DynamicMetaObject( - MaybeDebase(this, e => Expression.Call(Expression.Convert(e, typeof(Array)), typeof(Array).GetMethod("GetEnumerator")), + MaybeDebase(this, static e => Expression.Call(Expression.Convert(e, typeof(Array)), typeof(Array).GetMethod("GetEnumerator")), target), GetRestrictions(target))).WriteToDebugLog(this); } @@ -674,7 +676,7 @@ public override DynamicMetaObject FallbackConvert(DynamicMetaObject target, Dyna } return (new DynamicMetaObject( - MaybeDebase(this, e => Expression.Call(CachedReflectionInfo.EnumerableOps_GetEnumerator, Expression.Convert(e, typeof(IEnumerable))), + MaybeDebase(this, static e => Expression.Call(CachedReflectionInfo.EnumerableOps_GetEnumerator, Expression.Convert(e, typeof(IEnumerable))), target), GetRestrictions(target))).WriteToDebugLog(this); } @@ -683,7 +685,7 @@ public override DynamicMetaObject FallbackConvert(DynamicMetaObject target, Dyna if (enumerator != null) { return (new DynamicMetaObject( - MaybeDebase(this, e => e.Cast(typeof(IEnumerator)), target), + MaybeDebase(this, static e => e.Cast(typeof(IEnumerator)), target), GetRestrictions(target))).WriteToDebugLog(this); } @@ -780,7 +782,7 @@ private static IEnumerator PSObjectStringRule(CallSite site, object obj) /// /// This binder is used for the @() operator. /// - internal class PSToObjectArrayBinder : DynamicMetaObjectBinder + internal sealed class PSToObjectArrayBinder : DynamicMetaObjectBinder { private static readonly PSToObjectArrayBinder s_binder = new PSToObjectArrayBinder(); @@ -825,7 +827,7 @@ public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObje if (value is List) { return new DynamicMetaObject( - Expression.Call(PSEnumerableBinder.MaybeDebase(this, e => e.Cast(typeof(List)), target), CachedReflectionInfo.ObjectList_ToArray), + Expression.Call(PSEnumerableBinder.MaybeDebase(this, static e => e.Cast(typeof(List)), target), CachedReflectionInfo.ObjectList_ToArray), PSEnumerableBinder.GetRestrictions(target)).WriteToDebugLog(this); } @@ -836,7 +838,7 @@ public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObje } } - internal class PSPipeWriterBinder : DynamicMetaObjectBinder + internal sealed class PSPipeWriterBinder : DynamicMetaObjectBinder { private static readonly PSPipeWriterBinder s_binder = new PSPipeWriterBinder(); @@ -930,7 +932,7 @@ private static void AutomationNullRule(CallSite site, object obj, Pipe pipe, Exe /// The target in this binder is the RHS, the result expression is an IList where the Count matches the /// number of values assigned (_elements) on the left hand side of the assign. /// - internal class PSArrayAssignmentRHSBinder : DynamicMetaObjectBinder + internal sealed class PSArrayAssignmentRHSBinder : DynamicMetaObjectBinder { private static readonly List s_binders = new List(); private readonly int _elements; @@ -955,7 +957,7 @@ private PSArrayAssignmentRHSBinder(int elements) public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "MultiAssignRHSBinder {0}", _elements); + return string.Create(CultureInfo.InvariantCulture, $"MultiAssignRHSBinder {_elements}"); } public override Type ReturnType { get { return typeof(IList); } } @@ -1043,13 +1045,13 @@ public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObje /// This binder is used to convert objects to string in specific circumstances, including: /// /// * The LHS of a format expression. The arguments (the RHS objects) of the format - /// expression are not converted to string here, that is defered to string.Format which + /// expression are not converted to string here, that is deferred to string.Format which /// may have some custom formatting to apply. /// * The objects passed to the format expression as part of an expandable string. In this /// case, the format string is generated by the parser, so we know that there is no custom /// formatting to consider. /// - internal class PSToStringBinder : DynamicMetaObjectBinder + internal sealed class PSToStringBinder : DynamicMetaObjectBinder { private static readonly PSToStringBinder s_binder = new PSToStringBinder(); @@ -1114,7 +1116,7 @@ internal static Expression InvokeToString(Expression context, Expression target) /// /// This binder is used to optimize the conversion of the result. /// - internal class PSPipelineResultToBoolBinder : DynamicMetaObjectBinder + internal sealed class PSPipelineResultToBoolBinder : DynamicMetaObjectBinder { private static readonly PSPipelineResultToBoolBinder s_binder = new PSPipelineResultToBoolBinder(); @@ -1175,9 +1177,9 @@ public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObje } } - internal class PSInvokeDynamicMemberBinder : DynamicMetaObjectBinder + internal sealed class PSInvokeDynamicMemberBinder : DynamicMetaObjectBinder { - private class KeyComparer : IEqualityComparer + private sealed class KeyComparer : IEqualityComparer { public bool Equals(PSInvokeDynamicMemberBinderKeyType x, PSInvokeDynamicMemberBinderKeyType y) { @@ -1286,7 +1288,7 @@ public int GetHashCode(PSGetOrSetDynamicMemberBinderKeyType obj) } } - internal class PSGetDynamicMemberBinder : DynamicMetaObjectBinder + internal sealed class PSGetDynamicMemberBinder : DynamicMetaObjectBinder { private static readonly Dictionary s_binderCache = new Dictionary(new PSDynamicGetOrSetBinderKeyComparer()); @@ -1396,7 +1398,7 @@ internal static object GetIDictionaryMember(IDictionary hash, object key) } } - internal class PSSetDynamicMemberBinder : DynamicMetaObjectBinder + internal sealed class PSSetDynamicMemberBinder : DynamicMetaObjectBinder { private static readonly Dictionary s_binderCache = new Dictionary(new PSDynamicGetOrSetBinderKeyComparer()); @@ -1488,7 +1490,7 @@ public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObje } } - internal class PSSwitchClauseEvalBinder : DynamicMetaObjectBinder + internal sealed class PSSwitchClauseEvalBinder : DynamicMetaObjectBinder { // Increase this cache size if we add a new flag to the switch statement that: // - Influences evaluation of switch elements @@ -1608,7 +1610,7 @@ public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObje // This class implements the standard binder CreateInstanceBinder, but this binder handles the CallInfo a little differently. // The ArgumentNames are not used to invoke a constructor, instead they are used to set properties/fields in the attribute. - internal class PSAttributeGenerator : CreateInstanceBinder + internal sealed class PSAttributeGenerator : CreateInstanceBinder { private static readonly Dictionary s_binderCache = new Dictionary(); @@ -1653,7 +1655,7 @@ public override DynamicMetaObject FallbackCreateInstance(DynamicMetaObject targe newConstructors, invocationConstraints: null, allowCastingToByRefLikeType: false, - args.Take(positionalArgCount).Select(arg => arg.Value).ToArray(), + args.Take(positionalArgCount).Select(static arg => arg.Value).ToArray(), ref errorId, ref errorMsg, out expandParamsOnBest, @@ -1789,7 +1791,7 @@ public override DynamicMetaObject FallbackCreateInstance(DynamicMetaObject targe } } - internal class PSCustomObjectConverter : DynamicMetaObjectBinder + internal sealed class PSCustomObjectConverter : DynamicMetaObjectBinder { private static readonly PSCustomObjectConverter s_binder = new PSCustomObjectConverter(); @@ -1825,7 +1827,7 @@ public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObje } } - internal class PSDynamicConvertBinder : DynamicMetaObjectBinder + internal sealed class PSDynamicConvertBinder : DynamicMetaObjectBinder { private static readonly PSDynamicConvertBinder s_binder = new PSDynamicConvertBinder(); @@ -1863,7 +1865,7 @@ public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObje /// /// This binder is used to copy mutable value types when assigning to variables, otherwise just assigning the target object directly. /// - internal class PSVariableAssignmentBinder : DynamicMetaObjectBinder + internal sealed class PSVariableAssignmentBinder : DynamicMetaObjectBinder { private static readonly PSVariableAssignmentBinder s_binder = new PSVariableAssignmentBinder(); internal static int s_mutableValueWithInstanceMemberVersion; @@ -2089,9 +2091,7 @@ internal static void NoteTypeHasInstanceMemberOrTypeName(Type type) internal static object CopyInstanceMembersOfValueType(T t, object boxedT) where T : struct { - PSMemberInfoInternalCollection unused1; - ConsolidatedString unused2; - if (PSObject.HasInstanceMembers(boxedT, out unused1) || PSObject.HasInstanceTypeName(boxedT, out unused2)) + if (PSObject.HasInstanceMembers(boxedT, out _) || PSObject.HasInstanceTypeName(boxedT, out _)) { var psobj = PSObject.AsPSObject(boxedT); return PSObject.Base(psobj.Copy()); @@ -2116,7 +2116,7 @@ internal static BindingRestrictions GetVersionCheck(int expectedVersionNumber) /// /// The binder for common binary operators. PowerShell specific binary operators are handled elsewhere. /// - internal class PSBinaryOperationBinder : BinaryOperationBinder + internal sealed class PSBinaryOperationBinder : BinaryOperationBinder { #region Constructors and factory methods @@ -2241,7 +2241,12 @@ public override DynamicMetaObject FallbackBinaryOperation(DynamicMetaObject targ public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "PSBinaryOperationBinder {0}{1} ver:{2}", GetOperatorText(), _scalarCompare ? " scalarOnly" : string.Empty, _version); + return string.Format( + CultureInfo.InvariantCulture, + "PSBinaryOperationBinder {0}{1} ver:{2}", + GetOperatorText(), + _scalarCompare ? " scalarOnly" : string.Empty, + _version); } internal static void InvalidateCache() @@ -2713,6 +2718,14 @@ private DynamicMetaObject BinaryAdd(DynamicMetaObject target, DynamicMetaObject lhsEnumerator.Expression.Cast(typeof(IEnumerator)), rhsEnumerator.Expression.Cast(typeof(IEnumerator))); } + else if (target.Value is object[] targetArray) + { + // Adding 1 item to an object[] + // This is an optimisation over the default EnumerableOps_AddObject. + call = Expression.Call(CachedReflectionInfo.ArrayOps_AddObject, + target.Expression.Cast(typeof(object[])), + arg.Expression.Cast(typeof(object))); + } else { // Adding 1 item to a list @@ -3187,7 +3200,7 @@ private DynamicMetaObject CompareLT(DynamicMetaObject target, } return BinaryComparisonCommon(enumerable, target, arg) - ?? BinaryComparison(target, arg, e => Expression.LessThan(e, ExpressionCache.Constant(0))); + ?? BinaryComparison(target, arg, static e => Expression.LessThan(e, ExpressionCache.Constant(0))); } private DynamicMetaObject CompareLE(DynamicMetaObject target, @@ -3207,7 +3220,7 @@ private DynamicMetaObject CompareLE(DynamicMetaObject target, } return BinaryComparisonCommon(enumerable, target, arg) - ?? BinaryComparison(target, arg, e => Expression.LessThanOrEqual(e, ExpressionCache.Constant(0))); + ?? BinaryComparison(target, arg, static e => Expression.LessThanOrEqual(e, ExpressionCache.Constant(0))); } private DynamicMetaObject CompareGT(DynamicMetaObject target, @@ -3229,7 +3242,7 @@ private DynamicMetaObject CompareGT(DynamicMetaObject target, } return BinaryComparisonCommon(enumerable, target, arg) - ?? BinaryComparison(target, arg, e => Expression.GreaterThan(e, ExpressionCache.Constant(0))); + ?? BinaryComparison(target, arg, static e => Expression.GreaterThan(e, ExpressionCache.Constant(0))); } private DynamicMetaObject CompareGE(DynamicMetaObject target, @@ -3251,7 +3264,7 @@ private DynamicMetaObject CompareGE(DynamicMetaObject target, } return BinaryComparisonCommon(enumerable, target, arg) - ?? BinaryComparison(target, arg, e => Expression.GreaterThanOrEqual(e, ExpressionCache.Constant(0))); + ?? BinaryComparison(target, arg, static e => Expression.GreaterThanOrEqual(e, ExpressionCache.Constant(0))); } private DynamicMetaObject BinaryComparison(DynamicMetaObject target, DynamicMetaObject arg, Func toResult) @@ -3411,7 +3424,7 @@ private DynamicMetaObject BinaryComparisonCommon(DynamicMetaObject targetAsEnume /// /// The binder for unary operators like !, -, or +. /// - internal class PSUnaryOperationBinder : UnaryOperationBinder + internal sealed class PSUnaryOperationBinder : UnaryOperationBinder { private static PSUnaryOperationBinder s_notBinder; private static PSUnaryOperationBinder s_bnotBinder; @@ -3490,7 +3503,7 @@ public override DynamicMetaObject FallbackUnaryOperation(DynamicMetaObject targe public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "PSUnaryOperationBinder {0}", this.Operation); + return string.Create(CultureInfo.InvariantCulture, $"PSUnaryOperationBinder {this.Operation}"); } internal DynamicMetaObject Not(DynamicMetaObject target, DynamicMetaObject errorSuggestion) @@ -3727,7 +3740,7 @@ private DynamicMetaObject IncrDecr(DynamicMetaObject target, int valueToAdd, Dyn /// /// The binder for converting a value, e.g. [int]"42" /// - internal class PSConvertBinder : ConvertBinder + internal sealed class PSConvertBinder : ConvertBinder { private static readonly Dictionary s_binderCache = new Dictionary(); internal int _version; @@ -3792,7 +3805,11 @@ public override DynamicMetaObject FallbackConvert(DynamicMetaObject target, Dyna public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "PSConvertBinder [{0}] ver:{1}", Microsoft.PowerShell.ToStringCodeMethods.Type(this.Type, true), _version); + return string.Format( + CultureInfo.InvariantCulture, + "PSConvertBinder [{0}] ver:{1}", + Microsoft.PowerShell.ToStringCodeMethods.Type(this.Type, true), + _version); } internal static void InvalidateCache() @@ -3926,7 +3943,7 @@ private static string StringToStringRule(CallSite site, object obj) /// /// The binder to get the value of an indexable object, e.g. $x[1] /// - internal class PSGetIndexBinder : GetIndexBinder + internal sealed class PSGetIndexBinder : GetIndexBinder { private static readonly Dictionary, PSGetIndexBinder> s_binderCache = new Dictionary, PSGetIndexBinder>(); @@ -3961,12 +3978,13 @@ private PSGetIndexBinder(Tuple tu public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, - "PSGetIndexBinder indexCount={0}{1}{2} ver:{3}", - this.CallInfo.ArgumentCount, - _allowSlicing ? string.Empty : " slicing disallowed", - _constraints == null ? string.Empty : " constraints: " + _constraints, - _version); + return string.Format( + CultureInfo.InvariantCulture, + "PSGetIndexBinder indexCount={0}{1}{2} ver:{3}", + this.CallInfo.ArgumentCount, + _allowSlicing ? string.Empty : " slicing disallowed", + _constraints == null ? string.Empty : " constraints: " + _constraints, + _version); } internal static void InvalidateCache() @@ -3983,13 +4001,13 @@ internal static void InvalidateCache() public override DynamicMetaObject FallbackGetIndex(DynamicMetaObject target, DynamicMetaObject[] indexes, DynamicMetaObject errorSuggestion) { - if (!target.HasValue || indexes.Any(mo => !mo.HasValue)) + if (!target.HasValue || indexes.Any(static mo => !mo.HasValue)) { return Defer(indexes.Prepend(target).ToArray()).WriteToDebugLog(this); } if ((target.Value is PSObject && (PSObject.Base(target.Value) != target.Value)) || - indexes.Any(mo => mo.Value is PSObject && (PSObject.Base(mo.Value) != mo.Value))) + indexes.Any(static mo => mo.Value is PSObject && (PSObject.Base(mo.Value) != mo.Value))) { return this.DeferForPSObject(indexes.Prepend(target).ToArray()).WriteToDebugLog(this); } @@ -4084,7 +4102,7 @@ private DynamicMetaObject CannotIndexTarget(DynamicMetaObject target, DynamicMet bindingRestrictions = bindingRestrictions.Merge(BinderUtils.GetLanguageModeCheckIfHasEverUsedConstrainedLanguage()); var call = Expression.Call(CachedReflectionInfo.ArrayOps_GetNonIndexable, target.Expression.Cast(typeof(object)), - Expression.NewArrayInit(typeof(object), indexes.Select(d => d.Expression.Cast(typeof(object))))); + Expression.NewArrayInit(typeof(object), indexes.Select(static d => d.Expression.Cast(typeof(object))))); return new DynamicMetaObject(call, bindingRestrictions); } @@ -4267,7 +4285,7 @@ private DynamicMetaObject GetIndexArray(DynamicMetaObject target, DynamicMetaObj new DynamicMetaObject(target.Expression.Cast(target.LimitType), target.PSGetTypeRestriction()), new DynamicMetaObject(indexAsInt, indexes[0].PSGetTypeRestriction()), target.LimitType.GetProperty("Length"), - (t, i) => Expression.ArrayIndex(t, i).Cast(typeof(object))); + static (t, i) => Expression.ArrayIndex(t, i).Cast(typeof(object))); } private DynamicMetaObject GetIndexMultiDimensionArray(DynamicMetaObject target, DynamicMetaObject[] indexes, DynamicMetaObject errorSuggestion) @@ -4313,7 +4331,7 @@ private DynamicMetaObject GetIndexMultiDimensionArray(DynamicMetaObject target, target.CombineRestrictions(indexes)); } - var intIndexes = indexes.Select(index => ConvertIndex(index, typeof(int))).Where(i => i != null).ToArray(); + var intIndexes = indexes.Select(static index => ConvertIndex(index, typeof(int))).Where(static i => i != null).ToArray(); if (intIndexes.Length != indexes.Length) { if (!_allowSlicing) @@ -4477,7 +4495,7 @@ private DynamicMetaObject InvokeSlicingIndexer(DynamicMetaObject target, Dynamic Expression.Call(CachedReflectionInfo.ArrayOps_SlicingIndex, target.Expression.Cast(typeof(object)), Expression.NewArrayInit(typeof(object), - indexes.Select(dmo => dmo.Expression.Cast(typeof(object)))), + indexes.Select(static dmo => dmo.Expression.Cast(typeof(object)))), Expression.Constant(GetNonSlicingIndexer())), target.CombineRestrictions(indexes)); } @@ -4517,7 +4535,7 @@ private Func GetNonSlicingIndexer() /// /// The binder for setting the value of an indexable element, like $x[1] = 5. /// - internal class PSSetIndexBinder : SetIndexBinder + internal sealed class PSSetIndexBinder : SetIndexBinder { private static readonly Dictionary, PSSetIndexBinder> s_binderCache = new Dictionary, PSSetIndexBinder>(); @@ -4550,8 +4568,12 @@ private PSSetIndexBinder(Tuple tuple) public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "PSSetIndexBinder indexCnt={0}{1} ver:{2}", - CallInfo.ArgumentCount, _constraints == null ? string.Empty : " constraints: " + _constraints, _version); + return string.Format( + CultureInfo.InvariantCulture, + "PSSetIndexBinder indexCnt={0}{1} ver:{2}", + CallInfo.ArgumentCount, + _constraints == null ? string.Empty : " constraints: " + _constraints, + _version); } internal static void InvalidateCache() @@ -4572,13 +4594,13 @@ public override DynamicMetaObject FallbackSetIndex( DynamicMetaObject value, DynamicMetaObject errorSuggestion) { - if (!target.HasValue || indexes.Any(mo => !mo.HasValue) || !value.HasValue) + if (!target.HasValue || indexes.Any(static mo => !mo.HasValue) || !value.HasValue) { return Defer(indexes.Prepend(target).Append(value).ToArray()).WriteToDebugLog(this); } if (target.Value is PSObject && (PSObject.Base(target.Value) != target.Value) || - indexes.Any(mo => mo.Value is PSObject && (PSObject.Base(mo.Value) != mo.Value))) + indexes.Any(static mo => mo.Value is PSObject && (PSObject.Base(mo.Value) != mo.Value))) { return this.DeferForPSObject(indexes.Prepend(target).Append(value).ToArray()).WriteToDebugLog(this); } @@ -4794,7 +4816,7 @@ private DynamicMetaObject SetIndexArray(DynamicMetaObject target, ParserStrings.ArraySliceAssignmentFailed, Expression.Call(CachedReflectionInfo.ArrayOps_IndexStringMessage, Expression.NewArrayInit(typeof(object), - indexes.Select(i => i.Expression.Cast(typeof(object)))))); + indexes.Select(static i => i.Expression.Cast(typeof(object)))))); } var intIndex = PSGetIndexBinder.ConvertIndex(indexes[0], typeof(int)); @@ -4816,7 +4838,7 @@ private DynamicMetaObject SetIndexArray(DynamicMetaObject target, new DynamicMetaObject(target.Expression.Cast(target.LimitType), target.PSGetTypeRestriction()), new DynamicMetaObject(intIndex, indexes[0].PSGetTypeRestriction()), new DynamicMetaObject(valueExpr, value.PSGetTypeRestriction()), target.LimitType.GetProperty("Length"), - (t, i, v) => Expression.Assign(Expression.ArrayAccess(t, i), v)); + static (t, i, v) => Expression.Assign(Expression.ArrayAccess(t, i), v)); } private DynamicMetaObject SetIndexMultiDimensionArray(DynamicMetaObject target, @@ -4859,7 +4881,7 @@ private DynamicMetaObject SetIndexMultiDimensionArray(DynamicMetaObject target, ExpressionCache.Constant(array.Rank), Expression.Call(CachedReflectionInfo.ArrayOps_IndexStringMessage, Expression.NewArrayInit(typeof(object), - indexes.Select(i => i.Expression.Cast(typeof(object)))))); + indexes.Select(static i => i.Expression.Cast(typeof(object)))))); } var indexExprs = new Expression[indexes.Length]; @@ -4887,7 +4909,7 @@ private DynamicMetaObject SetIndexMultiDimensionArray(DynamicMetaObject target, /// internal class PSGetMemberBinder : GetMemberBinder { - private class KeyComparer : IEqualityComparer + private sealed class KeyComparer : IEqualityComparer { public bool Equals(PSGetMemberBinderKeyType x, PSGetMemberBinderKeyType y) { @@ -4911,7 +4933,7 @@ public int GetHashCode(PSGetMemberBinderKeyType obj) } } - private class ReservedMemberBinder : PSGetMemberBinder + private sealed class ReservedMemberBinder : PSGetMemberBinder { internal ReservedMemberBinder(string name, bool ignoreCase, bool @static) : base(name, null, ignoreCase, @static, nonEnumerating: false) { @@ -5007,7 +5029,7 @@ internal static void SetHasInstanceMember(string memberName) // This way, we can avoid the call to TryGetInstanceMember for binders when we know there aren't any instance // members, yet invalidate those rules once somebody adds an instance member. - var binderList = s_binderCacheIgnoringCase.GetOrAdd(memberName, _ => new List()); + var binderList = s_binderCacheIgnoringCase.GetOrAdd(memberName, static _ => new List()); lock (binderList) { @@ -5038,7 +5060,7 @@ internal static void SetHasInstanceMember(string memberName) internal static void TypeTableMemberAdded(string memberName) { - var binderList = s_binderCacheIgnoringCase.GetOrAdd(memberName, _ => new List()); + var binderList = s_binderCacheIgnoringCase.GetOrAdd(memberName, static _ => new List()); lock (binderList) { @@ -5061,7 +5083,7 @@ internal static void TypeTableMemberAdded(string memberName) internal static void TypeTableMemberPossiblyUpdated(string memberName) { - var binderList = s_binderCacheIgnoringCase.GetOrAdd(memberName, _ => new List()); + var binderList = s_binderCacheIgnoringCase.GetOrAdd(memberName, static _ => new List()); lock (binderList) { @@ -5109,7 +5131,7 @@ private static PSGetMemberBinder Get(string memberName, Type classScope, bool @s result = new PSGetMemberBinder(memberName, classScope, true, @static, nonEnumerating); if (!@static) { - var binderList = s_binderCacheIgnoringCase.GetOrAdd(memberName, _ => new List()); + var binderList = s_binderCacheIgnoringCase.GetOrAdd(memberName, static _ => new List()); lock (binderList) { if (binderList.Count > 0) @@ -5146,8 +5168,13 @@ private PSGetMemberBinder(string name, Type classScope, bool ignoreCase, bool @s public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "GetMember: {0}{1}{2} ver:{3}", - Name, _static ? " static" : string.Empty, _nonEnumerating ? " nonEnumerating" : string.Empty, _version); + return string.Format( + CultureInfo.InvariantCulture, + "GetMember: {0}{1}{2} ver:{3}", + Name, + _static ? " static" : string.Empty, + _nonEnumerating ? " nonEnumerating" : string.Empty, + _version); } public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) @@ -5271,13 +5298,24 @@ 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); } - expr = Expression.Property(targetExpr, propertyAccessor); + if (propertyAccessor.PropertyType.IsByRef) + { + expr = Expression.Call( + CachedReflectionInfo.ByRefOps_GetByRefPropertyValue, + targetExpr, + Expression.Constant(propertyAccessor)); + } + else + { + expr = Expression.Property(targetExpr, propertyAccessor); + } } else { @@ -5335,13 +5373,9 @@ public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, Dy if (!isGeneric || genericTypeArg != null) { var temp = Expression.Variable(typeof(object)); - if (expr == null) - { - // If expr is not null, it's the fallback when no member exists. If it is null, - // the fallback is the result from PropertyDoesntExist. - - expr = (errorSuggestion ?? PropertyDoesntExist(target, restrictions)).Expression; - } + // If expr is not null, it's the fallback when no member exists. If it is null, + // the fallback is the result from PropertyDoesntExist. + expr ??= (errorSuggestion ?? PropertyDoesntExist(target, restrictions)).Expression; var method = isGeneric ? CachedReflectionInfo.PSGetMemberBinder_TryGetGenericDictionaryValue.MakeGenericMethod(genericTypeArg) @@ -5407,18 +5441,6 @@ internal static Expression GetTargetExpr(DynamicMetaObject target, Type castToTy var type = castToType ?? ((value != null) ? value.GetType() : typeof(object)); - // Assemblies in CoreCLR might not allow reflection execution on their internal types. In such case, we walk up - // the derivation chain to find the first public parent, and use reflection methods on the public parent. - if (!TypeResolver.IsPublic(type) && DotNetAdapter.DisallowPrivateReflection(type)) - { - var publicType = DotNetAdapter.GetFirstPublicParentType(type); - if (publicType != null) - { - type = publicType; - } - // else we'll probably fail, but the error message might be more helpful than NullReferenceException - } - if (expr.Type != type) { // Unbox value types (or use Nullable.Value) to avoid a copy in case the value is mutated. @@ -5495,15 +5517,30 @@ private Expression ThrowPropertyNotFoundStrict() new object[] { Name }); } - internal static DynamicMetaObject EnsureAllowedInLanguageMode(ExecutionContext context, DynamicMetaObject target, object targetValue, + internal static DynamicMetaObject EnsureAllowedInLanguageMode(DynamicMetaObject target, object targetValue, string name, bool isStatic, DynamicMetaObject[] args, BindingRestrictions moreTests, string errorID, string resourceString) { - if (context != null && context.LanguageMode == PSLanguageMode.ConstrainedLanguage) + var context = LocalPipeline.GetExecutionContextFromTLS(); + if (context == null) { - if (!IsAllowedInConstrainedLanguage(targetValue, name, isStatic)) + return null; + } + + if (context.LanguageMode == PSLanguageMode.ConstrainedLanguage && + !IsAllowedInConstrainedLanguage(targetValue, name, isStatic)) + { + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) { return target.ThrowRuntimeError(args, moreTests, errorID, resourceString); } + + string targetName = (targetValue as Type)?.FullName ?? targetValue?.GetType().FullName; + SystemPolicy.LogWDACAuditMessage( + context: context, + title: ParameterBinderStrings.WDACBinderInvocationLogTitle, + message: StringUtil.Format(ParameterBinderStrings.WDACBinderInvocationLogMessage, name, targetName ?? string.Empty), + fqid: "MethodOrPropertyInvocationNotAllowed", + dropIntoDebugger: true); } return null; @@ -5625,8 +5662,7 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target, canOptimize = false; - PSMemberInfo unused; - Diagnostics.Assert(!TryGetInstanceMember(target.Value, Name, out unused), + Diagnostics.Assert(!TryGetInstanceMember(target.Value, Name, out _), "shouldn't get here if there is an instance member"); PSMemberInfo memberInfo = null; @@ -5687,19 +5723,13 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target, restrictions = versionRestriction; // When returning aliasRestrictions always include the version restriction - if (aliasRestrictions != null) - { - aliasRestrictions.Add(versionRestriction); - } + aliasRestrictions?.Add(versionRestriction); var alias = memberInfo as PSAliasProperty; if (alias != null) { aliasConversionType = alias.ConversionType; - if (aliasRestrictions == null) - { - aliasRestrictions = new List(); - } + aliasRestrictions ??= new List(); memberInfo = ResolveAlias(alias, target, aliases, aliasRestrictions); if (memberInfo == null) @@ -5729,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)); } @@ -5740,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)); } @@ -5748,12 +5778,9 @@ 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)) { - if (candidateMethods == null) - { - candidateMethods = new List(); - } + candidateMethods ??= new List(); candidateMethods.Add(methodInfo); } @@ -5768,7 +5795,7 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target, if (psMethodInfo != null) { var cacheEntry = (DotNetAdapter.MethodCacheEntry)psMethodInfo.adapterData; - candidateMethods.AddRange(cacheEntry.methodInformationStructures.Select(e => e.method)); + candidateMethods.AddRange(cacheEntry.methodInformationStructures.Select(static e => e.method)); memberInfo = null; } @@ -5779,7 +5806,7 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target, } else { - DotNetAdapter.MethodCacheEntry method = new DotNetAdapter.MethodCacheEntry(candidateMethods.ToArray()); + DotNetAdapter.MethodCacheEntry method = new DotNetAdapter.MethodCacheEntry(candidateMethods); memberInfo = PSMethod.Create(this.Name, PSObject.DotNetInstanceAdapter, null, method); } } @@ -5846,10 +5873,7 @@ internal static object GetAdaptedValue(object obj, string member) } var adapterSet = PSObject.GetMappedAdapter(obj, context?.TypeTable); - if (memberInfo == null) - { - memberInfo = adapterSet.OriginalAdapter.BaseGetMember(obj, member); - } + memberInfo ??= adapterSet.OriginalAdapter.BaseGetMember(obj, member); if (memberInfo == null && adapterSet.DotNetAdapter != null) { @@ -5936,7 +5960,7 @@ internal static bool TryGetGenericDictionaryValue(IDictionary hash /// internal class PSSetMemberBinder : SetMemberBinder { - private class KeyComparer : IEqualityComparer + private sealed class KeyComparer : IEqualityComparer { public bool Equals(PSSetMemberBinderKeyType x, PSSetMemberBinderKeyType y) { @@ -5998,7 +6022,12 @@ public PSSetMemberBinder(string name, bool ignoreCase, bool @static, Type classS public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "SetMember: {0}{1} ver:{2}", _static ? "static " : string.Empty, Name, _getMemberBinder._version); + return string.Format( + CultureInfo.InvariantCulture, + "SetMember: {0}{1} ver:{2}", + _static ? "static " : string.Empty, + Name, + _getMemberBinder._version); } private static Expression GetTransformedExpression(IEnumerable transformationAttributes, Expression originalExpression) @@ -6165,10 +6194,9 @@ public override DynamicMetaObject FallbackSetMember(DynamicMetaObject target, Dy { restrictions = restrictions.Merge(BinderUtils.GetLanguageModeCheckIfHasEverUsedConstrainedLanguage()); - // Validate that this is allowed in the current language mode - var context = LocalPipeline.GetExecutionContextFromTLS(); + // Validate that this is allowed in the current language mode. DynamicMetaObject runtimeError = PSGetMemberBinder.EnsureAllowedInLanguageMode( - context, target, targetValue, Name, _static, new[] { value }, restrictions, + target, targetValue, Name, _static, new[] { value }, restrictions, "PropertySetterNotSupportedInConstrainedLanguage", ParserStrings.PropertySetConstrainedLanguage); if (runtimeError != null) { @@ -6265,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); @@ -6450,10 +6479,7 @@ internal static object SetAdaptedValue(object obj, string member, object value) } var adapterSet = PSObject.GetMappedAdapter(obj, context?.TypeTable); - if (memberInfo == null) - { - memberInfo = adapterSet.OriginalAdapter.BaseGetMember(obj, member); - } + memberInfo ??= adapterSet.OriginalAdapter.BaseGetMember(obj, member); if (memberInfo == null && adapterSet.DotNetAdapter != null) { @@ -6508,8 +6534,23 @@ public override DynamicMetaObject FallbackInvoke(DynamicMetaObject target, Dynam } } - internal class PSInvokeMemberBinder : InvokeMemberBinder + 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 s_whereSearchValues = SearchValues.Create( + ["Where", "PSWhere"], + StringComparison.OrdinalIgnoreCase); + + private static readonly SearchValues s_foreachSearchValues = SearchValues.Create( + ["ForEach", "PSForEach"], + StringComparison.OrdinalIgnoreCase); + internal enum MethodInvocationType { Ordinary, @@ -6519,7 +6560,7 @@ internal enum MethodInvocationType NonVirtual, } - private class KeyComparer : IEqualityComparer + private sealed class KeyComparer : IEqualityComparer { public bool Equals(PSInvokeMemberBinderKeyType x, PSInvokeMemberBinderKeyType y) { @@ -6605,21 +6646,27 @@ private PSInvokeMemberBinder(string name, public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, - "PSInvokeMember: {0}{1}{2} ver:{3} args:{4} constraints:<{5}>", _static ? "static " : string.Empty, _propertySetter ? "propset " : string.Empty, - Name, _getMemberBinder._version, CallInfo.ArgumentCount, _invocationConstraints != null ? _invocationConstraints.ToString() : string.Empty); + return string.Format( + CultureInfo.InvariantCulture, + "PSInvokeMember: {0}{1}{2} ver:{3} args:{4} constraints:<{5}>", + _static ? "static " : string.Empty, + _propertySetter ? "propset " : string.Empty, + Name, + _getMemberBinder._version, + CallInfo.ArgumentCount, + _invocationConstraints != null ? _invocationConstraints.ToString() : string.Empty); } public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, DynamicMetaObject[] args, DynamicMetaObject errorSuggestion) { - if (!target.HasValue || args.Any(arg => !arg.HasValue)) + if (!target.HasValue || args.Any(static arg => !arg.HasValue)) { return Defer(args.Prepend(target).ToArray()); } // Defer COM objects or arguments wrapped in PSObjects if ((target.Value is PSObject && (PSObject.Base(target.Value) != target.Value)) || - args.Any(mo => mo.Value is PSObject && (PSObject.Base(mo.Value) != mo.Value))) + args.Any(static mo => mo.Value is PSObject && (PSObject.Base(mo.Value) != mo.Value))) { object baseObject = PSObject.Base(target.Value); if (baseObject != null && Marshal.IsComObject(baseObject)) @@ -6650,14 +6697,16 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, Expression.Call(Expression.NewArrayInit(typeof(object)), CachedReflectionInfo.IEnumerable_GetEnumerator), BindingRestrictions.GetInstanceRestriction(Expression.Call(CachedReflectionInfo.PSObject_Base, target.Expression), null)) .WriteToDebugLog(this); - BindingRestrictions argRestrictions = args.Aggregate(BindingRestrictions.Empty, (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction())); + 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); } @@ -6695,7 +6744,7 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, Expression.Call(CachedReflectionInfo.PSInvokeMemberBinder_TryGetInstanceMethod, target.Expression.Cast(typeof(object)), Expression.Constant(Name), methodInfoVar), Expression.Call(methodInfoVar, CachedReflectionInfo.PSMethodInfo_Invoke, - Expression.NewArrayInit(typeof(object), args.Select(dmo => dmo.Expression.Cast(typeof(object))))), + Expression.NewArrayInit(typeof(object), args.Select(static dmo => dmo.Expression.Cast(typeof(object))))), this.GetUpdateExpression(typeof(object))); return (new DynamicMetaObject(Expression.Block(new[] { methodInfoVar }, expr), @@ -6706,7 +6755,7 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, bool canOptimize; Type aliasConversionType; var methodInfo = _getMemberBinder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType, MemberTypes.Method) as PSMethodInfo; - restrictions = args.Aggregate(restrictions, (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction())); + restrictions = args.Aggregate(restrictions, static (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction())); // If the process has ever used ConstrainedLanguage, then we need to add the language mode // to the binding restrictions, and check whether it is allowed. We can't limit @@ -6715,10 +6764,9 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, { restrictions = restrictions.Merge(BinderUtils.GetLanguageModeCheckIfHasEverUsedConstrainedLanguage()); - // Validate that this is allowed in the current language mode - var context = LocalPipeline.GetExecutionContextFromTLS(); + // Validate that this is allowed in the current language mode. DynamicMetaObject runtimeError = PSGetMemberBinder.EnsureAllowedInLanguageMode( - context, target, targetValue, Name, _static, args, restrictions, + target, targetValue, Name, _static, args, restrictions, "MethodInvocationNotSupportedInConstrainedLanguage", ParserStrings.InvokeMethodConstrainedLanguage); if (runtimeError != null) { @@ -6738,7 +6786,7 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, PSGetMemberBinder.GetTargetExpr(target, typeof(object)), Expression.Constant(Name), Expression.NewArrayInit(typeof(object), - args.Take(args.Length - 1).Select(arg => arg.Expression.Cast(typeof(object)))), + args.Take(args.Length - 1).Select(static arg => arg.Expression.Cast(typeof(object)))), args.Last().Expression.Cast(typeof(object))); } else @@ -6748,7 +6796,7 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, PSGetMemberBinder.GetTargetExpr(target, typeof(object)), Expression.Constant(Name), Expression.NewArrayInit(typeof(object), - args.Select(arg => arg.Expression.Cast(typeof(object))))); + args.Select(static arg => arg.Expression.Cast(typeof(object))))); } return new DynamicMetaObject(call, restrictions).WriteToDebugLog(this); @@ -6800,7 +6848,7 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, Expression.Constant(scriptMethod.Script), target.Expression.Cast(typeof(object)), Expression.NewArrayInit(typeof(object), - args.Select(e => e.Expression.Cast(typeof(object))))), + args.Select(static e => e.Expression.Cast(typeof(object))))), restrictions).WriteToDebugLog(this); } @@ -6838,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); } @@ -6915,6 +6963,17 @@ internal static DynamicMetaObject InvokeDotNetMethod( expr = Expression.Block(expr, ExpressionCache.AutomationNullConstant); } + if (MethodInvocationTracer.IsEnabled) + { + expr = Expression.Block( + Expression.Call( + Expression.Constant(MethodInvocationTracer), + CachedReflectionInfo.PSTraceSource_WriteLine, + Expression.Constant("Invoking method: {0}"), + Expression.Constant(result.methodDefinition)), + expr); + } + // If we're calling SteppablePipeline.{Begin|Process|End}, we don't want // to wrap exceptions - this is very much a special case to help error // propagation and ensure errors are attributed to the correct code (the @@ -6967,7 +7026,7 @@ public override DynamicMetaObject FallbackInvoke(DynamicMetaObject target, DynamicExpression.Dynamic( new PSInvokeBinder(CallInfo), typeof(object), - args.Prepend(target).Select(dmo => dmo.Expression) + args.Prepend(target).Select(static dmo => dmo.Expression) ), target.Restrictions.Merge(BindingRestrictions.Combine(args)) )); @@ -6995,7 +7054,7 @@ internal static MethodInfo FindBestMethod(DynamicMetaObject target, data.methodInformationStructures, invocationConstraints, allowCastingToByRefLikeType: true, - args.Select(arg => arg.Value == AutomationNull.Value ? null : arg.Value).ToArray(), + args.Select(static arg => arg.Value == AutomationNull.Value ? null : arg.Value).ToArray(), ref errorId, ref errorMsg, out expandParameters, @@ -7077,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(Math.Max(parameters.Length, args.Length)); for (int i = 0; i < parameters.Length; ++i) { @@ -7101,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 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 { @@ -7121,18 +7186,30 @@ 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) { argExprs[i] = Expression.Default(parameterType); } + else if (!parameters[i].HasDefaultValue && parameterType != typeof(object) && argValue == Type.Missing) + { + // If the method contains just [Optional] without a default value set then we cannot use + // Type.Missing as a placeholder. Instead we use the default value for that type. Only + // exception to this rule is when the parameter type is object. + argExprs[i] = Expression.Default(parameterType); + } else { // We don't specify the parameter type in the constant expression. Normally the default @@ -7158,17 +7235,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); } } } @@ -7185,7 +7270,7 @@ internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, CachedReflectionInfo.ClassOps_CallBaseCtor, targetExpr, Expression.Constant(constructorInfo, typeof(ConstructorInfo)), - Expression.NewArrayInit(typeof(object), argExprs.Select(x => x.Cast(typeof(object))))); + Expression.NewArrayInit(typeof(object), argExprs.Select(static x => x.Cast(typeof(object))))); } else { @@ -7202,7 +7287,7 @@ internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, : CachedReflectionInfo.ClassOps_CallMethodNonVirtually, PSGetMemberBinder.GetTargetExpr(target, methodInfo.DeclaringType), Expression.Constant(methodInfo, typeof(MethodInfo)), - Expression.NewArrayInit(typeof(object), argExprs.Select(x => x.Cast(typeof(object))))); + Expression.NewArrayInit(typeof(object), argExprs.Select(static x => x.Cast(typeof(object))))); } else { @@ -7214,6 +7299,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) @@ -7224,22 +7315,27 @@ 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; } private DynamicMetaObject InvokeMemberOnCollection(DynamicMetaObject targetEnumerator, DynamicMetaObject[] args, Type typeForMessage, BindingRestrictions restrictions) { - var d = DynamicExpression.Dynamic(this, this.ReturnType, args.Select(a => a.Expression).Prepend(ExpressionCache.NullConstant)); + var d = DynamicExpression.Dynamic(this, this.ReturnType, args.Select(static a => a.Expression).Prepend(ExpressionCache.NullConstant)); return new DynamicMetaObject( Expression.Call(CachedReflectionInfo.EnumerableOps_MethodInvoker, Expression.Constant(this.GetNonEnumeratingBinder()), Expression.Constant(d.DelegateType, typeof(Type)), targetEnumerator.Expression, Expression.NewArrayInit(typeof(object), - args.Select(a => a.Expression.Cast(typeof(object)))), + args.Select(static a => a.Expression.Cast(typeof(object)))), Expression.Constant(typeForMessage, typeof(Type)) ), targetEnumerator.Restrictions.Merge(restrictions)); @@ -7248,14 +7344,11 @@ private DynamicMetaObject InvokeMemberOnCollection(DynamicMetaObject targetEnume private static DynamicMetaObject GetTargetAsEnumerable(DynamicMetaObject target) { var enumerableTarget = PSEnumerableBinder.IsEnumerable(target); - if (enumerableTarget == null) - { - // Wrap the target in an array. - enumerableTarget = PSEnumerableBinder.IsEnumerable( - new DynamicMetaObject( - Expression.NewArrayInit(typeof(object), target.Expression.Cast(typeof(object))), - target.GetSimpleTypeRestriction())); - } + // If null wrap the target in an array. + enumerableTarget ??= PSEnumerableBinder.IsEnumerable( + new DynamicMetaObject( + Expression.NewArrayInit(typeof(object), target.Expression.Cast(typeof(object))), + target.GetSimpleTypeRestriction())); return enumerableTarget; } @@ -7340,7 +7433,7 @@ private DynamicMetaObject InvokeForEachOnCollection(DynamicMetaObject targetEnum if (args.Length > 1) { argsToPass = Expression.NewArrayInit(typeof(object), - args.Skip(1).Select(a => a.Expression.Cast(typeof(object)))); + args.Skip(1).Select(static a => a.Expression.Cast(typeof(object)))); } else { @@ -7355,18 +7448,22 @@ private DynamicMetaObject InvokeForEachOnCollection(DynamicMetaObject targetEnum #region Runtime helpers - internal static bool IsHomogenousArray(object[] args) + internal static bool IsHomogeneousArray(object[] args) { if (args.Length == 0) { return false; } - return args.All(element => - { - var obj = PSObject.Base(element); - return obj != null && obj.GetType().Equals(typeof(T)); - }); + foreach (object element in args) + { + if (Adapter.GetObjectType(element, debase: true) != typeof(T)) + { + return false; + } + } + + return true; } internal static bool IsHeterogeneousArray(object[] args) @@ -7394,11 +7491,15 @@ internal static bool IsHeterogeneousArray(object[] args) return true; } - return args.Skip(1).Any(element => - { - var obj = PSObject.Base(element); - return obj == null || !firstType.Equals(obj.GetType()); - }); + for (int i = 1; i < args.Length; i++) + { + if (Adapter.GetObjectType(args[i], debase: true) != firstType) + { + return true; + } + } + + return false; } internal static object InvokeAdaptedMember(object obj, string methodName, object[] args) @@ -7420,7 +7521,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) @@ -7436,10 +7537,23 @@ 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(); - return EnumerableOps.ForEach(enumerator, args[0], Array.Empty()); + object[] argsToPass; + + if (args.Length > 1) + { + int length = args.Length - 1; + argsToPass = new object[length]; + Array.Copy(args, sourceIndex: 1, argsToPass, destinationIndex: 0, length: length); + } + else + { + argsToPass = Array.Empty(); + } + + return EnumerableOps.ForEach(enumerator, args[0], argsToPass); } throw InterpreterError.NewInterpreterException(methodName, typeof(RuntimeException), null, @@ -7499,6 +7613,55 @@ internal static void InvalidateCache() } } +#nullable enable + private static Expression AddMemberInvocationLogging( + Expression expr, + string targetName, + string name, + List 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 exprs, + string targetName, + string name, + List 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 } @@ -7509,7 +7672,7 @@ internal class PSCreateInstanceBinder : CreateInstanceBinder private readonly bool _publicTypeOnly; private int _version; - private class KeyComparer : IEqualityComparer> + private sealed class KeyComparer : IEqualityComparer> { public bool Equals(Tuple x, Tuple y) @@ -7569,13 +7732,17 @@ internal PSCreateInstanceBinder(CallInfo callInfo, PSMethodInvocationConstraints public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, - "PSCreateInstanceBinder: ver:{0} args:{1} constraints:<{2}>", _version, _callInfo.ArgumentCount, _constraints != null ? _constraints.ToString() : string.Empty); + return string.Format( + CultureInfo.InvariantCulture, + "PSCreateInstanceBinder: ver:{0} args:{1} constraints:<{2}>", + _version, + _callInfo.ArgumentCount, + _constraints != null ? _constraints.ToString() : string.Empty); } public override DynamicMetaObject FallbackCreateInstance(DynamicMetaObject target, DynamicMetaObject[] args, DynamicMetaObject errorSuggestion) { - if (!target.HasValue || args.Any(arg => !arg.HasValue)) + if (!target.HasValue || args.Any(static arg => !arg.HasValue)) { return Defer(args.Prepend(target).ToArray()); } @@ -7636,10 +7803,21 @@ public override DynamicMetaObject FallbackCreateInstance(DynamicMetaObject targe var context = LocalPipeline.GetExecutionContextFromTLS(); if (context != null && context.LanguageMode == PSLanguageMode.ConstrainedLanguage && !CoreTypes.Contains(instanceType)) { - return target.ThrowRuntimeError(restrictions, "CannotCreateTypeConstrainedLanguage", ParserStrings.CannotCreateTypeConstrainedLanguage).WriteToDebugLog(this); + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + return target.ThrowRuntimeError(restrictions, "CannotCreateTypeConstrainedLanguage", ParserStrings.CannotCreateTypeConstrainedLanguage).WriteToDebugLog(this); + } + + string targetName = instanceType?.FullName; + SystemPolicy.LogWDACAuditMessage( + context: context, + title: ParameterBinderStrings.WDACBinderTypeCreationLogTitle, + message: StringUtil.Format(ParameterBinderStrings.WDACBinderTypeCreationLogMessage, targetName ?? string.Empty), + fqid: "TypeCreationNotAllowed", + dropIntoDebugger: true); } - restrictions = args.Aggregate(restrictions, (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction())); + restrictions = args.Aggregate(restrictions, static (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction())); var newConstructors = DotNetAdapter.GetMethodInformationArray(ctors); return PSInvokeMemberBinder.InvokeDotNetMethod(_callInfo, "new", _constraints, PSInvokeMemberBinder.MethodInvocationType.Ordinary, target, args, restrictions, newConstructors, typeof(MethodException)).WriteToDebugLog(this); @@ -7687,7 +7865,7 @@ internal class PSInvokeBaseCtorBinder : InvokeMemberBinder private readonly CallInfo _callInfo; private readonly PSMethodInvocationConstraints _constraints; - private class KeyComparer : IEqualityComparer> + private sealed class KeyComparer : IEqualityComparer> { public bool Equals(Tuple x, Tuple y) @@ -7733,7 +7911,7 @@ internal PSInvokeBaseCtorBinder(CallInfo callInfo, PSMethodInvocationConstraints public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, DynamicMetaObject[] args, DynamicMetaObject errorSuggestion) { - if (!target.HasValue || args.Any(arg => !arg.HasValue)) + if (!target.HasValue || args.Any(static arg => !arg.HasValue)) { return Defer(args.Prepend(target).ToArray()); } @@ -7743,8 +7921,8 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, var restrictions = target.Value is PSObject ? BindingRestrictions.GetTypeRestriction(target.Expression, target.Value.GetType()) : target.PSGetTypeRestriction(); - restrictions = args.Aggregate(restrictions, (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction())); - var newConstructors = DotNetAdapter.GetMethodInformationArray(ctors.Where(c => c.IsPublic || c.IsFamily).ToArray()); + restrictions = args.Aggregate(restrictions, static (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction())); + 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)); } @@ -7755,7 +7933,7 @@ public override DynamicMetaObject FallbackInvoke(DynamicMetaObject target, Dynam DynamicExpression.Dynamic( new PSInvokeBinder(CallInfo), typeof(object), - args.Prepend(target).Select(dmo => dmo.Expression) + args.Prepend(target).Select(static dmo => dmo.Expression) ), target.Restrictions.Merge(BindingRestrictions.Combine(args)) )); diff --git a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs index 6478e9b091d..2569dd3a492 100644 --- a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs +++ b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs @@ -10,13 +10,12 @@ using System.Management.Automation.Internal; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; +using System.Management.Automation.Security; using System.Management.Automation.Tracing; using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Security.Cryptography.X509Certificates; using System.Text; -using System.Threading.Tasks; #if LEGACYTELEMETRY using Microsoft.PowerShell.Telemetry.Internal; #endif @@ -35,6 +34,7 @@ internal enum ScriptBlockClauseToInvoke Begin, Process, End, + Clean, ProcessBlockOnly, } @@ -188,7 +188,10 @@ private void ReallyCompile(bool optimize) TelemetryAPI.ReportScriptTelemetry((Ast)_ast, !optimize, sw.ElapsedMilliseconds); } #endif - if (etwEnabled) ParserEventSource.Log.CompileStop(); + if (etwEnabled) + { + ParserEventSource.Log.CompileStop(); + } } private void PerformSecurityChecks() @@ -247,6 +250,7 @@ bool IsScriptBlockInFactASafeHashtable() if (scriptBlockAst.BeginBlock != null || scriptBlockAst.ProcessBlock != null + || scriptBlockAst.CleanBlock != null || scriptBlockAst.ParamBlock != null || scriptBlockAst.DynamicParamBlock != null || scriptBlockAst.ScriptRequirements != null @@ -316,6 +320,8 @@ private IParameterMetadataProvider DelayParseScriptText() internal Dictionary NameToIndexMap { get; set; } + #region Named Blocks + internal Action DynamicParamBlock { get; set; } internal Action UnoptimizedDynamicParamBlock { get; set; } @@ -332,6 +338,12 @@ private IParameterMetadataProvider DelayParseScriptText() internal Action UnoptimizedEndBlock { get; set; } + internal Action CleanBlock { get; set; } + + internal Action UnoptimizedCleanBlock { get; set; } + + #endregion Named Blocks + internal IScriptExtent[] SequencePoints { get; set; } private RuntimeDefinedParameterDictionary _runtimeDefinedParameterDictionary; @@ -358,10 +370,7 @@ internal bool IsProductCode { get { - if (_isProductCode == null) - { - _isProductCode = SecuritySupport.IsProductBinary(((Ast)_ast).Extent.File); - } + _isProductCode ??= SecuritySupport.IsProductBinary(((Ast)_ast).Extent.File); return _isProductCode.Value; } @@ -440,7 +449,7 @@ internal CmdletBindingAttribute CmdletBindingAttribute } return _usesCmdletBinding - ? (CmdletBindingAttribute)Array.Find(_attributes, attr => attr is CmdletBindingAttribute) + ? (CmdletBindingAttribute)Array.Find(_attributes, static attr => attr is CmdletBindingAttribute) : null; } } @@ -454,7 +463,7 @@ internal ObsoleteAttribute ObsoleteAttribute InitializeMetadata(); } - return (ObsoleteAttribute)Array.Find(_attributes, attr => attr is ObsoleteAttribute); + return (ObsoleteAttribute)Array.Find(_attributes, static attr => attr is ObsoleteAttribute); } } @@ -530,8 +539,7 @@ public override string ToString() } } - [Serializable] - public partial class ScriptBlock : ISerializable + public partial class ScriptBlock { private readonly CompiledScriptBlockData _scriptBlockData; @@ -564,6 +572,7 @@ private ScriptBlock(CompiledScriptBlockData scriptBlockData) /// /// Protected constructor to support ISerializable. /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ScriptBlock(SerializationInfo info, StreamingContext context) { } @@ -612,8 +621,8 @@ internal static void CacheScriptBlock(ScriptBlock scriptBlock, string fileName, // TODO(sevoroby): we can optimize it to ignore 'using' if there are no actual type usage in locally defined types. // using is always a top-level statements in scriptBlock, we don't need to search in child blocks. - if (scriptBlock.Ast.Find(ast => IsUsingTypes(ast), false) != null - || scriptBlock.Ast.Find(ast => IsDynamicKeyword(ast), true) != null) + if (scriptBlock.Ast.Find(static ast => IsUsingTypes(ast), false) != null + || scriptBlock.Ast.Find(static ast => IsDynamicKeyword(ast), true) != null) { return; } @@ -703,21 +712,6 @@ internal string ToStringWithDollarUsingHandling( return sbText; } - /// - /// Support for . - /// - public virtual void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw PSTraceSource.NewArgumentNullException(nameof(info)); - } - - string serializedContent = this.ToString(); - info.AddValue("ScriptText", serializedContent); - info.SetType(typeof(ScriptBlockSerializationHelper)); - } - internal PowerShell GetPowerShellImpl( ExecutionContext context, Dictionary variables, @@ -751,9 +745,9 @@ internal SteppablePipeline GetSteppablePipelineImpl(CommandOrigin commandOrigin, private PipelineAst GetSimplePipeline(Func errorHandler) { - errorHandler ??= (_ => null); + errorHandler ??= (static _ => null); - if (HasBeginBlock || HasProcessBlock) + if (HasBeginBlock || HasProcessBlock || HasCleanBlock) { return errorHandler(AutomationExceptions.CanConvertOneClauseOnly); } @@ -891,7 +885,10 @@ public void CheckRestrictedLanguage( Parser parser = new Parser(); var ast = AstInternal; - if (HasBeginBlock || HasProcessBlock || ast.Body.ParamBlock != null) + if (HasBeginBlock + || HasProcessBlock + || HasCleanBlock + || ast.Body.ParamBlock is not null) { Ast errorAst = ast.Body.BeginBlock ?? (Ast)ast.Body.ProcessBlock ?? ast.Body.ParamBlock; parser.ReportError( @@ -974,6 +971,11 @@ internal void InvokeWithPipeImpl( InvocationInfo invocationInfo, params object[] args) { + if (clauseToInvoke == ScriptBlockClauseToInvoke.Clean) + { + throw new PSNotSupportedException(ParserStrings.InvokingCleanBlockNotSupported); + } + if ((clauseToInvoke == ScriptBlockClauseToInvoke.Begin && !HasBeginBlock) || (clauseToInvoke == ScriptBlockClauseToInvoke.Process && !HasProcessBlock) || (clauseToInvoke == ScriptBlockClauseToInvoke.End && !HasEndBlock)) @@ -991,7 +993,7 @@ internal void InvokeWithPipeImpl( throw new PipelineStoppedException(); } - // Validate at the arguments are consistent. The only public API that gets you here never sets createLocalScope to false... + // Validate that the arguments are consistent. The only public API that gets you here never sets createLocalScope to false... Diagnostics.Assert( createLocalScope || functionsToDefine == null, "When calling ScriptBlock.InvokeWithContext(), if 'functionsToDefine' != null then 'createLocalScope' must be true"); @@ -999,10 +1001,7 @@ internal void InvokeWithPipeImpl( createLocalScope || variablesToDefine == null, "When calling ScriptBlock.InvokeWithContext(), if 'variablesToDefine' != null then 'createLocalScope' must be true"); - if (args == null) - { - args = Array.Empty(); - } + args ??= Array.Empty(); bool runOptimized = context._debuggingMode <= 0 && createLocalScope; var codeToInvoke = GetCodeToInvoke(ref runOptimized, clauseToInvoke); @@ -1011,11 +1010,8 @@ internal void InvokeWithPipeImpl( return; } - if (outputPipe == null) - { - // If we don't have a pipe to write to, we need to discard all results. - outputPipe = new Pipe { NullPipe = true }; - } + // If we don't have a pipe to write to, we need to discard all results. + outputPipe ??= new Pipe { NullPipe = true }; var locals = MakeLocalsTuple(runOptimized); @@ -1041,12 +1037,11 @@ internal void InvokeWithPipeImpl( var oldScopeOrigin = context.EngineSessionState.CurrentScope.ScopeOrigin; var oldSessionState = context.EngineSessionState; - // If the script block has a different language mode than the current, + // If the script block has a different language mode than the current context, // change the language mode. PSLanguageMode? oldLanguageMode = null; PSLanguageMode? newLanguageMode = null; - if (this.LanguageMode.HasValue - && this.LanguageMode != context.LanguageMode) + if (this.LanguageMode.HasValue && this.LanguageMode != context.LanguageMode) { // Don't allow context: ConstrainedLanguage -> FullLanguage transition if // this is dot sourcing into the current scope, unless it is within a trusted module scope. @@ -1057,6 +1052,20 @@ internal void InvokeWithPipeImpl( oldLanguageMode = context.LanguageMode; newLanguageMode = this.LanguageMode; } + else if (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Audit) + { + string scriptBlockId = this.GetFileName() ?? string.Empty; + SystemPolicy.LogWDACAuditMessage( + context: context, + title: AutomationExceptions.WDACCompiledScriptBlockLogTitle, + message: StringUtil.Format(AutomationExceptions.WDACCompiledScriptBlockLogMessage, scriptBlockId, this.LanguageMode, context.LanguageMode), + fqid: "ScriptBlockDotSourceNotAllowed", + dropIntoDebugger: true); + + // Since we are in audit mode, go ahead and allow the language transition. + oldLanguageMode = context.LanguageMode; + newLanguageMode = this.LanguageMode; + } } Dictionary backupWhenDotting = null; @@ -1195,7 +1204,7 @@ internal void InvokeWithPipeImpl( _sequencePoints = SequencePoints, }; - ScriptBlock.LogScriptBlockStart(this, context.CurrentRunspace.InstanceId); + LogScriptBlockStart(this, context.CurrentRunspace.InstanceId); try { @@ -1203,7 +1212,7 @@ internal void InvokeWithPipeImpl( } finally { - ScriptBlock.LogScriptBlockEnd(this, context.CurrentRunspace.InstanceId); + LogScriptBlockEnd(this, context.CurrentRunspace.InstanceId); } } catch (TargetInvocationException tie) @@ -1362,7 +1371,7 @@ internal static void SetAutomaticVariable(AutomaticVariable variable, object val private Action GetCodeToInvoke(ref bool optimized, ScriptBlockClauseToInvoke clauseToInvoke) { if (clauseToInvoke == ScriptBlockClauseToInvoke.ProcessBlockOnly - && (HasBeginBlock || (HasEndBlock && HasProcessBlock))) + && (HasBeginBlock || HasCleanBlock || (HasEndBlock && HasProcessBlock))) { throw PSTraceSource.NewInvalidOperationException(AutomationExceptions.ScriptBlockInvokeOnOneClauseOnly); } @@ -1379,6 +1388,8 @@ private Action GetCodeToInvoke(ref bool optimized, ScriptBlockC return _scriptBlockData.ProcessBlock; case ScriptBlockClauseToInvoke.End: return _scriptBlockData.EndBlock; + case ScriptBlockClauseToInvoke.Clean: + return _scriptBlockData.CleanBlock; default: return HasProcessBlock ? _scriptBlockData.ProcessBlock : _scriptBlockData.EndBlock; } @@ -1392,6 +1403,8 @@ private Action GetCodeToInvoke(ref bool optimized, ScriptBlockC return _scriptBlockData.UnoptimizedProcessBlock; case ScriptBlockClauseToInvoke.End: return _scriptBlockData.UnoptimizedEndBlock; + case ScriptBlockClauseToInvoke.Clean: + return _scriptBlockData.UnoptimizedCleanBlock; default: return HasProcessBlock ? _scriptBlockData.UnoptimizedProcessBlock : _scriptBlockData.UnoptimizedEndBlock; } @@ -1438,7 +1451,7 @@ internal static void LogScriptBlockCreation(ScriptBlock scriptBlock, bool force) // But split the segments into random sizes (10k + between 0 and 10kb extra) // so that attackers can't creatively force their scripts to span well-known // segments (making simple rules less reliable). - int segmentSize = 10000 + (new Random()).Next(10000); + int segmentSize = 10000 + Random.Shared.Next(10000); int segments = (int)Math.Floor((double)(scriptBlockText.Length / segmentSize)) + 1; int currentLocation = 0; int currentSegmentSize = 0; @@ -1733,7 +1746,7 @@ private static bool GetAndValidateEncryptionRecipients( private static CmsMessageRecipient[] s_encryptionRecipients = null; private static readonly Lazy s_sbLoggingSettingCache = new Lazy( - () => Utils.GetPolicySetting(Utils.SystemWideThenCurrentUserConfig), + static () => Utils.GetPolicySetting(Utils.SystemWideThenCurrentUserConfig), isThreadSafe: true); // Reset any static caches if the certificate has changed @@ -1957,7 +1970,7 @@ private static string LookupHash(uint h) /// /// If a hash matches, we ignore the possibility of a /// collision. If the hash is acceptable, collisions will - /// be infrequent and we'll just log an occasionaly script + /// be infrequent and we'll just log an occasional script /// that isn't really suspicious. /// /// The string matching the hash, or null. @@ -2022,7 +2035,7 @@ public static string Match(string text) continue; } - for (int j = Math.Min(i, runningHash.Length) - 1; j > 0; j--) + for (int j = Math.Min(i, runningHash.Length - 1); j > 0; j--) { // Say our input is: `Emit` (our shortest pattern, len 4). // Towards the end just before matching, we will: @@ -2043,7 +2056,10 @@ public static string Match(string text) if (++longestPossiblePattern >= 4) { var result = CheckForMatches(runningHash, longestPossiblePattern); - if (result != null) return result; + if (result != null) + { + return result; + } } } @@ -2147,46 +2163,17 @@ internal static void LogScriptBlockEnd(ScriptBlock scriptBlock, Guid runspaceId) internal Action UnoptimizedEndBlock { get => _scriptBlockData.UnoptimizedEndBlock; } + internal Action CleanBlock { get => _scriptBlockData.CleanBlock; } + + internal Action UnoptimizedCleanBlock { get => _scriptBlockData.UnoptimizedCleanBlock; } + internal bool HasBeginBlock { get => AstInternal.Body.BeginBlock != null; } internal bool HasProcessBlock { get => AstInternal.Body.ProcessBlock != null; } internal bool HasEndBlock { get => AstInternal.Body.EndBlock != null; } - } - - [Serializable] - internal class ScriptBlockSerializationHelper : ISerializable, IObjectReference - { - private readonly string _scriptText; - private ScriptBlockSerializationHelper(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - _scriptText = info.GetValue("ScriptText", typeof(string)) as string; - if (_scriptText == null) - { - throw PSTraceSource.NewArgumentNullException(nameof(info)); - } - } - - /// - /// Returns a script block that corresponds to the version deserialized. - /// - /// The streaming context for this instance. - /// A script block that corresponds to the version deserialized. - public object GetRealObject(StreamingContext context) => ScriptBlock.Create(_scriptText); - - /// - /// Implements the ISerializable contract for serializing a scriptblock. - /// - /// Serialization information for this instance. - /// The streaming context for this instance. - public virtual void GetObjectData(SerializationInfo info, StreamingContext context) - => throw new NotSupportedException(); + internal bool HasCleanBlock { get => AstInternal.Body.CleanBlock != null; } } internal sealed class PSScriptCmdlet : PSCmdlet, IDynamicParameters, IDisposable @@ -2197,11 +2184,13 @@ internal sealed class PSScriptCmdlet : PSCmdlet, IDynamicParameters, IDisposable private readonly bool _useLocalScope; private readonly bool _runOptimized; private readonly bool _rethrowExitException; - private MshCommandRuntime _commandRuntime; private readonly MutableTuple _localsTuple; - private bool _exitWasCalled; private readonly FunctionContext _functionContext; + private MshCommandRuntime _commandRuntime; + private bool _exitWasCalled; + private bool _anyClauseExecuted; + public PSScriptCmdlet(ScriptBlock scriptBlock, bool useNewScope, bool fromScriptFile, ExecutionContext context) { _scriptBlock = scriptBlock; @@ -2291,6 +2280,34 @@ internal override void DoEndProcessing() } } + internal override void DoCleanResource() + { + if (_scriptBlock.HasCleanBlock && _anyClauseExecuted) + { + // The 'Clean' block doesn't write any output to pipeline, so we use a 'NullPipe' here and + // disallow the output to be collected by an 'out' variable. However, the error, warning, + // and information records should still be collectable by the corresponding variables. + Pipe oldOutputPipe = _commandRuntime.OutputPipe; + _functionContext._outputPipe = _commandRuntime.OutputPipe = new Pipe + { + NullPipe = true, + IgnoreOutVariableList = true, + }; + + try + { + RunClause( + clause: _runOptimized ? _scriptBlock.CleanBlock : _scriptBlock.UnoptimizedCleanBlock, + dollarUnderbar: AutomationNull.Value, + inputToProcess: AutomationNull.Value); + } + finally + { + _functionContext._outputPipe = _commandRuntime.OutputPipe = oldOutputPipe; + } + } + } + private void EnterScope() { _commandRuntime.SetVariableListsInPipe(); @@ -2303,14 +2320,15 @@ private void ExitScope() private void RunClause(Action clause, object dollarUnderbar, object inputToProcess) { + _anyClauseExecuted = true; Pipe oldErrorOutputPipe = this.Context.ShellFunctionErrorOutputPipe; // If the script block has a different language mode than the current, // change the language mode. PSLanguageMode? oldLanguageMode = null; PSLanguageMode? newLanguageMode = null; - if (_scriptBlock.LanguageMode.HasValue - && _scriptBlock.LanguageMode != Context.LanguageMode) + if (_scriptBlock.LanguageMode.HasValue && + _scriptBlock.LanguageMode != Context.LanguageMode) { oldLanguageMode = Context.LanguageMode; newLanguageMode = _scriptBlock.LanguageMode; @@ -2356,9 +2374,9 @@ private void RunClause(Action clause, object dollarUnderbar, ob } finally { - this.Context.RestoreErrorPipe(oldErrorOutputPipe); + Context.ShellFunctionErrorOutputPipe = oldErrorOutputPipe; - // Set the language mode + // Restore the language mode if (oldLanguageMode.HasValue) { Context.LanguageMode = oldLanguageMode.Value; @@ -2471,6 +2489,11 @@ private void SetPreferenceVariables() _localsTuple.SetPreferenceVariable(PreferenceVariable.Information, _commandRuntime.InformationPreference); } + if (_commandRuntime.IsProgressActionSet) + { + _localsTuple.SetPreferenceVariable(PreferenceVariable.Progress, _commandRuntime.ProgressPreference); + } + if (_commandRuntime.IsWhatIfFlagSet) { _localsTuple.SetPreferenceVariable(PreferenceVariable.WhatIf, _commandRuntime.WhatIf); @@ -2518,9 +2541,6 @@ public void Dispose() commandRuntime = null; currentObjectInPipeline = null; _input.Clear(); - // _scriptBlock = null; - // _localsTuple = null; - // _functionContext = null; base.InternalDispose(true); _disposed = true; diff --git a/src/System.Management.Automation/engine/runtime/MutableTuple.cs b/src/System.Management.Automation/engine/runtime/MutableTuple.cs index e7ee0d615df..53c40ef3810 100644 --- a/src/System.Management.Automation/engine/runtime/MutableTuple.cs +++ b/src/System.Management.Automation/engine/runtime/MutableTuple.cs @@ -281,7 +281,11 @@ public static int GetSize(Type tupleType) // ContractUtils.RequiresNotNull(tupleType, "tupleType"); int count = 0; - lock (s_sizeDict) if (s_sizeDict.TryGetValue(tupleType, out count)) return count; + lock (s_sizeDict) if (s_sizeDict.TryGetValue(tupleType, out count)) + { + return count; + } + Stack types = new Stack(tupleType.GetGenericArguments()); while (types.Count != 0) @@ -298,7 +302,10 @@ public static int GetSize(Type tupleType) continue; } - if (t == typeof(DynamicNull)) continue; + if (t == typeof(DynamicNull)) + { + continue; + } count++; } @@ -369,7 +376,7 @@ internal static IEnumerable GetAccessProperties(Type tupleType, in foreach (int curIndex in GetAccessPath(size, index)) { - PropertyInfo pi = tupleType.GetProperty("Item" + string.Format(CultureInfo.InvariantCulture, "{0:D3}", curIndex)); + PropertyInfo pi = tupleType.GetProperty("Item" + string.Create(CultureInfo.InvariantCulture, $"{curIndex:D3}")); Diagnostics.Assert(pi != null, "reflection should always find Item"); yield return pi; tupleType = pi.PropertyType; @@ -440,7 +447,7 @@ private static MutableTuple MakeTuple(Func creator, Type tupleType for (int i = 0; i < size; i++) { - PropertyInfo pi = tupleType.GetProperty("Item" + string.Format(CultureInfo.InvariantCulture, "{0:D3}", i)); + PropertyInfo pi = tupleType.GetProperty("Item" + string.Create(CultureInfo.InvariantCulture, $"{i:D3}")); res.SetValueImpl(i, MakeTuple(pi.PropertyType, null, null)); } } @@ -504,7 +511,7 @@ public abstract int Capacity /// public static Expression Create(params Expression[] values) { - return CreateNew(MakeTupleType(values.Select(x => x.Type).ToArray()), 0, values.Length, values); + return CreateNew(MakeTupleType(values.Select(static x => x.Type).ToArray()), 0, values.Length, values); } private static int PowerOfTwoRound(int value) @@ -540,7 +547,7 @@ internal static Expression CreateNew(Type tupleType, int start, int end, Express int newStart = start + (i * multiplier); int newEnd = System.Math.Min(end, start + ((i + 1) * multiplier)); - PropertyInfo pi = tupleType.GetProperty("Item" + string.Format(CultureInfo.InvariantCulture, "{0:D3}", i)); + PropertyInfo pi = tupleType.GetProperty("Item" + string.Create(CultureInfo.InvariantCulture, $"{i:D3}")); newValues[i] = CreateNew(pi.PropertyType, newStart, newEnd, values); } @@ -564,7 +571,7 @@ internal static Expression CreateNew(Type tupleType, int start, int end, Express } } - return Expression.New(tupleType.GetConstructor(newValues.Select(x => x.Type).ToArray()), newValues); + return Expression.New(tupleType.GetConstructor(newValues.Select(static x => x.Type).ToArray()), newValues); } } diff --git a/src/System.Management.Automation/engine/runtime/Operations/ArrayOps.cs b/src/System.Management.Automation/engine/runtime/Operations/ArrayOps.cs index fa4c2c2b565..4fd68cdd201 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/ArrayOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/ArrayOps.cs @@ -12,6 +12,15 @@ namespace System.Management.Automation { internal static class ArrayOps { + internal static object AddObjectArray(object[] lhs, object rhs) + { + int newIdx = lhs.Length; + Array.Resize(ref lhs, newIdx + 1); + lhs[newIdx] = rhs; + + return lhs; + } + internal static object[] SlicingIndex(object target, object[] indexes, Func indexer) { var result = new object[indexes.Length]; diff --git a/src/System.Management.Automation/engine/runtime/Operations/ClassOps.cs b/src/System.Management.Automation/engine/runtime/Operations/ClassOps.cs index 5afe23a6362..2b741a29c31 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/ClassOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/ClassOps.cs @@ -22,7 +22,7 @@ namespace System.Management.Automation.Internal /// /// Every Runspace in one process contains SessionStateInternal per module (module SessionState). /// Every RuntimeType is associated to only one SessionState in the Runspace, which creates it: - /// it's ever global state or a module state. + /// it's either global state or a module state. /// In the former case, module can be imported from the different runspaces in the same process. /// And so runspaces will share RuntimeType. But in every runspace, Type is associated with just one SessionState. /// We want type methods to be able access $script: variables and module-specific methods. @@ -115,10 +115,10 @@ public class ScriptBlockMemberMethodWrapper /// We use WeakReference object to point to the default SessionState because if GC already collect the SessionState, /// or the Runspace it chains to is closed and disposed, then we cannot run the static method there anyways. /// - /// + /// /// The default SessionState is used only if a static method is called from a Runspace where the PowerShell class is /// never defined, or is called on a thread without a default Runspace. Usage like those should be rare. - /// + /// private readonly WeakReference _defaultSessionStateToUse; /// @@ -162,7 +162,7 @@ internal ScriptBlockMemberMethodWrapper(IParameterMetadataProvider ast) /// Initialization happens when the script that defines PowerShell class is executed. /// This initialization is required only if this wrapper is for a static method. /// - /// + /// /// When the same script file gets executed multiple times, the .NET type generated from the PowerShell class /// defined in the file will be shared in those executions, and thus this method will be called multiple times /// possibly in the contexts of different Runspace/SessionState. @@ -174,7 +174,7 @@ internal ScriptBlockMemberMethodWrapper(IParameterMetadataProvider ast) /// is declared, and thus we can always get the correct SessionState to use by querying the 'SessionStateKeeper'. /// The default SessionState is used only if a static method is called from a Runspace where the class is never /// defined, or is called on a thread without a default Runspace. - /// + /// internal void InitAtRuntime() { if (_isStatic) @@ -356,7 +356,7 @@ private static DynamicMethod CreateDynamicMethod(MethodInfo mi) { // Pass in the declaring type because instance method has a hidden parameter 'this' as the first parameter. var paramTypes = new List { mi.DeclaringType }; - paramTypes.AddRange(mi.GetParameters().Select(x => x.ParameterType)); + paramTypes.AddRange(mi.GetParameters().Select(static x => x.ParameterType)); var dm = new DynamicMethod("PSNonVirtualCall_" + mi.Name, mi.ReturnType, paramTypes.ToArray(), mi.DeclaringType); ILGenerator il = dm.GetILGenerator(); diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index df8b36eb40f..ddc70fabd50 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -13,6 +13,8 @@ using System.Management.Automation.Internal.Host; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; +using System.Management.Automation.Security; +using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; @@ -53,13 +55,24 @@ private static CommandProcessorBase AddCommand(PipelineProcessor pipe, throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException), null, "CantInvokeInNonImportedModule", ParserStrings.CantInvokeInNonImportedModule, mi.Name); } - else if (((invocationToken == TokenKind.Ampersand) || (invocationToken == TokenKind.Dot)) && (mi.LanguageMode != context.LanguageMode)) + else if ((invocationToken == TokenKind.Ampersand || invocationToken == TokenKind.Dot) && mi.LanguageMode != context.LanguageMode) { - // Disallow FullLanguage "& (Get-Module MyModule) MyPrivateFn" from ConstrainedLanguage because it always - // runs "internal" origin and so has access to all functions, including non-exported functions. - // Otherwise we end up leaking non-exported functions that run in FullLanguage. - throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException), null, - "CantInvokeCallOperatorAcrossLanguageBoundaries", ParserStrings.CantInvokeCallOperatorAcrossLanguageBoundaries); + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + // Disallow FullLanguage "& (Get-Module MyModule) MyPrivateFn" from ConstrainedLanguage because it always + // runs "internal" origin and so has access to all functions, including non-exported functions. + // Otherwise we end up leaking non-exported functions that run in FullLanguage. + throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException), null, + "CantInvokeCallOperatorAcrossLanguageBoundaries", ParserStrings.CantInvokeCallOperatorAcrossLanguageBoundaries); + } + + // In audit mode, report but don't enforce. + SystemPolicy.LogWDACAuditMessage( + context: context, + title: ParserStrings.WDACParserModuleScopeCallOperatorLogTitle, + message: ParserStrings.WDACParserModuleScopeCallOperatorLogMessage, + fqid: "ModuleScopeCallOperatorNotAllowed", + dropIntoDebugger: true); } commandSessionState = mi.SessionState.Internal; @@ -161,6 +174,7 @@ private static CommandProcessorBase AddCommand(PipelineProcessor pipe, (cmd is ScriptCommand || cmd is PSScriptCmdlet); bool isNativeCommand = commandProcessor is NativeCommandProcessor; + for (int i = commandIndex + 1; i < commandElements.Length; ++i) { var cpi = commandElements[i]; @@ -207,9 +221,24 @@ private static CommandProcessorBase AddCommand(PipelineProcessor pipe, bool redirectedInformation = false; if (redirections != null) { - foreach (var redirection in redirections) + if (isNativeCommand) { - redirection.Bind(pipe, commandProcessor, context); + foreach (CommandRedirection redirection in redirections) + { + if (redirection is MergingRedirection) + { + redirection.Bind(pipe, commandProcessor, context); + } + } + } + + foreach (CommandRedirection redirection in redirections) + { + if (!isNativeCommand || redirection is not MergingRedirection) + { + redirection.Bind(pipe, commandProcessor, context); + } + switch (redirection.FromStream) { case RedirectionStream.Error: @@ -426,10 +455,7 @@ internal static void InvokePipeline(object input, try { - if (context.Events != null) - { - context.Events.ProcessPendingActions(); - } + context.Events?.ProcessPendingActions(); if (input == AutomationNull.Value && !ignoreInput) { @@ -519,24 +545,17 @@ internal static void InvokePipelineInBackground( try { - if (context.Events != null) - { - context.Events.ProcessPendingActions(); - } + context.Events?.ProcessPendingActions(); CommandProcessorBase commandProcessor = null; // For background jobs rewrite the pipeline as a Start-Job command var scriptblockBodyString = pipelineAst.Extent.Text; var pipelineOffset = pipelineAst.Extent.StartOffset; - var variables = pipelineAst.FindAll(x => x is VariableExpressionAst, true); - - // Used to make sure that the job runs in the current directory - const string cmdPrefix = @"Microsoft.PowerShell.Management\Set-Location -LiteralPath $using:pwd ; "; + var variables = pipelineAst.FindAll(static x => x is VariableExpressionAst, true); - // Minimize allocations by initializing the stringbuilder to the size of the source string + prefix + space for ${using:} * 2 - System.Text.StringBuilder updatedScriptblock = new System.Text.StringBuilder(cmdPrefix.Length + scriptblockBodyString.Length + 18); - updatedScriptblock.Append(cmdPrefix); + // Minimize allocations by initializing the stringbuilder to the size of the source string + space for ${using:} * 2 + System.Text.StringBuilder updatedScriptblock = new System.Text.StringBuilder(scriptblockBodyString.Length + 18); int position = 0; // Prefix variables in the scriptblock with $using: @@ -568,14 +587,25 @@ internal static void InvokePipelineInBackground( var sb = ScriptBlock.Create(updatedScriptblock.ToString()); var commandInfo = new CmdletInfo("Start-Job", typeof(StartJobCommand)); commandProcessor = context.CommandDiscovery.LookupCommandProcessor(commandInfo, CommandOrigin.Internal, false, context.EngineSessionState); - var parameter = CommandParameterInternal.CreateParameterWithArgument( + + var workingDirectoryParameter = CommandParameterInternal.CreateParameterWithArgument( parameterAst: pipelineAst, - "ScriptBlock", - null, + parameterName: "WorkingDirectory", + parameterText: null, argumentAst: pipelineAst, - sb, - false); - commandProcessor.AddParameter(parameter); + value: context.SessionState.Path.CurrentLocation.Path, + spaceAfterParameter: false); + + var scriptBlockParameter = CommandParameterInternal.CreateParameterWithArgument( + parameterAst: pipelineAst, + parameterName: "ScriptBlock", + parameterText: null, + argumentAst: pipelineAst, + value: sb, + spaceAfterParameter: false); + + commandProcessor.AddParameter(workingDirectoryParameter); + commandProcessor.AddParameter(scriptBlockParameter); pipelineProcessor.Add(commandProcessor); pipelineProcessor.LinkPipelineSuccessOutput(outputPipe ?? new Pipe(new List())); @@ -684,6 +714,18 @@ internal static SteppablePipeline GetSteppablePipeline(PipelineAst pipelineAst, // of invoking it. So the trustworthiness is defined by the trustworthiness of the // script block's language mode. bool isTrusted = scriptBlock.LanguageMode == PSLanguageMode.FullLanguage; + if (scriptBlock.LanguageMode == PSLanguageMode.ConstrainedLanguage + && SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Audit) + { + // In audit mode, report but don't enforce. + isTrusted = true; + SystemPolicy.LogWDACAuditMessage( + context: context, + title: ParserStrings.WDACGetSteppablePipelineLogTitle, + message: ParserStrings.WDACGetSteppablePipelineLogMessage, + fqid: "GetSteppablePipelineMayFail", + dropIntoDebugger: true); + } foreach (var commandAst in pipelineAst.PipelineElements.Cast()) { @@ -699,7 +741,7 @@ internal static SteppablePipeline GetSteppablePipeline(PipelineAst pipelineAst, var exprAst = (ExpressionAst)commandElement; var argument = Compiler.GetExpressionValue(exprAst, isTrusted, context); - var splatting = (exprAst is VariableExpressionAst && ((VariableExpressionAst)exprAst).Splatted); + var splatting = exprAst is VariableExpressionAst && ((VariableExpressionAst)exprAst).Splatted; commandParameters.Add(CommandParameterInternal.CreateArgument(argument, exprAst, splatting)); } @@ -767,8 +809,8 @@ private static CommandParameterInternal GetCommandParameter(CommandParameterAst } object argumentValue = Compiler.GetExpressionValue(argumentAst, isTrusted, context); - bool spaceAfterParameter = (errorPos.EndLineNumber != argumentAst.Extent.StartLineNumber || - errorPos.EndColumnNumber != argumentAst.Extent.StartColumnNumber); + bool spaceAfterParameter = errorPos.EndLineNumber != argumentAst.Extent.StartLineNumber || + errorPos.EndColumnNumber != argumentAst.Extent.StartColumnNumber; return CommandParameterInternal.CreateParameterWithArgument(commandParameterAst, commandParameterAst.ParameterName, errorPos.Text, argumentAst, argumentValue, spaceAfterParameter); @@ -834,10 +876,7 @@ internal static ExitException GetExitException(object exitCodeObj) internal static void CheckForInterrupts(ExecutionContext context) { - if (context.Events != null) - { - context.Events.ProcessPendingActions(); - } + context.Events?.ProcessPendingActions(); if (context.CurrentPipelineStopping) { @@ -923,7 +962,7 @@ public override string ToString() { return FromStream == RedirectionStream.All ? "*>&1" - : string.Format(CultureInfo.InvariantCulture, "{0}>&1", (int)FromStream); + : string.Create(CultureInfo.InvariantCulture, $"{(int)FromStream}>&1"); } // private RedirectionStream ToStream { get; set; } @@ -1032,11 +1071,13 @@ internal FileRedirection(RedirectionStream from, bool appending, string file) public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "{0}> {1}", - FromStream == RedirectionStream.All - ? "*" - : ((int)FromStream).ToString(CultureInfo.InvariantCulture), - File); + return string.Format( + CultureInfo.InvariantCulture, + "{0}> {1}", + FromStream == RedirectionStream.All + ? "*" + : ((int)FromStream).ToString(CultureInfo.InvariantCulture), + File); } internal string File { get; } @@ -1049,6 +1090,28 @@ public override string ToString() // dir > out internal override void Bind(PipelineProcessor pipelineProcessor, CommandProcessorBase commandProcessor, ExecutionContext context) { + // 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)) + { + ProviderInfo p; + context.SessionState.Path.GetUnresolvedProviderPathFromPSPath(File, out p, out _); + if (p != null && p.NameEquals(context.ProviderNames.Variable)) + { + redirectToVariable = true; + } + } + + if (commandProcessor is NativeCommandProcessor nativeCommand + && nativeCommand.CommandRuntime.ErrorMergeTo is not MshCommandRuntime.MergeDataStream.Output + && FromStream is RedirectionStream.Output + && !string.IsNullOrWhiteSpace(File) + && !redirectToVariable) + { + nativeCommand.StdOutDestination = FileBytePipe.Create(File, Appending); + return; + } + Pipe pipe = GetRedirectionPipe(context, pipelineProcessor); switch (FromStream) @@ -1058,10 +1121,7 @@ internal override void Bind(PipelineProcessor pipelineProcessor, CommandProcesso // Normally, context.CurrentCommandProcessor will not be null. But in legacy DRTs from ParserTest.cs, // a scriptblock may be invoked through 'DoInvokeReturnAsIs' using .NET reflection. In that case, // context.CurrentCommandProcessor will be null. We don't try passing along variable lists in such case. - if (context.CurrentCommandProcessor != null) - { - context.CurrentCommandProcessor.CommandRuntime.OutputPipe.SetVariableListForTemporaryPipe(pipe); - } + context.CurrentCommandProcessor?.CommandRuntime.OutputPipe.SetVariableListForTemporaryPipe(pipe); commandProcessor.CommandRuntime.OutputPipe = pipe; commandProcessor.CommandRuntime.ErrorOutputPipe = pipe; @@ -1072,10 +1132,7 @@ internal override void Bind(PipelineProcessor pipelineProcessor, CommandProcesso break; case RedirectionStream.Output: // Since a temp output pipe is going to be used, we should pass along the error and warning variable list. - if (context.CurrentCommandProcessor != null) - { - context.CurrentCommandProcessor.CommandRuntime.OutputPipe.SetVariableListForTemporaryPipe(pipe); - } + context.CurrentCommandProcessor?.CommandRuntime.OutputPipe.SetVariableListForTemporaryPipe(pipe); commandProcessor.CommandRuntime.OutputPipe = pipe; break; @@ -1164,26 +1221,51 @@ internal Pipe GetRedirectionPipe(ExecutionContext context, PipelineProcessor par return new Pipe { NullPipe = true }; } - CommandProcessorBase commandProcessor = context.CreateCommand("out-file", false); - Diagnostics.Assert(commandProcessor != null, "CreateCommand returned null"); - - // Previously, we mandated Unicode encoding here - // Now, We can take what ever has been set if PSDefaultParameterValues - // Unicode is still the default, but now may be overridden + // 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 cpi = CommandParameterInternal.CreateParameterWithArgument( - /*parameterAst*/null, "Filepath", "-Filepath:", - /*argumentAst*/null, File, - false); - commandProcessor.AddParameter(cpi); + if (ExperimentalFeature.IsEnabled(ExperimentalFeature.PSRedirectToVariable) && p != null && p.NameEquals(context.ProviderNames.Variable)) + { + commandProcessor = context.CreateCommand("Set-Variable", false); + Diagnostics.Assert(commandProcessor != null, "CreateCommand returned null"); + var cpi = CommandParameterInternal.CreateParameterWithArgument( + /*parameterAst*/null, "Name", "-Name:", + /*argumentAst*/null, name, + false); + commandProcessor.AddParameter(cpi); - if (this.Appending) + if (this.Appending) + { + commandProcessor.AddParameter(CommandParameterInternal.CreateParameter("Append", "-Append", null)); + } + } + else { - cpi = CommandParameterInternal.CreateParameterWithArgument( - /*parameterAst*/null, "Append", "-Append:", - /*argumentAst*/null, true, + commandProcessor = context.CreateCommand("out-file", false); + Diagnostics.Assert(commandProcessor != null, "CreateCommand returned null"); + + // Previously, we mandated Unicode encoding here + // Now, We can take what ever has been set if PSDefaultParameterValues + // Unicode is still the default, but now may be overridden + + var cpi = CommandParameterInternal.CreateParameterWithArgument( + /*parameterAst*/null, "Filepath", "-Filepath:", + /*argumentAst*/null, File, false); commandProcessor.AddParameter(cpi); + + if (this.Appending) + { + cpi = CommandParameterInternal.CreateParameterWithArgument( + /*parameterAst*/null, "Append", "-Append:", + /*argumentAst*/null, true, + false); + commandProcessor.AddParameter(cpi); + } } PipelineProcessor = new PipelineProcessor(); @@ -1199,19 +1281,22 @@ internal Pipe GetRedirectionPipe(ExecutionContext context, PipelineProcessor par // is more specific tp the redirection operation... if (rte.ErrorRecord.Exception is System.ArgumentException) { - throw InterpreterError.NewInterpreterExceptionWithInnerException(null, - typeof(RuntimeException), null, "RedirectionFailed", ParserStrings.RedirectionFailed, - rte.ErrorRecord.Exception, File, rte.ErrorRecord.Exception.Message); + throw InterpreterError.NewInterpreterExceptionWithInnerException( + null, + typeof(RuntimeException), + null, + "RedirectionFailed", + ParserStrings.RedirectionFailed, + rte.ErrorRecord.Exception, + File, + rte.ErrorRecord.Exception.Message); } throw; } - if (parentPipelineProcessor != null) - { - // I think this is only necessary for calling Dispose on the commands in the redirection pipe. - parentPipelineProcessor.AddRedirectionPipe(PipelineProcessor); - } + // I think this is only necessary for calling Dispose on the commands in the redirection pipe. + parentPipelineProcessor?.AddRedirectionPipe(PipelineProcessor); return new Pipe(context, PipelineProcessor); } @@ -1220,17 +1305,14 @@ internal Pipe GetRedirectionPipe(ExecutionContext context, PipelineProcessor par /// After file redirection is done, we need to call 'DoComplete' on the pipeline processor, /// so that 'EndProcessing' of Out-File can be called to wrap up the file write operation. /// - /// + /// /// 'StartStepping' is called after creating the pipeline processor. /// 'Step' is called when an object is added to the pipe created with the pipeline processor. - /// + /// internal void CallDoCompleteForExpression() { // The pipe returned from 'GetRedirectionPipe' could be a NullPipe - if (PipelineProcessor != null) - { - PipelineProcessor.DoComplete(); - } + PipelineProcessor?.DoComplete(); } private bool _disposed; @@ -1248,10 +1330,7 @@ private void Dispose(bool disposing) if (disposing) { - if (PipelineProcessor != null) - { - PipelineProcessor.Dispose(); - } + PipelineProcessor?.Dispose(); } _disposed = true; @@ -1280,7 +1359,7 @@ internal static void DefineFunction(ExecutionContext context, } catch (Exception exception) { - if (!(exception is RuntimeException rte)) + if (exception is not RuntimeException rte) { throw ExceptionHandlingOps.ConvertToRuntimeException(exception, functionDefinitionAst.Extent); } @@ -1314,6 +1393,17 @@ internal ScriptBlock GetScriptBlock(ExecutionContext context, bool isFilter) } } + internal static class ByRefOps + { + /// + /// There is no way to directly work with ByRef type in the expression tree, so we turn to reflection in this case. + /// + internal static object GetByRefPropertyValue(object target, PropertyInfo property) + { + return property.GetValue(target); + } + } + internal static class HashtableOps { internal static void AddKeyValuePair(IDictionary hashtable, object key, object value, IScriptExtent errorExtent) @@ -1379,7 +1469,7 @@ internal class CatchAll { } /// /// Represent a handler search result. /// - private class HandlerSearchResult + private sealed class HandlerSearchResult { internal HandlerSearchResult() { @@ -1447,7 +1537,10 @@ private static void FindAndProcessHandler(Type[] types, int[] ranks, int handler = FindMatchingHandlerByType(exception.GetType(), types); // If no handler was found, return without changing the current result. - if (handler == -1) { return; } + if (handler == -1) + { + return; + } // New handler was found. // - If new-rank is less than current-rank -- meaning the new handler is more specific, @@ -1481,7 +1574,7 @@ internal static int FindMatchingHandler(MutableTuple tuple, RuntimeException rte do { - // Always assume no need to repeat the search for another interation + // Always assume no need to repeat the search for another iteration continueToSearch = false; // The 'ErrorRecord' of the current RuntimeException would be passed to $_ ErrorRecord errorRecordToPass = rte.ErrorRecord; @@ -1578,23 +1671,33 @@ private static int FindMatchingHandlerByType(Type exceptionType, Type[] types) internal static bool SuspendStoppingPipeline(ExecutionContext context) { - LocalPipeline lpl = (LocalPipeline)context.CurrentRunspace.GetCurrentlyRunningPipeline(); - if (lpl != null) + var localPipeline = (LocalPipeline)context.CurrentRunspace.GetCurrentlyRunningPipeline(); + return SuspendStoppingPipelineImpl(localPipeline); + } + + internal static void RestoreStoppingPipeline(ExecutionContext context, bool oldIsStopping) + { + var localPipeline = (LocalPipeline)context.CurrentRunspace.GetCurrentlyRunningPipeline(); + RestoreStoppingPipelineImpl(localPipeline, oldIsStopping); + } + + internal static bool SuspendStoppingPipelineImpl(LocalPipeline localPipeline) + { + if (localPipeline is not null) { - bool oldIsStopping = lpl.Stopper.IsStopping; - lpl.Stopper.IsStopping = false; + bool oldIsStopping = localPipeline.Stopper.IsStopping; + localPipeline.Stopper.IsStopping = false; return oldIsStopping; } return false; } - internal static void RestoreStoppingPipeline(ExecutionContext context, bool oldIsStopping) + internal static void RestoreStoppingPipelineImpl(LocalPipeline localPipeline, bool oldIsStopping) { - LocalPipeline lpl = (LocalPipeline)context.CurrentRunspace.GetCurrentlyRunningPipeline(); - if (lpl != null) + if (localPipeline is not null) { - lpl.Stopper.IsStopping = oldIsStopping; + localPipeline.Stopper.IsStopping = oldIsStopping; } } @@ -1616,6 +1719,9 @@ internal static void CheckActionPreference(FunctionContext funcContext, Exceptio InterpreterError.UpdateExceptionErrorRecordPosition(rte, funcContext.CurrentPosition); } + // Update the history id if needed to associate the exception with the right history item. + InterpreterError.UpdateExceptionErrorRecordHistoryId(rte, funcContext._executionContext); + var context = funcContext._executionContext; var outputPipe = funcContext._outputPipe; @@ -1726,10 +1832,7 @@ private static ActionPreference ProcessTraps(FunctionContext funcContext, ErrorRecord err = rte.ErrorRecord; // CurrentCommandProcessor is normally not null, but it is null // when executing some unit tests through reflection. - if (context.CurrentCommandProcessor != null) - { - context.CurrentCommandProcessor.ForgetScriptException(); - } + context.CurrentCommandProcessor?.ForgetScriptException(); try { @@ -1923,10 +2026,7 @@ internal static void SetErrorVariables(IScriptExtent extent, RuntimeException rt if (rte is not PipelineStoppedException) { - if (outputPipe != null) - { - outputPipe.AppendVariableList(VariableStreamKind.Error, errRec); - } + outputPipe?.AppendVariableList(VariableStreamKind.Error, errRec); context.AppendDollarError(errRec); } @@ -2301,8 +2401,13 @@ internal static void InitPowerShellTypesAtRuntime(TypeDefinitionAst[] types) Diagnostics.Assert(t.Type != null, "TypeDefinitionAst.Type cannot be null"); if (t.IsClass) { - var helperType = - t.Type.Assembly.GetType(t.Type.FullName + "_"); + if (t.Type.IsDefined(typeof(NoRunspaceAffinityAttribute), inherit: true)) + { + // Skip the initialization for session state affinity. + continue; + } + + var helperType = t.Type.Assembly.GetType(t.Type.FullName + "_"); Diagnostics.Assert(helperType != null, "no corresponding " + t.Type.FullName + "_ type found"); foreach (var p in helperType.GetFields(BindingFlags.Static | BindingFlags.NonPublic)) { @@ -2773,10 +2878,8 @@ internal static object ForEach(IEnumerator enumerator, object expression, object { Diagnostics.Assert(enumerator != null, "The ForEach() operator should never receive a null enumerator value from the runtime."); Diagnostics.Assert(arguments != null, "The ForEach() operator should never receive a null value for the 'arguments' parameter from the runtime."); - if (expression == null) - { - throw new ArgumentNullException(nameof(expression)); - } + + ArgumentNullException.ThrowIfNull(expression); var context = Runspace.DefaultRunspace.ExecutionContext; @@ -2855,6 +2958,11 @@ internal static object ForEach(IEnumerator enumerator, object expression, object ScriptBlock sb = expression as ScriptBlock; if (sb != null) { + if (sb.HasCleanBlock) + { + throw new PSNotSupportedException(ParserStrings.ForEachNotSupportCleanBlock); + } + Pipe outputPipe = new Pipe(result); if (sb.HasBeginBlock) { @@ -2979,8 +3087,18 @@ internal static object ForEach(IEnumerator enumerator, object expression, object { if (!CoreTypes.Contains(basedCurrent.GetType())) { - throw InterpreterError.NewInterpreterException(current, typeof(PSInvalidOperationException), - null, "MethodInvocationNotSupportedInConstrainedLanguage", ParserStrings.InvokeMethodConstrainedLanguage); + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + throw InterpreterError.NewInterpreterException(current, typeof(PSInvalidOperationException), + null, "MethodInvocationNotSupportedInConstrainedLanguage", ParserStrings.InvokeMethodConstrainedLanguage); + } + + SystemPolicy.LogWDACAuditMessage( + context: context, + title: ParserStrings.WDACParserForEachOperatorLogTitle, + message: StringUtil.Format(ParserStrings.WDACParserForEachOperatorLogMessage, method.Name ?? string.Empty), + fqid: "ForEachOperatorMethodInvocationNotAllowed", + dropIntoDebugger: true); } } @@ -3497,10 +3615,7 @@ internal static void WriteEnumerableToPipe(IEnumerator enumerator, Pipe pipe, Ex if (dispose) { var disposable = enumerator as IDisposable; - if (disposable != null) - { - disposable.Dispose(); - } + disposable?.Dispose(); } } } @@ -3531,4 +3646,101 @@ internal static object[] GetSlice(IList list, int startIndex) return result; } } + + internal static class MemberInvocationLoggingOps + { + private static readonly Lazy DumpLogAMSIContent = new Lazy( + () => { + object result = Environment.GetEnvironmentVariable("__PSDumpAMSILogContent"); + if (result != null && LanguagePrimitives.TryConvertTo(result, out int value)) + { + return value == 1; + } + return false; + } + ); + + private static string ArgumentToString(object arg) + { + object baseObj = PSObject.Base(arg); + if (baseObj is null) + { + // The argument is null or AutomationNull.Value. + return "null"; + } + + // The comparisons below are ordered by the likelihood of arguments being of those types. + if (baseObj is string str) + { + return str; + } + + // Special case some types to call 'ToString' on the object. For the rest, we return its + // full type name to avoid calling a potentially expensive 'ToString' implementation. + Type baseType = baseObj.GetType(); + if (baseType.IsEnum || baseType.IsPrimitive + || baseType == typeof(Guid) + || baseType == typeof(Uri) + || baseType == typeof(Version) + || baseType == typeof(SemanticVersion) + || baseType == typeof(BigInteger) + || baseType == typeof(decimal)) + { + return baseObj.ToString(); + } + + return baseType.FullName; + } + + internal static void LogMemberInvocation(string targetName, string name, object[] args) + { + try + { + var contentName = "PowerShellMemberInvocation"; + var argsBuilder = new Text.StringBuilder(); + + for (int i = 0; i < args.Length; i++) + { + string value = ArgumentToString(args[i]); + + if (i > 0) + { + argsBuilder.Append(", "); + } + + argsBuilder.Append($"<{value}>"); + } + + string content = $"<{targetName}>.{name}({argsBuilder})"; + + if (DumpLogAMSIContent.Value) + { + Console.WriteLine("\n=== Amsi notification report content ==="); + Console.WriteLine(content); + } + + var success = AmsiUtils.ReportContent( + name: contentName, + content: content); + + if (DumpLogAMSIContent.Value) + { + Console.WriteLine($"=== Amsi notification report success: {success} ==="); + } + } + catch (PSSecurityException) + { + // ReportContent() will throw PSSecurityException if AMSI detects malware, which + // must be propagated. + throw; + } + catch (Exception ex) + { + if (DumpLogAMSIContent.Value) + { + Console.WriteLine($"!!! Amsi notification report exception: {ex} !!!"); + } + } + } + } } diff --git a/src/System.Management.Automation/engine/runtime/Operations/StringOps.cs b/src/System.Management.Automation/engine/runtime/Operations/StringOps.cs index 0c4658cc14d..0ac1db53ff9 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/StringOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/StringOps.cs @@ -28,11 +28,8 @@ internal static string Multiply(string s, int times) { Diagnostics.Assert(s != null, "caller to verify argument is not null"); - if (times < 0) - { - // TODO: this should be a runtime error. - throw new ArgumentOutOfRangeException(nameof(times)); - } + // TODO: this should be a runtime error. + ArgumentOutOfRangeException.ThrowIfNegative(times); if (times == 0 || s.Length == 0) { diff --git a/src/System.Management.Automation/engine/runtime/Operations/VariableOps.cs b/src/System.Management.Automation/engine/runtime/Operations/VariableOps.cs index d337c56ab2b..72126c726c9 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/VariableOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/VariableOps.cs @@ -46,6 +46,13 @@ internal static object SetVariableValue(VariablePath variablePath, object value, : GetAttributeCollection(attributeAsts); var = new PSVariable(variablePath.UnqualifiedPath, value, ScopedItemOptions.None, attributes); + if (attributes.Count > 0) + { + // When there are any attributes, it's possible the value was converted/transformed. + // Use 'GetValueRaw' here so the debugger check won't be triggered. + value = var.GetValueRaw(); + } + // Marking untrusted values for assignments in 'ConstrainedLanguage' mode is done in // SessionStateScope.SetVariable. sessionState.SetVariable(variablePath, var, false, origin); @@ -81,7 +88,7 @@ internal static object SetVariableValue(VariablePath variablePath, object value, null, Metadata.InvalidValueFailure, var.Name, - ((value != null) ? value.ToString() : "$null")); + (value != null) ? value.ToString() : "$null"); throw e; } @@ -277,7 +284,7 @@ private static UsingResult GetUsingValueFromTuple(MutableTuple tuple, string usi return null; } - private class UsingResult + private sealed class UsingResult { public object Value { get; set; } } diff --git a/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs b/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs index 93394be6808..b0017080571 100644 --- a/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs +++ b/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs @@ -172,7 +172,7 @@ internal static void ThrowError(ScriptBlockToPowerShellNotSupportedException ex, } } - internal class UsingExpressionAstSearcher : AstSearcher + internal sealed class UsingExpressionAstSearcher : AstSearcher { internal static IEnumerable FindAllUsingExpressions(Ast ast) { @@ -207,7 +207,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst ast /// Converts a ScriptBlock to a PowerShell object by traversing the /// given Ast. /// - internal class ScriptBlockToPowerShellConverter + internal sealed class ScriptBlockToPowerShellConverter { private readonly PowerShell _powershell; private ExecutionContext _context; @@ -230,10 +230,7 @@ internal static PowerShell Convert(ScriptBlockAst body, { ExecutionContext.CheckStackDepth(); - if (args == null) - { - args = Array.Empty(); - } + args ??= Array.Empty(); // Perform validations on the ScriptBlock. GetSimplePipeline can allow for more than one // pipeline if the first parameter is true, but Invoke-Command doesn't yet support multiple @@ -324,13 +321,11 @@ internal static PowerShell Convert(ScriptBlockAst body, /// Scriptblock to search. /// True when input is trusted. /// Execution context. - /// List of foreach command names and aliases. /// Dictionary of using variable map. internal static Dictionary GetUsingValuesForEachParallel( ScriptBlock scriptBlock, bool isTrustedInput, - ExecutionContext context, - string[] foreachNames) + 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 @@ -350,7 +345,7 @@ internal static Dictionary GetUsingValuesForEachParallel( for (int i = 0; i < usingAsts.Count; ++i) { usingAst = (UsingExpressionAst)usingAsts[i]; - if (IsInForeachParallelCallingScope(usingAst, foreachNames)) + if (IsInForeachParallelCallingScope(scriptBlock.Ast, usingAst)) { var value = Compiler.GetExpressionValue(usingAst.SubExpression, isTrustedInput, context); string usingAstKey = PsUtils.GetUsingExpressionKey(usingAst); @@ -382,17 +377,61 @@ internal static Dictionary GetUsingValuesForEachParallel( return usingValueMap; } + // List of Foreach-Object command names and aliases. + // TODO: Look into using SessionState.Internal.GetAliasTable() to find all user created aliases. + // But update Alias command logic to maintain reverse table that lists all aliases mapping + // to a single command definition, for performance. + private static readonly string[] forEachNames = new string[] + { + "ForEach-Object", + "foreach", + "%" + }; + + private static bool FindForEachInCommand(CommandAst commandAst) + { + // Command name is always the first element in the CommandAst. + // e.g., 'foreach -parallel {}' + var commandNameElement = (commandAst.CommandElements.Count > 0) ? commandAst.CommandElements[0] : null; + if (commandNameElement is StringConstantExpressionAst commandName) + { + bool found = false; + foreach (var foreachName in forEachNames) + { + if (commandName.Value.Equals(foreachName, StringComparison.OrdinalIgnoreCase)) + { + found = true; + break; + } + } + + if (found) + { + // Verify this is foreach-object with parallel parameter set. + var bindingResult = StaticParameterBinder.BindCommand(commandAst); + if (bindingResult.BoundParameters.ContainsKey("Parallel")) + { + return true; + } + } + } + + return false; + } + /// /// Walks the using Ast to verify it is used within a foreach-object -parallel command /// and parameter set scope, and not from within a nested foreach-object -parallel call. /// + /// Scriptblock Ast containing this using Ast /// Using Ast to check. - /// List of foreach-object command names. /// True if using expression is in current call scope. private static bool IsInForeachParallelCallingScope( - UsingExpressionAst usingAst, - string[] foreachNames) + Ast scriptblockAst, + UsingExpressionAst usingAst) { + Diagnostics.Assert(usingAst != null, "usingAst argument cannot be null."); + /* Example: $Test1 = "Hello" @@ -405,54 +444,23 @@ private static bool IsInForeachParallelCallingScope( } } */ - Diagnostics.Assert(usingAst != null, "usingAst argument cannot be null."); // Search up the parent Ast chain for 'Foreach-Object -Parallel' commands. Ast currentParent = usingAst.Parent; - int foreachNestedCount = 0; - while (currentParent != null) + while (currentParent != scriptblockAst) { // Look for Foreach-Object outer commands - if (currentParent is CommandAst commandAst) - { - foreach (var commandElement in commandAst.CommandElements) - { - if (commandElement is StringConstantExpressionAst commandName) - { - bool found = false; - foreach (var foreachName in foreachNames) - { - if (commandName.Value.Equals(foreachName, StringComparison.OrdinalIgnoreCase)) - { - found = true; - break; - } - } - - if (found) - { - // Verify this is foreach-object with parallel parameter set. - var bindingResult = StaticParameterBinder.BindCommand(commandAst); - if (bindingResult.BoundParameters.ContainsKey("Parallel")) - { - foreachNestedCount++; - break; - } - } - } - } - } - - if (foreachNestedCount > 1) + if (currentParent is CommandAst commandAst && + FindForEachInCommand(commandAst)) { - // This using expression Ast is outside the original calling scope. + // Using Ast is outside the invoking foreach scope. return false; } currentParent = currentParent.Parent; } - return foreachNestedCount == 1; + return true; } /// @@ -938,7 +946,7 @@ private void AddParameter(CommandParameterAst commandParameterAst, bool isTruste // first character in parameter name must be a dash _powershell.AddParameter( - string.Format(CultureInfo.InvariantCulture, "-{0}{1}", commandParameterAst.ParameterName, nameSuffix), + string.Create(CultureInfo.InvariantCulture, $"-{commandParameterAst.ParameterName}{nameSuffix}"), argument); } } diff --git a/src/System.Management.Automation/engine/serialization.cs b/src/System.Management.Automation/engine/serialization.cs index b4115de05cb..3cffd998d49 100644 --- a/src/System.Management.Automation/engine/serialization.cs +++ b/src/System.Management.Automation/engine/serialization.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Specialized; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -82,10 +83,8 @@ internal SerializationContext(int depth, SerializationOptions options, PSRemotin /// /// This class provides public functionality for serializing a PSObject. /// - public class PSSerializer + public static class PSSerializer { - internal PSSerializer() { } - /// /// Serializes an object into PowerShell CliXml. /// @@ -123,6 +122,45 @@ public static string Serialize(object source, int depth) return sb.ToString(); } + /// + /// Serializes list of objects into PowerShell CliXml. + /// + /// The input objects to serialize. + /// The depth of the members to serialize. + /// Enumerates input objects and serializes one at a time. + /// The serialized object, as CliXml. + internal static string Serialize(IList source, int depth, bool enumerate) + { + StringBuilder sb = new(); + + XmlWriterSettings xmlSettings = new() + { + CloseOutput = true, + Encoding = Encoding.Unicode, + Indent = true, + OmitXmlDeclaration = true + }; + + XmlWriter xw = XmlWriter.Create(sb, xmlSettings); + Serializer serializer = new(xw, depth, useDepthFromTypes: true); + + if (enumerate) + { + foreach (object item in source) + { + serializer.Serialize(item); + } + } + else + { + serializer.Serialize(source); + } + + serializer.Done(); + + return sb.ToString(); + } + /// /// Deserializes PowerShell CliXml into an object. /// @@ -734,7 +772,7 @@ internal static string MaskDeserializationPrefix(string typeName) /// /// Gets a new collection of typenames without "Deserialization." prefix - /// in the typename. This will allow to map type info/format info of the orignal type + /// in the typename. This will allow to map type info/format info of the original type /// for deserialized objects. /// /// @@ -1610,8 +1648,8 @@ private void PrepareCimInstanceForSerialization(PSObject psObject, CimInstance c // ATTACH INSTANCE METADATA TO THE OBJECT BEING SERIALIZED List namesOfModifiedProperties = cimInstance .CimInstanceProperties - .Where(p => p.IsValueModified) - .Select(p => p.Name) + .Where(static p => p.IsValueModified) + .Select(static p => p.Name) .ToList(); if (namesOfModifiedProperties.Count != 0) { @@ -1633,7 +1671,7 @@ private void PrepareCimInstanceForSerialization(PSObject psObject, CimInstance c instanceMetadata.Properties.Add( new PSNoteProperty( InternalDeserializer.CimModifiedProperties, - string.Join(" ", namesOfModifiedProperties))); + string.Join(' ', namesOfModifiedProperties))); } } @@ -2160,7 +2198,10 @@ int depth } Dbg.Assert(key != null, "Dictionary keys should never be null"); - if (key == null) break; + if (key == null) + { + break; + } WriteStartElement(SerializationStrings.DictionaryEntryTag); WriteOneObject(key, null, SerializationStrings.DictionaryKey, depth); @@ -3458,7 +3499,7 @@ private PSObject RehydrateCimInstance(PSObject deserializedObject) if ((modifiedPropertiesProperty != null) && (modifiedPropertiesProperty.Value != null)) { string modifiedPropertiesString = modifiedPropertiesProperty.Value.ToString(); - foreach (string nameOfModifiedProperty in modifiedPropertiesString.Split(Utils.Separators.Space)) + foreach (string nameOfModifiedProperty in modifiedPropertiesString.Split(' ')) { namesOfModifiedProperties.Add(nameOfModifiedProperty); } @@ -3670,7 +3711,7 @@ private PSObject ReadPSObject() else if (IsKnownContainerTag(out ct)) { s_trace.WriteLine("Found container node {0}", ct); - baseObject = ReadKnownContainer(ct); + baseObject = ReadKnownContainer(ct, dso.InternalTypeNames); } else if (IsNextElement(SerializationStrings.PSObjectTag)) { @@ -3934,12 +3975,12 @@ private bool IsKnownContainerTag(out ContainerType ct) return ct != ContainerType.None; } - private object ReadKnownContainer(ContainerType ct) + private object ReadKnownContainer(ContainerType ct, ConsolidatedString InternalTypeNames) { switch (ct) { case ContainerType.Dictionary: - return ReadDictionary(ct); + return ReadDictionary(ct, InternalTypeNames); case ContainerType.Enumerable: case ContainerType.List: @@ -3988,19 +4029,81 @@ private object ReadListContainer(ContainerType ct) return list; } + /// + /// Utility class for ReadDictionary(), supporting ordered or non-ordered Dictionary methods. + /// + private class PSDictionary + { + private IDictionary dict; + private readonly bool _isOrdered; + private int _keyClashFoundIteration = 0; + + public PSDictionary(bool isOrdered) { + _isOrdered = isOrdered; + + // By default use a non case-sensitive comparer + if (_isOrdered) { + dict = new OrderedDictionary(StringComparer.CurrentCultureIgnoreCase); + } else { + dict = new Hashtable(StringComparer.CurrentCultureIgnoreCase); + } + } + + public object DictionaryObject { get { return dict; } } + + public void Add(object key, object value) { + // On the first collision, copy the hash table to one that uses the `key` object's default comparer. + if (_keyClashFoundIteration == 0 && dict.Contains(key)) + { + _keyClashFoundIteration++; + IDictionary newDict = _isOrdered ? new OrderedDictionary(dict.Count) : new Hashtable(dict.Count); + + foreach (DictionaryEntry entry in dict) { + newDict.Add(entry.Key, entry.Value); + } + + dict = newDict; + } + + // win8: 389060. If there are still collisions even with case-sensitive default comparer, + // use an IEqualityComparer that does object ref equality. + if (_keyClashFoundIteration == 1 && dict.Contains(key)) + { + _keyClashFoundIteration++; + IEqualityComparer equalityComparer = new ReferenceEqualityComparer(); + IDictionary newDict = _isOrdered ? + new OrderedDictionary(dict.Count, equalityComparer) : + new Hashtable(dict.Count, equalityComparer); + + foreach (DictionaryEntry entry in dict) { + newDict.Add(entry.Key, entry.Value); + } + + dict = newDict; + } + + dict.Add(key, value); + } + } + /// /// Deserialize Dictionary. /// /// - private object ReadDictionary(ContainerType ct) + private object ReadDictionary(ContainerType ct, ConsolidatedString InternalTypeNames) { Dbg.Assert(ct == ContainerType.Dictionary, "Unrecognized ContainerType enum"); // We assume the hash table is a PowerShell hash table and hence uses // a case insensitive string comparer. If we discover a key collision, // we'll revert back to the default comparer. - Hashtable table = new Hashtable(StringComparer.CurrentCultureIgnoreCase); - int keyClashFoundIteration = 0; + + // Find whether original directory was ordered + bool isOrdered = InternalTypeNames.Count > 0 && + (Deserializer.MaskDeserializationPrefix(InternalTypeNames[0]) == typeof(OrderedDictionary).FullName); + + PSDictionary dictionary = new PSDictionary(isOrdered); + if (ReadStartElementAndHandleEmpty(SerializationStrings.DictionaryTag)) { while (_reader.NodeType == XmlNodeType.Element) @@ -4039,38 +4142,10 @@ private object ReadDictionary(ContainerType ct) object value = ReadOneObject(); - // On the first collision, copy the hash table to one that uses the default comparer. - if (table.ContainsKey(key) && (keyClashFoundIteration == 0)) - { - keyClashFoundIteration++; - Hashtable newHashTable = new Hashtable(); - foreach (DictionaryEntry entry in table) - { - newHashTable.Add(entry.Key, entry.Value); - } - - table = newHashTable; - } - - // win8: 389060. If there are still collisions even with case-sensitive default comparer, - // use an IEqualityComparer that does object ref equality. - if (table.ContainsKey(key) && (keyClashFoundIteration == 1)) - { - keyClashFoundIteration++; - IEqualityComparer equalityComparer = new ReferenceEqualityComparer(); - Hashtable newHashTable = new Hashtable(equalityComparer); - foreach (DictionaryEntry entry in table) - { - newHashTable.Add(entry.Key, entry.Value); - } - - table = newHashTable; - } - try { // Add entry to hashtable - table.Add(key, value); + dictionary.Add(key, value); } catch (ArgumentException e) { @@ -4083,7 +4158,7 @@ private object ReadDictionary(ContainerType ct) ReadEndElement(); } - return table; + return dictionary.DictionaryObject; } #endregion known containers @@ -4916,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"); } @@ -5086,7 +5161,7 @@ internal TypeSerializationInfo(Type type, string itemTag, string propertyTag, Ty /// /// A class for identifying types which are treated as KnownType by Monad. - /// A KnownType is guranteed to be available on machine on which monad is + /// A KnownType is guaranteed to be available on machine on which monad is /// running. /// internal static class KnownTypes @@ -5639,7 +5714,7 @@ internal static object GetPropertyValueInThreadSafeManner(PSPropertyInfo propert /// type of dictionary values internal class WeakReferenceDictionary : IDictionary { - private class WeakReferenceEqualityComparer : IEqualityComparer + private sealed class WeakReferenceEqualityComparer : IEqualityComparer { public bool Equals(WeakReference x, WeakReference y) { @@ -5874,7 +5949,6 @@ IEnumerator IEnumerable.GetEnumerator() /// 2) values that can be serialized and deserialized during PowerShell remoting handshake /// (in major-version compatible versions of PowerShell remoting) /// - [Serializable] public sealed class PSPrimitiveDictionary : Hashtable { #region Constructors @@ -5900,10 +5974,7 @@ public PSPrimitiveDictionary() public PSPrimitiveDictionary(Hashtable other) : base(StringComparer.OrdinalIgnoreCase) { - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } + ArgumentNullException.ThrowIfNull(other); foreach (DictionaryEntry entry in other) { @@ -5975,7 +6046,7 @@ private static string VerifyKey(object key) typeof(PSPrimitiveDictionary) }; - private void VerifyValue(object value) + private static void VerifyValue(object value) { // null is a primitive type if (value == null) @@ -6033,7 +6104,7 @@ private void VerifyValue(object value) public override void Add(object key, object value) { string keyAsString = VerifyKey(key); - this.VerifyValue(value); + VerifyValue(value); base.Add(keyAsString, value); } @@ -6061,7 +6132,7 @@ public override object this[object key] set { string keyAsString = VerifyKey(key); - this.VerifyValue(value); + VerifyValue(value); base[keyAsString] = value; } } @@ -6089,7 +6160,7 @@ public object this[string key] set { - this.VerifyValue(value); + VerifyValue(value); base[key] = value; } } @@ -6493,7 +6564,7 @@ public void Add(string key, PSPrimitiveDictionary[] value) /// /// If originalHash contains PSVersionTable, then just returns the Cloned copy of - /// the original hash. Othewise, creates a clone copy and add PSVersionInfo.GetPSVersionTable + /// the original hash. Otherwise, creates a clone copy and add PSVersionInfo.GetPSVersionTable /// to the clone and returns. /// /// @@ -6595,7 +6666,6 @@ namespace Microsoft.PowerShell /// - PropertySerializationSet= /// - TargetTypeForDeserialization=DeserializingTypeConverter /// - Add a field of that type in unit tests / S.M.A.Test.SerializationTest+RehydratedType - /// (testsrc\admintest\monad\DRT\engine\UnitTests\SerializationTest.cs) /// --> public sealed class DeserializingTypeConverter : PSTypeConverter { @@ -7214,7 +7284,6 @@ internal static PSSenderInfo RehydratePSSenderInfo(PSObject pso) PSSenderInfo senderInfo = new PSSenderInfo(psPrincipal, GetPropertyValue(pso, "ConnectionString")); - senderInfo.ClientTimeZone = TimeZoneInfo.Local; senderInfo.ApplicationArguments = GetPropertyValue(pso, "ApplicationArguments"); return senderInfo; @@ -7223,7 +7292,9 @@ internal static PSSenderInfo RehydratePSSenderInfo(PSObject pso) private static System.Security.Cryptography.X509Certificates.X509Certificate2 RehydrateX509Certificate2(PSObject pso) { byte[] rawData = GetPropertyValue(pso, "RawData"); + #pragma warning disable SYSLIB0057 return new System.Security.Cryptography.X509Certificates.X509Certificate2(rawData); + #pragma warning restore SYSLIB0057 } private static System.Security.Cryptography.X509Certificates.X500DistinguishedName RehydrateX500DistinguishedName(PSObject pso) diff --git a/src/System.Management.Automation/help/AliasHelpInfo.cs b/src/System.Management.Automation/help/AliasHelpInfo.cs index 57cb7fdd184..5d02e754397 100644 --- a/src/System.Management.Automation/help/AliasHelpInfo.cs +++ b/src/System.Management.Automation/help/AliasHelpInfo.cs @@ -8,7 +8,7 @@ namespace System.Management.Automation /// /// Stores help information related to Alias Commands. /// - internal class AliasHelpInfo : HelpInfo + internal sealed class AliasHelpInfo : HelpInfo { /// /// Initializes a new instance of the AliasHelpInfo class. @@ -40,8 +40,7 @@ private AliasHelpInfo(AliasInfo aliasInfo) } _fullHelpObject.TypeNames.Clear(); - _fullHelpObject.TypeNames.Add(string.Format(Globalization.CultureInfo.InvariantCulture, - "AliasHelpInfo#{0}", Name)); + _fullHelpObject.TypeNames.Add(string.Create(Globalization.CultureInfo.InvariantCulture, $"AliasHelpInfo#{Name}")); _fullHelpObject.TypeNames.Add("AliasHelpInfo"); _fullHelpObject.TypeNames.Add("HelpInfo"); } diff --git a/src/System.Management.Automation/help/AliasHelpProvider.cs b/src/System.Management.Automation/help/AliasHelpProvider.cs index f14f815891a..d93aa76b2f3 100644 --- a/src/System.Management.Automation/help/AliasHelpProvider.cs +++ b/src/System.Management.Automation/help/AliasHelpProvider.cs @@ -161,7 +161,7 @@ internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool foreach (HelpInfo helpInfo in ExactMatchHelp(exactMatchHelpRequest)) { // Component/Role/Functionality match is done only for SearchHelp - // as "get-help * -category alias" should not forwad help to + // as "get-help * -category alias" should not forward help to // CommandHelpProvider..(ExactMatchHelp does forward help to // CommandHelpProvider) if (!Match(helpInfo, helpRequest)) @@ -208,7 +208,7 @@ internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool foreach (HelpInfo helpInfo in ExactMatchHelp(exactMatchHelpRequest)) { // Component/Role/Functionality match is done only for SearchHelp - // as "get-help * -category alias" should not forwad help to + // as "get-help * -category alias" should not forward help to // CommandHelpProvider..(ExactMatchHelp does forward help to // CommandHelpProvider) if (!Match(helpInfo, helpRequest)) diff --git a/src/System.Management.Automation/help/BaseCommandHelpInfo.cs b/src/System.Management.Automation/help/BaseCommandHelpInfo.cs index 0c70b815928..c24e45ee788 100644 --- a/src/System.Management.Automation/help/BaseCommandHelpInfo.cs +++ b/src/System.Management.Automation/help/BaseCommandHelpInfo.cs @@ -214,8 +214,7 @@ internal Uri LookupUriFromCommandInfo() string commandToSearch = commandName; if (!string.IsNullOrEmpty(moduleName)) { - commandToSearch = string.Format(CultureInfo.InvariantCulture, - "{0}\\{1}", moduleName, commandName); + commandToSearch = string.Create(CultureInfo.InvariantCulture, $"{moduleName}\\{commandName}"); } ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS(); @@ -252,7 +251,7 @@ internal Uri LookupUriFromCommandInfo() // Split the string based on (space). We decided to go with this approach as // UX localization authors use spaces. Correctly extracting only the wellformed URI // is out-of-scope for this fix. - string[] tempUriSplitArray = uriString.Split(Utils.Separators.Space); + string[] tempUriSplitArray = uriString.Split(' '); uriString = tempUriSplitArray[0]; } @@ -319,7 +318,7 @@ internal static Uri GetUriFromCommandPSObject(PSObject commandFullHelp) // Split the string based on (space). We decided to go with this approach as // UX localization authors use spaces. Correctly extracting only the wellformed URI // is out-of-scope for this fix. - string[] tempUriSplitArray = uriString.Split(Utils.Separators.Space); + string[] tempUriSplitArray = uriString.Split(' '); uriString = tempUriSplitArray[0]; } @@ -356,15 +355,9 @@ internal override bool MatchPatternInContent(WildcardPattern pattern) string synopsis = Synopsis; string detailedDescription = DetailedDescription; - if (synopsis == null) - { - synopsis = string.Empty; - } + synopsis ??= string.Empty; - if (detailedDescription == null) - { - detailedDescription = string.Empty; - } + detailedDescription ??= string.Empty; return pattern.IsMatch(synopsis) || pattern.IsMatch(detailedDescription); } @@ -462,7 +455,7 @@ internal string DetailedDescription return string.Empty; } - // I think every cmdlet description should atleast have 400 characters... + // I think every cmdlet description should at least have 400 characters... // so starting with this assumption..I did an average of all the cmdlet // help content available at the time of writing this code and came up // with this number. diff --git a/src/System.Management.Automation/help/CabinetNativeApi.cs b/src/System.Management.Automation/help/CabinetNativeApi.cs index 76e8fe853a4..fd369ca03be 100644 --- a/src/System.Management.Automation/help/CabinetNativeApi.cs +++ b/src/System.Management.Automation/help/CabinetNativeApi.cs @@ -70,10 +70,7 @@ protected override void Dispose(bool disposing) } // Free managed objects within 'if (disposing)' if needed - if (fdiContext != null) - { - fdiContext.Dispose(); - } + fdiContext?.Dispose(); // Free unmanaged objects here this.CleanUpDelegates(); @@ -546,7 +543,7 @@ internal static FileShare ConvertPermissionModeToFileShare(int pmode) #region IO classes, structures, and enums - [FlagsAttribute] + [Flags] internal enum PermissionMode : int { None = 0x0000, @@ -554,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/CommandHelpProvider.cs b/src/System.Management.Automation/help/CommandHelpProvider.cs index d5863f31819..15af80745db 100644 --- a/src/System.Management.Automation/help/CommandHelpProvider.cs +++ b/src/System.Management.Automation/help/CommandHelpProvider.cs @@ -879,7 +879,7 @@ private HelpInfo GetFromCommandCacheOrCmdletInfo(CmdletInfo cmdletInfo) /// /// Used to retrieve helpinfo by removing the prefix from the noun portion of a command name. /// Import-Module and Import-PSSession supports changing the name of a command - /// by suppling a custom prefix. In those cases, the help content is stored by using the + /// by supplying a custom prefix. In those cases, the help content is stored by using the /// original command name (without prefix) as the key. /// /// This method retrieves the help content by suppressing the prefix and then making a copy @@ -950,15 +950,21 @@ private void AddToCommandCache(string mshSnapInId, string cmdletName, MamlComman // Add snapin qualified type name for this command at the top.. // this will enable customizations of the help object. - helpInfo.FullHelp.TypeNames.Insert(0, string.Format(CultureInfo.InvariantCulture, - "MamlCommandHelpInfo#{0}#{1}", mshSnapInId, cmdletName)); + helpInfo.FullHelp.TypeNames.Insert( + index: 0, + string.Create( + CultureInfo.InvariantCulture, + $"MamlCommandHelpInfo#{mshSnapInId}#{cmdletName}")); if (!string.IsNullOrEmpty(mshSnapInId)) { key = mshSnapInId + "\\" + key; // Add snapin name to the typenames of this object - helpInfo.FullHelp.TypeNames.Insert(1, string.Format(CultureInfo.InvariantCulture, - "MamlCommandHelpInfo#{0}", mshSnapInId)); + helpInfo.FullHelp.TypeNames.Insert( + index: 1, + string.Create( + CultureInfo.InvariantCulture, + $"MamlCommandHelpInfo#{mshSnapInId}")); } AddCache(key, helpInfo); @@ -1075,10 +1081,7 @@ internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool { // this command is not visible to the user (from CommandOrigin) so // dont show help topic for it. - if (!hiddenCommands.Contains(helpName)) - { - hiddenCommands.Add(helpName); - } + hiddenCommands.Add(helpName); continue; } @@ -1275,7 +1278,7 @@ internal override IEnumerable ProcessForwardedHelp(HelpInfo helpInfo, } catch (CommandNotFoundException) { - // ignore errors for aliases pointing to non-existant commands + // ignore errors for aliases pointing to non-existent commands } } @@ -1359,7 +1362,7 @@ internal virtual CommandSearcher GetCommandSearcherForSearch(string pattern, Exe /// Legally, user-defined Help Data should be within the same file as the corresponding /// commandHelp and it should appear after the commandHelp. /// - internal class UserDefinedHelpData + internal sealed class UserDefinedHelpData { private UserDefinedHelpData() { diff --git a/src/System.Management.Automation/help/DefaultCommandHelpObjectBuilder.cs b/src/System.Management.Automation/help/DefaultCommandHelpObjectBuilder.cs index 34b1bd84cef..bce76b005a2 100644 --- a/src/System.Management.Automation/help/DefaultCommandHelpObjectBuilder.cs +++ b/src/System.Management.Automation/help/DefaultCommandHelpObjectBuilder.cs @@ -57,8 +57,8 @@ internal static PSObject GetPSObjectFromCmdletInfo(CommandInfo input) PSObject obj = new PSObject(); obj.TypeNames.Clear(); - obj.TypeNames.Add(string.Format(CultureInfo.InvariantCulture, "{0}#{1}#command", DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp, commandInfo.ModuleName)); - obj.TypeNames.Add(string.Format(CultureInfo.InvariantCulture, "{0}#{1}", DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp, commandInfo.ModuleName)); + obj.TypeNames.Add(string.Create(CultureInfo.InvariantCulture, $"{DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp}#{commandInfo.ModuleName}#command")); + obj.TypeNames.Add(string.Create(CultureInfo.InvariantCulture, $"{DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp}#{commandInfo.ModuleName}")); obj.TypeNames.Add(DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp); obj.TypeNames.Add("CmdletHelpInfo"); obj.TypeNames.Add("HelpInfo"); @@ -160,7 +160,7 @@ internal static void AddDetailsProperties(PSObject obj, string name, string noun PSObject mshObject = new PSObject(); mshObject.TypeNames.Clear(); - mshObject.TypeNames.Add(string.Format(CultureInfo.InvariantCulture, "{0}#details", typeNameForHelp)); + mshObject.TypeNames.Add(string.Create(CultureInfo.InvariantCulture, $"{typeNameForHelp}#details")); mshObject.Properties.Add(new PSNoteProperty("name", name)); mshObject.Properties.Add(new PSNoteProperty("noun", noun)); @@ -192,7 +192,7 @@ internal static void AddSyntaxProperties(PSObject obj, string cmdletName, ReadOn PSObject mshObject = new PSObject(); mshObject.TypeNames.Clear(); - mshObject.TypeNames.Add(string.Format(CultureInfo.InvariantCulture, "{0}#syntax", typeNameForHelp)); + mshObject.TypeNames.Add(string.Create(CultureInfo.InvariantCulture, $"{typeNameForHelp}#syntax")); AddSyntaxItemProperties(mshObject, cmdletName, parameterSets, common, typeNameForHelp); @@ -216,7 +216,7 @@ private static void AddSyntaxItemProperties(PSObject obj, string cmdletName, Rea PSObject mshObject = new PSObject(); mshObject.TypeNames.Clear(); - mshObject.TypeNames.Add(string.Format(CultureInfo.InvariantCulture, "{0}#syntaxItem", typeNameForHelp)); + mshObject.TypeNames.Add(string.Create(CultureInfo.InvariantCulture, $"{typeNameForHelp}#syntaxItem")); mshObject.Properties.Add(new PSNoteProperty("name", cmdletName)); mshObject.Properties.Add(new PSNoteProperty("CommonParameters", common)); @@ -263,7 +263,7 @@ private static void AddSyntaxParametersProperties(PSObject obj, IEnumerable attributes = new Collection(parameter.Attributes); @@ -339,7 +339,7 @@ private static void AddParameterValueGroupProperties(PSObject obj, string[] valu PSObject paramValueGroup = new PSObject(); paramValueGroup.TypeNames.Clear(); - paramValueGroup.TypeNames.Add(string.Format(CultureInfo.InvariantCulture, "{0}#parameterValueGroup", DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp)); + paramValueGroup.TypeNames.Add(string.Create(CultureInfo.InvariantCulture, $"{DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp}#parameterValueGroup")); ArrayList paramValue = new ArrayList(values); @@ -359,7 +359,7 @@ internal static void AddParametersProperties(PSObject obj, Dictionary GetHelpInfo(DscResourceSearcher searcher) } else if (!string.IsNullOrEmpty(moduleDir)) { - string[] splitPath = moduleDir.Split(Utils.Separators.Backslash); + string[] splitPath = moduleDir.Split('\\'); moduleName = splitPath[splitPath.Length - 1]; } @@ -279,7 +279,7 @@ private void LoadHelpFile(string helpFile, string helpFileIdentifier, string com } if (e != null) - s_tracer.WriteLine("Error occured in DscResourceHelpProvider {0}", e.Message); + s_tracer.WriteLine("Error occurred in DscResourceHelpProvider {0}", e.Message); if (reportErrors && (e != null)) { diff --git a/src/System.Management.Automation/help/HelpCategoryInvalidException.cs b/src/System.Management.Automation/help/HelpCategoryInvalidException.cs index d9ccee7bae6..8ab4fa37ca6 100644 --- a/src/System.Management.Automation/help/HelpCategoryInvalidException.cs +++ b/src/System.Management.Automation/help/HelpCategoryInvalidException.cs @@ -16,7 +16,6 @@ namespace Microsoft.PowerShell.Commands /// The exception that is thrown when there is no help category matching /// a specific input string. /// - [Serializable] public class HelpCategoryInvalidException : ArgumentException, IContainsErrorRecord { /// @@ -113,30 +112,11 @@ public override string Message /// /// Serialization information. /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected HelpCategoryInvalidException(SerializationInfo info, - StreamingContext context) - : base(info, context) + StreamingContext context) { - _helpCategory = info.GetString("HelpCategory"); - CreateErrorRecord(); - } - - /// - /// Populates a with the - /// data needed to serialize the HelpCategoryInvalidException object. - /// - /// The to populate with data. - /// The destination for this serialization. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw PSTraceSource.NewArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - - info.AddValue("HelpCategory", this._helpCategory); + throw new NotSupportedException(); } #endregion Serialization diff --git a/src/System.Management.Automation/help/HelpCommands.cs b/src/System.Management.Automation/help/HelpCommands.cs index edf8d9e4355..3bed2a26820 100644 --- a/src/System.Management.Automation/help/HelpCommands.cs +++ b/src/System.Management.Automation/help/HelpCommands.cs @@ -231,8 +231,8 @@ public SwitchParameter ShowWindow // The following variable controls the view. private HelpView _viewTokenToAdd = HelpView.Default; - private readonly Stopwatch _timer = new Stopwatch(); #if LEGACYTELEMETRY + private readonly Stopwatch _timer = new Stopwatch(); private bool _updatedHelp; #endif @@ -245,7 +245,9 @@ public SwitchParameter ShowWindow /// protected override void BeginProcessing() { +#if LEGACYTELEMETRY _timer.Start(); +#endif } /// @@ -253,6 +255,17 @@ protected override void BeginProcessing() /// protected override void ProcessRecord() { +#if !UNIX + string fileSystemPath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(this.Name); + string normalizedName = FileSystemProvider.NormalizePath(fileSystemPath); + // In a restricted session, do not allow help on network paths or device paths, because device paths can be used to bypass the restrictions. + if (Utils.IsSessionRestricted(this.Context) && (FileSystemProvider.PathIsNetworkPath(normalizedName) || Utils.PathIsDevicePath(normalizedName))) { + Exception e = new ArgumentException(HelpErrors.NoNetworkCommands, "Name"); + ErrorRecord errorRecord = new ErrorRecord(e, "CommandNameNotAllowed", ErrorCategory.InvalidArgument, null); + this.ThrowTerminatingError(errorRecord); + } +#endif + HelpSystem helpSystem = this.Context.HelpSystem; try { @@ -321,9 +334,9 @@ protected override void ProcessRecord() countOfHelpInfos++; } +#if LEGACYTELEMETRY _timer.Stop(); -#if LEGACYTELEMETRY if (!string.IsNullOrEmpty(Name)) Microsoft.PowerShell.Telemetry.Internal.TelemetryAPI.ReportGetHelpTelemetry(Name, countOfHelpInfos, _timer.ElapsedMilliseconds, _updatedHelp); #endif @@ -422,7 +435,7 @@ private PSObject TransformView(PSObject originalHelpObject) if (originalHelpObject.TypeNames.Count == 0) { - string typeToAdd = string.Format(CultureInfo.InvariantCulture, "HelpInfo#{0}", tokenToAdd); + string typeToAdd = string.Create(CultureInfo.InvariantCulture, $"HelpInfo#{tokenToAdd}"); objectToReturn.TypeNames.Add(typeToAdd); } else @@ -438,7 +451,7 @@ private PSObject TransformView(PSObject originalHelpObject) continue; } - string typeToAdd = string.Format(CultureInfo.InvariantCulture, "{0}#{1}", typeName, tokenToAdd); + string typeToAdd = string.Create(CultureInfo.InvariantCulture, $"{typeName}#{tokenToAdd}"); s_tracer.WriteLine("Adding type {0}", typeToAdd); objectToReturn.TypeNames.Add(typeToAdd); } @@ -502,11 +515,11 @@ private void GetAndWriteParameterInfo(HelpInfo helpInfo) } /// - /// Validates input parameters. + /// Validates input parameters. /// /// Category specified by the user. /// - /// If the request cant be serviced. + /// If the request can't be serviced. /// private void ValidateAndThrowIfError(HelpCategory cat) { @@ -721,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 } @@ -735,8 +746,8 @@ public static class GetHelpCodeMethods { /// /// Checks whether the default runspace associated with the current thread has the standard Get-Help cmdlet. - /// - /// True if Get-Help is found, false otherwise. + /// + /// True if Get-Help is found, false otherwise. private static bool DoesCurrentRunspaceIncludeCoreHelpCmdlet() { InitialSessionState iss = Runspace.DefaultRunspace.InitialSessionState; @@ -819,8 +830,7 @@ public static string GetHelpUri(PSObject commandInfoPSObject) string cmdName = cmdInfo.Name; if (!string.IsNullOrEmpty(cmdInfo.ModuleName)) { - cmdName = string.Format(CultureInfo.InvariantCulture, - "{0}\\{1}", cmdInfo.ModuleName, cmdInfo.Name); + cmdName = string.Create(CultureInfo.InvariantCulture, $"{cmdInfo.ModuleName}\\{cmdInfo.Name}"); } if (DoesCurrentRunspaceIncludeCoreHelpCmdlet()) @@ -839,7 +849,7 @@ public static string GetHelpUri(PSObject commandInfoPSObject) foreach ( Uri result in currentContext.HelpSystem.ExactMatchHelp(helpRequest).Select( - helpInfo => helpInfo.GetUriForOnlineHelp()).Where(result => result != null)) + helpInfo => helpInfo.GetUriForOnlineHelp()).Where(static result => result != null)) { return result.OriginalString; } diff --git a/src/System.Management.Automation/help/HelpCommentsParser.cs b/src/System.Management.Automation/help/HelpCommentsParser.cs index 7b853381d4d..36d0cb6450e 100644 --- a/src/System.Management.Automation/help/HelpCommentsParser.cs +++ b/src/System.Management.Automation/help/HelpCommentsParser.cs @@ -20,7 +20,7 @@ namespace System.Management.Automation /// /// Parses help comments and turns them into HelpInfo objects. /// - internal class HelpCommentsParser + internal sealed class HelpCommentsParser { private HelpCommentsParser() { @@ -482,7 +482,7 @@ private void BuildSyntaxForParameterSet(XmlElement command, XmlElement syntax, M CompiledCommandParameter parameter = mergedParameter.Parameter; ParameterSetSpecificMetadata parameterSetData = parameter.GetParameterSetData(1u << i); string description = GetParameterDescription(parameter.Name); - bool supportsWildcards = parameter.CompiledAttributes.Any(attribute => attribute is SupportsWildcardsAttribute); + bool supportsWildcards = parameter.CompiledAttributes.Any(static attribute => attribute is SupportsWildcardsAttribute); XmlElement parameterElement = BuildXmlForParameter(parameter.Name, parameterSetData.IsMandatory, parameterSetData.ValueFromPipeline, parameterSetData.ValueFromPipelineByPropertyName, @@ -628,7 +628,7 @@ private static string GetSection(List commentLines, ref int i) start++; } - sb.Append(line.Substring(start)); + sb.Append(line.AsSpan(start)); sb.Append('\n'); } diff --git a/src/System.Management.Automation/help/HelpFileHelpInfo.cs b/src/System.Management.Automation/help/HelpFileHelpInfo.cs index adb2afac47f..104111777ef 100644 --- a/src/System.Management.Automation/help/HelpFileHelpInfo.cs +++ b/src/System.Management.Automation/help/HelpFileHelpInfo.cs @@ -9,7 +9,7 @@ namespace System.Management.Automation /// Class HelpFileHelpInfo keeps track of help information to be returned by /// command help provider. /// - internal class HelpFileHelpInfo : HelpInfo + internal sealed class HelpFileHelpInfo : HelpInfo { /// /// Constructor for HelpFileHelpInfo. diff --git a/src/System.Management.Automation/help/HelpFileHelpProvider.cs b/src/System.Management.Automation/help/HelpFileHelpProvider.cs index 8900a7167b3..a6c21a3bacc 100644 --- a/src/System.Management.Automation/help/HelpFileHelpProvider.cs +++ b/src/System.Management.Automation/help/HelpFileHelpProvider.cs @@ -170,11 +170,7 @@ private Collection FilterToLatestModuleVersion(Collection filesM { string fileName = Path.GetFileName(file); - if (!fileNameHash.Contains(fileName)) - { - fileNameHash.Add(fileName); - } - else + if (!fileNameHash.Add(fileName)) { // If the file need to be removed, add it to matchedFilesToRemove, if not already present. if (!matchedFilesToRemove.Contains(file)) @@ -275,9 +271,8 @@ private static void GetModuleNameAndVersion(string psmodulePathRoot, string file if (filePath.StartsWith(psmodulePathRoot, StringComparison.OrdinalIgnoreCase)) { - var moduleRootSubPath = filePath.Remove(0, psmodulePathRoot.Length).TrimStart(Utils.Separators.Directory); + var moduleRootSubPath = filePath.Remove(0, psmodulePathRoot.Length); var pathParts = moduleRootSubPath.Split(Utils.Separators.Directory, StringSplitOptions.RemoveEmptyEntries); - moduleName = pathParts[0]; var potentialVersion = pathParts[1]; Version result; @@ -368,9 +363,9 @@ internal Collection GetExtendedSearchPaths() { // Get all the directories under the module path // * and SearchOption.AllDirectories gets all the version directories. - string[] directories = Directory.GetDirectories(psModulePath, "*", SearchOption.AllDirectories); + IEnumerable directories = Directory.EnumerateDirectories(psModulePath, "*", SearchOption.AllDirectories); - var possibleModuleDirectories = directories.Where(directory => !ModuleUtils.IsPossibleResourceDirectory(directory)); + var possibleModuleDirectories = directories.Where(static directory => !ModuleUtils.IsPossibleResourceDirectory(directory)); foreach (string directory in possibleModuleDirectories) { diff --git a/src/System.Management.Automation/help/HelpNotFoundException.cs b/src/System.Management.Automation/help/HelpNotFoundException.cs index 6008b4afbc1..109cd21dbd9 100644 --- a/src/System.Management.Automation/help/HelpNotFoundException.cs +++ b/src/System.Management.Automation/help/HelpNotFoundException.cs @@ -15,7 +15,6 @@ namespace Microsoft.PowerShell.Commands /// /// The exception that is thrown when there is no help found for a topic. /// - [Serializable] public class HelpNotFoundException : SystemException, IContainsErrorRecord { /// @@ -119,32 +118,13 @@ public override string Message /// /// Serialization information. /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected HelpNotFoundException(SerializationInfo info, StreamingContext context) - : base(info, context) { - _helpTopic = info.GetString("HelpTopic"); - CreateErrorRecord(); + throw new NotSupportedException(); } - - /// - /// Populates a with the - /// data needed to serialize the HelpNotFoundException object. - /// - /// The to populate with data. - /// The destination for this serialization. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw PSTraceSource.NewArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - - info.AddValue("HelpTopic", this._helpTopic); - } - + #endregion Serialization } } diff --git a/src/System.Management.Automation/help/HelpProvider.cs b/src/System.Management.Automation/help/HelpProvider.cs index 7e2adbe552f..3280a8d2540 100644 --- a/src/System.Management.Automation/help/HelpProvider.cs +++ b/src/System.Management.Automation/help/HelpProvider.cs @@ -226,8 +226,7 @@ internal string GetDefaultShellSearchPath() string shellID = this.HelpSystem.ExecutionContext.ShellID; // Beginning in PowerShell 6.0.0.12, the $pshome is no longer registry specified, we search the application base instead. // We use executing assemblies location in case registry entry not found - return Utils.GetApplicationBase(shellID) - ?? Path.GetDirectoryName(PsUtils.GetMainModule(System.Diagnostics.Process.GetCurrentProcess()).FileName); + return Utils.GetApplicationBase(shellID) ?? Path.GetDirectoryName(Environment.ProcessPath); } /// diff --git a/src/System.Management.Automation/help/HelpSystem.cs b/src/System.Management.Automation/help/HelpSystem.cs index a7c86bd5ebd..8880e022cac 100644 --- a/src/System.Management.Automation/help/HelpSystem.cs +++ b/src/System.Management.Automation/help/HelpSystem.cs @@ -903,12 +903,12 @@ internal enum HelpCategory /// All = 0xFFFFF, - /// + /// /// Default Help. /// DefaultHelp = 0x1000, - /// + /// /// Help for a Configuration. /// Configuration = 0x4000, diff --git a/src/System.Management.Automation/help/HelpUtils.cs b/src/System.Management.Automation/help/HelpUtils.cs index d4f2abe255b..ab4a39f362a 100644 --- a/src/System.Management.Automation/help/HelpUtils.cs +++ b/src/System.Management.Automation/help/HelpUtils.cs @@ -41,7 +41,7 @@ internal static string GetModuleBaseForUserHelp(string moduleBase, string module // In case of other modules, the help is under moduleBase/ or // under moduleBase//. // The code below creates a similar layout for CurrentUser scope. - // If the the scope is AllUsers, then the help goes under moduleBase. + // If the scope is AllUsers, then the help goes under moduleBase. var userHelpPath = GetUserHomeHelpSearchPath(); string moduleBaseParent = Directory.GetParent(moduleBase).Name; diff --git a/src/System.Management.Automation/help/MUIFileSearcher.cs b/src/System.Management.Automation/help/MUIFileSearcher.cs index d6d3313b68d..dd437fa90c0 100644 --- a/src/System.Management.Automation/help/MUIFileSearcher.cs +++ b/src/System.Management.Automation/help/MUIFileSearcher.cs @@ -10,7 +10,7 @@ namespace System.Management.Automation { - internal class MUIFileSearcher + internal sealed class MUIFileSearcher { /// /// Constructor. It is private so that MUIFileSearcher is used only internal for this class. @@ -57,6 +57,14 @@ private MUIFileSearcher(string target, Collection searchPaths) /// internal SearchMode SearchMode { get; } = SearchMode.Unique; + private static readonly System.IO.EnumerationOptions _enumerationOptions = new() + { + IgnoreInaccessible = false, + AttributesToSkip = 0, + MatchType = MatchType.Win32, + MatchCasing = MatchCasing.CaseInsensitive, + }; + private Collection _result = null; /// @@ -113,52 +121,11 @@ private void SearchForFiles() } } - private static string[] GetFiles(string path, string pattern) - { -#if UNIX - // On Linux, file names are case sensitive, so we need to add - // extra logic to select the files that match the given pattern. - var result = new List(); - string[] files = Directory.GetFiles(path); - - var wildcardPattern = WildcardPattern.ContainsWildcardCharacters(pattern) - ? WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase) - : null; - - foreach (string filePath in files) - { - if (filePath.Contains(pattern, StringComparison.OrdinalIgnoreCase)) - { - result.Add(filePath); - break; - } - - if (wildcardPattern != null) - { - string fileName = Path.GetFileName(filePath); - if (wildcardPattern.IsMatch(fileName)) - { - result.Add(filePath); - } - } - } - - return result.ToArray(); -#else - return Directory.GetFiles(path, pattern); -#endif - } - private void AddFiles(string muiDirectory, string directory, string pattern) { if (Directory.Exists(muiDirectory)) { - string[] files = GetFiles(muiDirectory, pattern); - - if (files == null) - return; - - foreach (string file in files) + foreach (string file in Directory.EnumerateFiles(muiDirectory, pattern, _enumerationOptions)) { string path = Path.Combine(muiDirectory, file); diff --git a/src/System.Management.Automation/help/MamlClassHelpInfo.cs b/src/System.Management.Automation/help/MamlClassHelpInfo.cs index 042ab21e703..579d72c87fe 100644 --- a/src/System.Management.Automation/help/MamlClassHelpInfo.cs +++ b/src/System.Management.Automation/help/MamlClassHelpInfo.cs @@ -86,7 +86,7 @@ internal MamlClassHelpInfo Copy() internal MamlClassHelpInfo Copy(HelpCategory newCategoryToUse) { MamlClassHelpInfo result = new MamlClassHelpInfo(_fullHelpObject.Copy(), newCategoryToUse); - result.FullHelp.Properties["Category"].Value = newCategoryToUse; + result.FullHelp.Properties["Category"].Value = newCategoryToUse.ToString(); return result; } diff --git a/src/System.Management.Automation/help/MamlCommandHelpInfo.cs b/src/System.Management.Automation/help/MamlCommandHelpInfo.cs index 774ef1fc1c0..8ae7b0a32cd 100644 --- a/src/System.Management.Automation/help/MamlCommandHelpInfo.cs +++ b/src/System.Management.Automation/help/MamlCommandHelpInfo.cs @@ -315,7 +315,7 @@ internal MamlCommandHelpInfo MergeProviderSpecificHelp(PSObject cmdletHelp, PSOb /// Name of the property for which text needs to be extracted. /// /// - private string ExtractTextForHelpProperty(PSObject psObject, string propertyName) + private static string ExtractTextForHelpProperty(PSObject psObject, string propertyName) { if (psObject == null) return string.Empty; @@ -336,14 +336,14 @@ private string ExtractTextForHelpProperty(PSObject psObject, string propertyName /// /// /// - private string ExtractText(PSObject psObject) + private static string ExtractText(PSObject psObject) { if (psObject == null) { return string.Empty; } - // I think every cmdlet description should atleast have 400 characters... + // I think every cmdlet description should at least have 400 characters... // so starting with this assumption..I did an average of all the cmdlet // help content available at the time of writing this code and came up // with this number. @@ -441,7 +441,7 @@ internal MamlCommandHelpInfo Copy() internal MamlCommandHelpInfo Copy(HelpCategory newCategoryToUse) { MamlCommandHelpInfo result = new MamlCommandHelpInfo(_fullHelpObject.Copy(), newCategoryToUse); - result.FullHelp.Properties["Category"].Value = newCategoryToUse; + result.FullHelp.Properties["Category"].Value = newCategoryToUse.ToString(); return result; } diff --git a/src/System.Management.Automation/help/MamlNode.cs b/src/System.Management.Automation/help/MamlNode.cs index fe4e803a294..ecf565c942b 100644 --- a/src/System.Management.Automation/help/MamlNode.cs +++ b/src/System.Management.Automation/help/MamlNode.cs @@ -123,7 +123,7 @@ internal PSObject PSObject /// /// In this case, an PSObject that wraps string "atomic xml text" will be returned with following properties /// attribute => name - /// 3. Composite xml, which is an xmlNode with structured child nodes, but not a special case for Maml formating. + /// 3. Composite xml, which is an xmlNode with structured child nodes, but not a special case for Maml formatting. /// /// /// single child node text @@ -209,7 +209,7 @@ private PSObject GetPSObject(XmlNode xmlNode) { mshObject = new PSObject(GetInsidePSObject(xmlNode)); // Add typeNames to this MSHObject and create views so that - // the ouput is readable. This is done only for complex nodes. + // the output is readable. This is done only for complex nodes. mshObject.TypeNames.Clear(); if (xmlNode.Attributes["type"] != null) @@ -321,7 +321,7 @@ private Hashtable GetInsideProperties(XmlNode xmlNode) /// /// Node whose children are verified for maml. /// - private void RemoveUnsupportedNodes(XmlNode xmlNode) + private static void RemoveUnsupportedNodes(XmlNode xmlNode) { // Start with the first child.. // We want to modify only children.. @@ -1109,7 +1109,7 @@ private static string GetPreformattedText(string text) // It is discouraged to use tab in preformatted text. string noTabText = text.Replace("\t", " "); - string[] lines = noTabText.Split(Utils.Separators.Newline); + string[] lines = noTabText.Split('\n'); string[] trimedLines = TrimLines(lines); if (trimedLines == null || trimedLines.Length == 0) @@ -1212,7 +1212,7 @@ private static int GetIndentation(string line) if (IsEmptyLine(line)) return 0; - string leftTrimedLine = line.TrimStart(Utils.Separators.Space); + string leftTrimedLine = line.TrimStart(' '); return line.Length - leftTrimedLine.Length; } diff --git a/src/System.Management.Automation/help/PSClassHelpProvider.cs b/src/System.Management.Automation/help/PSClassHelpProvider.cs index e4c74c6398a..2d80b2cc863 100644 --- a/src/System.Management.Automation/help/PSClassHelpProvider.cs +++ b/src/System.Management.Automation/help/PSClassHelpProvider.cs @@ -279,7 +279,7 @@ private void LoadHelpFile(string helpFile, string helpFileIdentifier, string com } if (e != null) - s_tracer.WriteLine("Error occured in PSClassHelpProvider {0}", e.Message); + s_tracer.WriteLine("Error occurred in PSClassHelpProvider {0}", e.Message); if (reportErrors && (e != null)) { diff --git a/src/System.Management.Automation/help/ProviderContext.cs b/src/System.Management.Automation/help/ProviderContext.cs index cb24ece0593..817e2b3f0e6 100644 --- a/src/System.Management.Automation/help/ProviderContext.cs +++ b/src/System.Management.Automation/help/ProviderContext.cs @@ -141,7 +141,7 @@ Runspaces.SessionStateProviderEntry sessionStateProvider in } } - // ok we have path and valid provider that supplys content..initialize the provider + // ok we have path and valid provider that supplies content..initialize the provider // and get the help content for the path. cmdletProvider.Start(providerInfo, cmdletProviderContext); // There should be exactly one resolved path. diff --git a/src/System.Management.Automation/help/ProviderHelpInfo.cs b/src/System.Management.Automation/help/ProviderHelpInfo.cs index e159892a73e..80859a2ddf8 100644 --- a/src/System.Management.Automation/help/ProviderHelpInfo.cs +++ b/src/System.Management.Automation/help/ProviderHelpInfo.cs @@ -10,7 +10,7 @@ namespace System.Management.Automation /// Class ProviderHelpInfo keeps track of help information to be returned by /// command help provider. /// - internal class ProviderHelpInfo : HelpInfo + internal sealed class ProviderHelpInfo : HelpInfo { /// /// Constructor for HelpProvider. @@ -101,7 +101,7 @@ internal string DetailedDescription return string.Empty; } - // I think every provider description should atleast have 400 characters... + // I think every provider description should at least have 400 characters... // so starting with this assumption..I did an average of all the help content // available at the time of writing this code and came up with this number. Text.StringBuilder result = new Text.StringBuilder(400); @@ -167,15 +167,9 @@ internal override bool MatchPatternInContent(WildcardPattern pattern) string synopsis = Synopsis; string detailedDescription = DetailedDescription; - if (synopsis == null) - { - synopsis = string.Empty; - } + synopsis ??= string.Empty; - if (detailedDescription == null) - { - detailedDescription = string.Empty; - } + detailedDescription ??= string.Empty; return pattern.IsMatch(synopsis) || pattern.IsMatch(detailedDescription); } diff --git a/src/System.Management.Automation/help/ProviderHelpProvider.cs b/src/System.Management.Automation/help/ProviderHelpProvider.cs index b28dcf86398..afc0c71c6c2 100644 --- a/src/System.Management.Automation/help/ProviderHelpProvider.cs +++ b/src/System.Management.Automation/help/ProviderHelpProvider.cs @@ -236,14 +236,20 @@ private void LoadHelpFile(ProviderInfo providerInfo) this.HelpSystem.TraceErrors(helpInfo.Errors); // Add snapin qualified type name for this command.. // this will enable customizations of the help object. - helpInfo.FullHelp.TypeNames.Insert(0, string.Format(CultureInfo.InvariantCulture, - "ProviderHelpInfo#{0}#{1}", providerInfo.PSSnapInName, helpInfo.Name)); + helpInfo.FullHelp.TypeNames.Insert( + index: 0, + string.Create( + CultureInfo.InvariantCulture, + $"ProviderHelpInfo#{providerInfo.PSSnapInName}#{helpInfo.Name}")); if (!string.IsNullOrEmpty(providerInfo.PSSnapInName)) { helpInfo.FullHelp.Properties.Add(new PSNoteProperty("PSSnapIn", providerInfo.PSSnapIn)); - helpInfo.FullHelp.TypeNames.Insert(1, string.Format(CultureInfo.InvariantCulture, - "ProviderHelpInfo#{0}", providerInfo.PSSnapInName)); + helpInfo.FullHelp.TypeNames.Insert( + index: 1, + string.Create( + CultureInfo.InvariantCulture, + $"ProviderHelpInfo#{providerInfo.PSSnapInName}")); } AddCache(providerInfo.PSSnapInName + "\\" + helpInfo.Name, helpInfo); diff --git a/src/System.Management.Automation/help/SaveHelpCommand.cs b/src/System.Management.Automation/help/SaveHelpCommand.cs index 94803728c99..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; } @@ -260,10 +260,7 @@ internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, } finally { - if (helpInfoDrive != null) - { - helpInfoDrive.Dispose(); - } + helpInfoDrive?.Dispose(); } } @@ -407,10 +404,7 @@ internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, } finally { - if (helpContentDrive != null) - { - helpContentDrive.Dispose(); - } + helpContentDrive?.Dispose(); } } } diff --git a/src/System.Management.Automation/help/SyntaxHelpInfo.cs b/src/System.Management.Automation/help/SyntaxHelpInfo.cs index 469ab94a913..261fdd7f849 100644 --- a/src/System.Management.Automation/help/SyntaxHelpInfo.cs +++ b/src/System.Management.Automation/help/SyntaxHelpInfo.cs @@ -7,7 +7,7 @@ namespace System.Management.Automation /// Class HelpFileHelpInfo keeps track of help information to be returned by /// command help provider. /// - internal class SyntaxHelpInfo : BaseCommandHelpInfo + internal sealed class SyntaxHelpInfo : BaseCommandHelpInfo { /// /// Constructor for SyntaxHelpInfo. diff --git a/src/System.Management.Automation/help/UpdatableHelpCommandBase.cs b/src/System.Management.Automation/help/UpdatableHelpCommandBase.cs index 49d1e67e5d9..687faa68246 100644 --- a/src/System.Management.Automation/help/UpdatableHelpCommandBase.cs +++ b/src/System.Management.Automation/help/UpdatableHelpCommandBase.cs @@ -60,7 +60,11 @@ public CultureInfo[] UICulture set { - if (value == null) return; + if (value == null) + { + return; + } + _language = new string[value.Length]; for (int index = 0; index < value.Length; index++) { @@ -74,8 +78,8 @@ public CultureInfo[] UICulture /// /// Gets or sets the credential parameter. /// - [Parameter()] - [Credential()] + [Parameter] + [Credential] public PSCredential Credential { get { return _credential; } @@ -166,22 +170,22 @@ private void HandleProgressChanged(object sender, UpdatableHelpProgressEventArgs /// /// Static constructor /// - /// NOTE: FWLinks for core PowerShell modules are needed since they get loaded as snapins in a Remoting Endpoint. + /// NOTE: HelpInfoUri for core PowerShell modules are needed since they get loaded as snapins in a Remoting Endpoint. /// When we moved to modules in V3, we were not able to make this change as it was a risky change to make at that time. /// static UpdatableHelpCommandBase() { s_metadataCache = new Dictionary(StringComparer.OrdinalIgnoreCase); - // TODO: assign real TechNet addresses + // NOTE: The HelpInfoUri must be updated with each release. - s_metadataCache.Add("Microsoft.PowerShell.Diagnostics", "https://aka.ms/powershell71-help"); - s_metadataCache.Add("Microsoft.PowerShell.Core", "https://aka.ms/powershell71-help"); - s_metadataCache.Add("Microsoft.PowerShell.Utility", "https://aka.ms/powershell71-help"); - s_metadataCache.Add("Microsoft.PowerShell.Host", "https://aka.ms/powershell71-help"); - s_metadataCache.Add("Microsoft.PowerShell.Management", "https://aka.ms/powershell71-help"); - s_metadataCache.Add("Microsoft.PowerShell.Security", "https://aka.ms/powershell71-help"); - s_metadataCache.Add("Microsoft.WSMan.Management", "https://aka.ms/powershell71-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"); } /// @@ -206,9 +210,7 @@ internal UpdatableHelpCommandBase(UpdatableHelpCommandType commandType) _exceptions = new Dictionary(); _helpSystem.OnProgressChanged += HandleProgressChanged; - Random rand = new Random(); - - activityId = rand.Next(); + activityId = Random.Shared.Next(); } #endregion @@ -443,7 +445,10 @@ internal void Process(IEnumerable moduleNames, IEnumerableModule objects given by the user. internal void Process(IEnumerable modules) { - if (modules == null || !modules.Any()) { return; } + if (modules == null || !modules.Any()) + { + return; + } var helpModules = new Dictionary, UpdatableHelpModuleInfo>(); @@ -509,6 +514,7 @@ private void ProcessModule(UpdatableHelpModuleInfo module) // Win8: 572882 When the system locale is English and the UI is JPN, // running "update-help" still downs English help content. var cultures = _language ?? _helpSystem.GetCurrentUICulture(); + UpdatableHelpSystemException implicitCultureNotSupported = null; foreach (string culture in cultures) { @@ -549,7 +555,8 @@ private void ProcessModule(UpdatableHelpModuleInfo module) #endif catch (UpdatableHelpSystemException e) { - if (e.FullyQualifiedErrorId == "HelpCultureNotSupported") + if (e.FullyQualifiedErrorId == "HelpCultureNotSupported" + || e.FullyQualifiedErrorId == "UnableToRetrieveHelpInfoXml") { installed = false; @@ -558,6 +565,12 @@ private void ProcessModule(UpdatableHelpModuleInfo module) // Display the error message only if we are not using the fallback chain ProcessException(module.ModuleName, culture, e); } + else + { + // Hold first exception, it will be displayed if fallback chain fails + WriteVerbose(StringUtil.Format(HelpDisplayStrings.HelpCultureNotSupportedFallback, e.Message)); + implicitCultureNotSupported ??= e; + } } else { @@ -581,13 +594,19 @@ private void ProcessModule(UpdatableHelpModuleInfo module) } } - // If -Language is not specified, we only install + // If -UICulture is not specified, we only install // one culture from the fallback chain if (_language == null && installed) { - break; + return; } } + + // If the exception is not null and did not return early, then all of the fallback chain failed + if (implicitCultureNotSupported != null) + { + ProcessException(module.ModuleName, cultures.First(), implicitCultureNotSupported); + } } /// @@ -652,7 +671,7 @@ internal bool IsUpdateNecessary(UpdatableHelpModuleInfo module, UpdatableHelpInf } // Culture check - if (!newHelpInfo.IsCultureSupported(culture)) + if (!newHelpInfo.IsCultureSupported(culture.Name)) { throw new UpdatableHelpSystemException("HelpCultureNotSupported", StringUtil.Format(HelpDisplayStrings.HelpCultureNotSupported, @@ -778,7 +797,7 @@ internal IEnumerable ResolvePath(string path, bool recurse, bool isLiter /// /// Path to resolve. /// A list of directories. - private IEnumerable RecursiveResolvePathHelper(string path) + private static IEnumerable RecursiveResolvePathHelper(string path) { if (System.IO.Directory.Exists(path)) { @@ -886,6 +905,7 @@ public enum UpdateHelpScope { /// /// Save the help content to the user directory. + /// CurrentUser, /// diff --git a/src/System.Management.Automation/help/UpdatableHelpInfo.cs b/src/System.Management.Automation/help/UpdatableHelpInfo.cs index 5af3fd57b6d..8170de90654 100644 --- a/src/System.Management.Automation/help/UpdatableHelpInfo.cs +++ b/src/System.Management.Automation/help/UpdatableHelpInfo.cs @@ -1,9 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; +using System.Linq; using System.Management.Automation.Internal; using System.Text; @@ -37,6 +39,44 @@ internal CultureSpecificUpdatableHelp(CultureInfo culture, Version version) /// Supported culture. /// internal CultureInfo Culture { get; set; } + + /// + /// Enumerates fallback chain (parents) of the culture, including itself. + /// + /// Culture to enumerate + /// + /// Examples: + /// en-GB => { en-GB, en } + /// zh-Hans-CN => { zh-Hans-CN, zh-Hans, zh }. + /// + /// An enumerable list of culture names. + internal static IEnumerable GetCultureFallbackChain(CultureInfo culture) + { + // We use just names instead because comparing two CultureInfo objects + // can fail if they are created using different means + while (culture != null) + { + if (string.IsNullOrEmpty(culture.Name)) + { + yield break; + } + + yield return culture.Name; + + culture = culture.Parent; + } + } + + /// + /// Checks if a culture is supported. + /// + /// Name of the culture to check. + /// True if supported, false if not. + internal bool IsCultureSupported(string cultureName) + { + Debug.Assert(cultureName != null, $"{nameof(cultureName)} may not be null"); + return GetCultureFallbackChain(Culture).Any(fallback => fallback == cultureName); + } } /// @@ -99,22 +139,12 @@ internal bool IsNewerVersion(UpdatableHelpInfo helpInfo, CultureInfo culture) /// /// Checks if a culture is supported. /// - /// Culture to check. + /// Name of the culture to check. /// True if supported, false if not. - internal bool IsCultureSupported(CultureInfo culture) + internal bool IsCultureSupported(string cultureName) { - Debug.Assert(culture != null); - - foreach (CultureSpecificUpdatableHelp updatableHelpItem in UpdatableHelpItems) - { - if (string.Equals(updatableHelpItem.Culture.Name, culture.Name, - StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; + Debug.Assert(cultureName != null, $"{nameof(cultureName)} may not be null"); + return UpdatableHelpItems.Any(item => item.IsCultureSupported(cultureName)); } /// diff --git a/src/System.Management.Automation/help/UpdatableHelpModuleInfo.cs b/src/System.Management.Automation/help/UpdatableHelpModuleInfo.cs index b46717920f4..ccf0b95f0f6 100644 --- a/src/System.Management.Automation/help/UpdatableHelpModuleInfo.cs +++ b/src/System.Management.Automation/help/UpdatableHelpModuleInfo.cs @@ -28,7 +28,6 @@ internal class UpdatableHelpModuleInfo internal UpdatableHelpModuleInfo(string name, Guid guid, string path, string uri) { Debug.Assert(!string.IsNullOrEmpty(name)); - Debug.Assert(guid != Guid.Empty); Debug.Assert(!string.IsNullOrEmpty(path)); Debug.Assert(!string.IsNullOrEmpty(uri)); diff --git a/src/System.Management.Automation/help/UpdatableHelpSystem.cs b/src/System.Management.Automation/help/UpdatableHelpSystem.cs index 31fe5362d77..6ab8d469a97 100644 --- a/src/System.Management.Automation/help/UpdatableHelpSystem.cs +++ b/src/System.Management.Automation/help/UpdatableHelpSystem.cs @@ -28,7 +28,6 @@ namespace System.Management.Automation.Help /// /// Updatable help system exception. /// - [Serializable] internal class UpdatableHelpSystemException : Exception { /// @@ -294,19 +293,13 @@ internal IEnumerable GetCurrentUICulture() { CultureInfo culture = CultureInfo.CurrentUICulture; - while (culture != null) + // Allow tests to override system culture + if (InternalTestHooks.CurrentUICulture != null) { - if (string.IsNullOrEmpty(culture.Name)) - { - yield break; - } - - yield return culture.Name; - - culture = culture.Parent; + culture = InternalTestHooks.CurrentUICulture; } - yield break; + return CultureSpecificUpdatableHelp.GetCultureFallbackChain(culture); } #region Help Metadata Retrieval @@ -555,7 +548,10 @@ internal UpdatableHelpInfo CreateHelpInfo(string xml, string moduleName, Guid mo } catch (XmlException e) { - if (ignoreValidationException) { return null; } + if (ignoreValidationException) + { + return null; + } throw new UpdatableHelpSystemException(HelpInfoXmlValidationFailure, e.Message, ErrorCategory.InvalidData, null, e); @@ -591,13 +587,9 @@ internal UpdatableHelpInfo CreateHelpInfo(string xml, string moduleName, Guid mo if (!string.IsNullOrEmpty(currentCulture)) { - IEnumerable patternList = SessionStateUtilities.CreateWildcardsFromStrings( - globPatterns: new[] { currentCulture }, - options: WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); - for (int i = 0; i < updatableHelpItem.Length; i++) { - if (SessionStateUtilities.MatchesAnyWildcardPattern(updatableHelpItem[i].Culture.Name, patternList, true)) + if (updatableHelpItem[i].IsCultureSupported(currentCulture)) { helpInfo.HelpContentUriCollection.Add(new UpdatableHelpUri(moduleName, moduleGuid, updatableHelpItem[i].Culture, uri)); } @@ -1097,14 +1089,14 @@ internal void InstallHelpContent(UpdatableHelpCommandType commandType, Execution #if UNIX private static bool ExpandArchive(string source, string destination) { - bool sucessfulDecompression = false; + bool successfulDecompression = false; try { using (ZipArchive zipArchive = ZipFile.Open(source, ZipArchiveMode.Read)) { zipArchive.ExtractToDirectory(destination); - sucessfulDecompression = true; + successfulDecompression = true; } } catch (ArgumentException) { } @@ -1116,7 +1108,7 @@ private static bool ExpandArchive(string source, string destination) catch (UnauthorizedAccessException) { } catch (ObjectDisposedException) { } - return sucessfulDecompression; + return successfulDecompression; } #endif @@ -1137,9 +1129,9 @@ private static void UnzipHelpContent(ExecutionContext context, string srcPath, s } string sourceDirectory = Path.GetDirectoryName(srcPath); - bool sucessfulDecompression = false; + bool successfulDecompression = false; #if UNIX - sucessfulDecompression = ExpandArchive(Path.Combine(sourceDirectory, Path.GetFileName(srcPath)), destPath); + successfulDecompression = ExpandArchive(Path.Combine(sourceDirectory, Path.GetFileName(srcPath)), destPath); #else // Cabinet API doesn't handle the trailing back slash if (!sourceDirectory.EndsWith('\\')) @@ -1152,9 +1144,9 @@ private static void UnzipHelpContent(ExecutionContext context, string srcPath, s destPath += "\\"; } - sucessfulDecompression = CabinetExtractorFactory.GetCabinetExtractor().Extract(Path.GetFileName(srcPath), sourceDirectory, destPath); + successfulDecompression = CabinetExtractorFactory.GetCabinetExtractor().Extract(Path.GetFileName(srcPath), sourceDirectory, destPath); #endif - if (!sucessfulDecompression) + if (!successfulDecompression) { throw new UpdatableHelpSystemException("UnableToExtract", StringUtil.Format(HelpDisplayStrings.UnzipFailure), ErrorCategory.InvalidOperation, null, null); diff --git a/src/System.Management.Automation/help/UpdatableHelpUri.cs b/src/System.Management.Automation/help/UpdatableHelpUri.cs index 8b854e82a5d..82bbb4016fb 100644 --- a/src/System.Management.Automation/help/UpdatableHelpUri.cs +++ b/src/System.Management.Automation/help/UpdatableHelpUri.cs @@ -21,7 +21,6 @@ internal class UpdatableHelpUri internal UpdatableHelpUri(string moduleName, Guid moduleGuid, CultureInfo culture, string resolvedUri) { Debug.Assert(!string.IsNullOrEmpty(moduleName)); - Debug.Assert(moduleGuid != Guid.Empty); Debug.Assert(!string.IsNullOrEmpty(resolvedUri)); ModuleName = moduleName; diff --git a/src/System.Management.Automation/help/UpdateHelpCommand.cs b/src/System.Management.Automation/help/UpdateHelpCommand.cs index 01b17cbad7e..9df82699a32 100644 --- a/src/System.Management.Automation/help/UpdateHelpCommand.cs +++ b/src/System.Management.Automation/help/UpdateHelpCommand.cs @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; +using System.Linq; using System.Management.Automation; using System.Management.Automation.Help; using System.Management.Automation.Internal; @@ -181,6 +182,16 @@ protected override void ProcessRecord() _isInitialized = true; } + + // check if there is an UI, if not Throw out terminating error. + var cultures = _language ?? _helpSystem.GetCurrentUICulture(); + if (!cultures.Any()) + { + string cultureString = string.IsNullOrEmpty(CultureInfo.CurrentCulture.Name) ? CultureInfo.CurrentCulture.DisplayName : CultureInfo.CurrentCulture.Name; + string errMsg = StringUtil.Format(HelpDisplayStrings.FailedToUpdateHelpWithLocaleNoUICulture, cultureString); + ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "FailedToUpdateHelpWithLocaleNoUICulture", ErrorCategory.InvalidOperation, targetObject: null); + ThrowTerminatingError(error); + } base.Process(_module, FullyQualifiedModule); @@ -214,6 +225,14 @@ protected override void ProcessRecord() /// True if the module has been processed, false if not. internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, string culture) { + // Simulate culture not found + if (InternalTestHooks.ThrowHelpCultureNotSupported) + { + throw new UpdatableHelpSystemException("HelpCultureNotSupported", + StringUtil.Format(HelpDisplayStrings.HelpCultureNotSupported, culture, "en-US"), + ErrorCategory.InvalidOperation, null, null); + } + UpdatableHelpInfo currentHelpInfo = null; UpdatableHelpInfo newHelpInfo = null; string helpInfoUri = null; @@ -322,10 +341,7 @@ internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, } finally { - if (helpInfoDrive != null) - { - helpInfoDrive.Dispose(); - } + helpInfoDrive?.Dispose(); } } else diff --git a/src/System.Management.Automation/logging/LogProvider.cs b/src/System.Management.Automation/logging/LogProvider.cs index a46f181b262..e02807a38e8 100644 --- a/src/System.Management.Automation/logging/LogProvider.cs +++ b/src/System.Management.Automation/logging/LogProvider.cs @@ -102,6 +102,37 @@ internal LogProvider() /// internal abstract void LogSettingsEvent(LogContext logContext, string variableName, string value, string previousValue); + /// + /// Provider interface function for logging AmsiUtil State event. + /// + /// This the action performed in AmsiUtil class, like init, scan, etc. + /// The amsiContext handled - Session pair. + internal abstract void LogAmsiUtilStateEvent(string state, string context); + + /// + /// Provider interface function for logging WDAC query event. + /// + /// Name of the WDAC query. + /// Name of script file for policy query. Can be null value. + /// Query call succeed code. + /// Result code of WDAC query. + internal abstract void LogWDACQueryEvent( + string queryName, + string fileName, + int querySuccess, + int queryResult); + + /// + /// Provider interface function for logging WDAC audit event. + /// + /// Title of WDAC audit event. + /// WDAC audit event message. + /// FullyQualifiedId of WDAC audit event. + internal abstract void LogWDACAuditEvent( + string title, + string message, + string fqid); + /// /// True if the log provider needs to use logging variables. /// @@ -169,9 +200,7 @@ protected static void AppendException(StringBuilder sb, Exception except) { sb.AppendLine(StringUtil.Format(EtwLoggingStrings.ErrorRecordMessage, except.Message)); - IContainsErrorRecord ier = except as IContainsErrorRecord; - - if (ier != null) + if (except is IContainsErrorRecord ier) { ErrorRecord er = ier.ErrorRecord; @@ -370,6 +399,43 @@ internal override void LogSettingsEvent(LogContext logContext, string variableNa { } + /// + /// Provider interface function for logging provider health event. + /// + /// This the action performed in AmsiUtil class, like init, scan, etc. + /// The amsiContext handled - Session pair. + internal override void LogAmsiUtilStateEvent(string state, string context) + { + } + + /// + /// Provider interface function for logging WDAC query event. + /// + /// Name of the WDAC query. + /// Name of script file for policy query. Can be null value. + /// Query call succeed code. + /// Result code of WDAC query. + internal override void LogWDACQueryEvent( + string queryName, + string fileName, + int querySuccess, + int queryResult) + { + } + + /// + /// Provider interface function for logging WDAC audit event. + /// + /// Title of WDAC audit event. + /// WDAC audit event message. + /// FullyQualifiedId of WDAC audit event. + internal override void LogWDACAuditEvent( + string title, + string message, + string fqid) + { + } + #endregion } } diff --git a/src/System.Management.Automation/logging/MshLog.cs b/src/System.Management.Automation/logging/MshLog.cs index 02a8990bc69..aae65271892 100644 --- a/src/System.Management.Automation/logging/MshLog.cs +++ b/src/System.Management.Automation/logging/MshLog.cs @@ -60,13 +60,13 @@ internal static class MshLog /// The value of this dictionary is never empty. A value of type DummyProvider means /// no logging. /// - private static ConcurrentDictionary> s_logProviders = + private static readonly ConcurrentDictionary> s_logProviders = new ConcurrentDictionary>(); private const string _crimsonLogProviderAssemblyName = "MshCrimsonLog"; private const string _crimsonLogProviderTypeName = "System.Management.Automation.Logging.CrimsonLogProvider"; - private static Collection s_ignoredCommands = new Collection(); + private static readonly Collection s_ignoredCommands = new Collection(); /// /// Static constructor. @@ -134,11 +134,6 @@ private static Collection CreateLogProvider(string shellId) try { -#if !CORECLR // TODO:CORECLR EventLogLogProvider not handled yet - LogProvider eventLogLogProvider = new EventLogLogProvider(shellId); - providers.Add(eventLogLogProvider); -#endif - #if UNIX LogProvider sysLogProvider = new PSSysLogProvider(); providers.Add(sysLogProvider); @@ -216,9 +211,11 @@ internal static void LogEngineHealthEvent(ExecutionContext executionContext, } InvocationInfo invocationInfo = null; - IContainsErrorRecord icer = exception as IContainsErrorRecord; - if (icer != null && icer.ErrorRecord != null) + if (exception is IContainsErrorRecord icer && icer.ErrorRecord != null) + { invocationInfo = icer.ErrorRecord.InvocationInfo; + } + foreach (LogProvider provider in GetLogProvider(executionContext)) { if (NeedToLogEngineHealthEvent(provider, executionContext)) @@ -418,9 +415,11 @@ Severity severity } InvocationInfo invocationInfo = null; - IContainsErrorRecord icer = exception as IContainsErrorRecord; - if (icer != null && icer.ErrorRecord != null) + if (exception is IContainsErrorRecord icer && icer.ErrorRecord != null) + { invocationInfo = icer.ErrorRecord.InvocationInfo; + } + foreach (LogProvider provider in GetLogProvider(executionContext)) { if (NeedToLogCommandHealthEvent(provider, executionContext)) @@ -610,9 +609,11 @@ Severity severity } InvocationInfo invocationInfo = null; - IContainsErrorRecord icer = exception as IContainsErrorRecord; - if (icer != null && icer.ErrorRecord != null) + if (exception is IContainsErrorRecord icer && icer.ErrorRecord != null) + { invocationInfo = icer.ErrorRecord.InvocationInfo; + } + foreach (LogProvider provider in GetLogProvider(executionContext)) { if (NeedToLogProviderHealthEvent(provider, executionContext)) @@ -776,7 +777,7 @@ private static LogContext GetLogContext(ExecutionContext executionContext, Invoc logContext.HostId = (string)executionContext.EngineHostInterface.InstanceId.ToString(); } - logContext.HostApplication = string.Join(" ", Environment.GetCommandLineArgs()); + logContext.HostApplication = string.Join(' ', Environment.GetCommandLineArgs()); if (executionContext.CurrentRunspace != null) { @@ -809,9 +810,7 @@ private static LogContext GetLogContext(ExecutionContext executionContext, Invoc logContext.User = Logging.UnknownUserName; } - System.Management.Automation.Remoting.PSSenderInfo psSenderInfo = - executionContext.SessionState.PSVariable.GetValue("PSSenderInfo") as System.Management.Automation.Remoting.PSSenderInfo; - if (psSenderInfo != null) + if (executionContext.SessionState.PSVariable.GetValue("PSSenderInfo") is System.Management.Automation.Remoting.PSSenderInfo psSenderInfo) { logContext.ConnectedUser = psSenderInfo.UserInfo.Identity.Name; } diff --git a/src/System.Management.Automation/logging/eventlog/EventLogLogProvider.cs b/src/System.Management.Automation/logging/eventlog/EventLogLogProvider.cs deleted file mode 100644 index e6f301a04ac..00000000000 --- a/src/System.Management.Automation/logging/eventlog/EventLogLogProvider.cs +++ /dev/null @@ -1,684 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Globalization; -using System.Resources; -using System.Text; -using System.Threading; - -namespace System.Management.Automation -{ - /// - /// EventLogLogProvider is a class to implement Msh Provider interface using EventLog technology. - /// - /// EventLogLogProvider will be the provider to use if Monad is running in early windows releases - /// from 2000 to 2003. - /// - /// EventLogLogProvider will be packaged in the same dll as Msh Log Engine since EventLog should - /// always be available. - /// - internal class EventLogLogProvider : LogProvider - { - /// - /// Constructor. - /// - /// - internal EventLogLogProvider(string shellId) - { - string source = SetupEventSource(shellId); - - _eventLog = new EventLog(); - _eventLog.Source = source; - - _resourceManager = new ResourceManager("System.Management.Automation.resources.Logging", System.Reflection.Assembly.GetExecutingAssembly()); - } - - internal string SetupEventSource(string shellId) - { - string source; - - // In case shellId == null, use the "Default" source. - if (string.IsNullOrEmpty(shellId)) - { - source = "Default"; - } - else - { - int index = shellId.LastIndexOf('.'); - - if (index < 0) - source = shellId; - else - source = shellId.Substring(index + 1); - - // There may be a situation where ShellId ends with a '.'. - // In that case, use the default source. - if (string.IsNullOrEmpty(source)) - source = "Default"; - } - - if (EventLog.SourceExists(source)) - { - return source; - } - - string message = string.Format(Thread.CurrentThread.CurrentCulture, "Event source '{0}' is not registered", source); - throw new InvalidOperationException(message); - } - - /// - /// This represent a handle to EventLog. - /// - private EventLog _eventLog; - private ResourceManager _resourceManager; - - #region Log Provider Api - - private const int EngineHealthCategoryId = 1; - private const int CommandHealthCategoryId = 2; - private const int ProviderHealthCategoryId = 3; - private const int EngineLifecycleCategoryId = 4; - private const int CommandLifecycleCategoryId = 5; - private const int ProviderLifecycleCategoryId = 6; - private const int SettingsCategoryId = 7; - private const int PipelineExecutionDetailCategoryId = 8; - - /// - /// Log engine health event. - /// - /// - /// - /// - /// - internal override void LogEngineHealthEvent(LogContext logContext, int eventId, Exception exception, Dictionary additionalInfo) - { - Hashtable mapArgs = new Hashtable(); - - IContainsErrorRecord icer = exception as IContainsErrorRecord; - if (icer != null && icer.ErrorRecord != null) - { - mapArgs["ExceptionClass"] = exception.GetType().Name; - mapArgs["ErrorCategory"] = icer.ErrorRecord.CategoryInfo.Category; - mapArgs["ErrorId"] = icer.ErrorRecord.FullyQualifiedErrorId; - - if (icer.ErrorRecord.ErrorDetails != null) - { - mapArgs["ErrorMessage"] = icer.ErrorRecord.ErrorDetails.Message; - } - else - { - mapArgs["ErrorMessage"] = exception.Message; - } - } - else - { - mapArgs["ExceptionClass"] = exception.GetType().Name; - mapArgs["ErrorCategory"] = string.Empty; - mapArgs["ErrorId"] = string.Empty; - mapArgs["ErrorMessage"] = exception.Message; - } - - FillEventArgs(mapArgs, logContext); - - FillEventArgs(mapArgs, additionalInfo); - - EventInstance entry = new EventInstance(eventId, EngineHealthCategoryId); - - entry.EntryType = GetEventLogEntryType(logContext); - - string detail = GetEventDetail("EngineHealthContext", mapArgs); - - LogEvent(entry, mapArgs["ErrorMessage"], detail); - } - - private static EventLogEntryType GetEventLogEntryType(LogContext logContext) - { - switch (logContext.Severity) - { - case "Critical": - case "Error": - return EventLogEntryType.Error; - case "Warning": - return EventLogEntryType.Warning; - default: - return EventLogEntryType.Information; - } - } - - /// - /// Log engine lifecycle event. - /// - /// - /// - /// - internal override void LogEngineLifecycleEvent(LogContext logContext, EngineState newState, EngineState previousState) - { - int eventId = GetEngineLifecycleEventId(newState); - - if (eventId == _invalidEventId) - return; - - Hashtable mapArgs = new Hashtable(); - - mapArgs["NewEngineState"] = newState.ToString(); - mapArgs["PreviousEngineState"] = previousState.ToString(); - - FillEventArgs(mapArgs, logContext); - - EventInstance entry = new EventInstance(eventId, EngineLifecycleCategoryId); - - entry.EntryType = EventLogEntryType.Information; - - string detail = GetEventDetail("EngineLifecycleContext", mapArgs); - - LogEvent(entry, newState, previousState, detail); - } - - private const int _baseEngineLifecycleEventId = 400; - private const int _invalidEventId = -1; - - /// - /// Get engine lifecycle event id based on engine state. - /// - /// - /// - private static int GetEngineLifecycleEventId(EngineState engineState) - { - switch (engineState) - { - case EngineState.None: - return _invalidEventId; - case EngineState.Available: - return _baseEngineLifecycleEventId; - case EngineState.Degraded: - return _baseEngineLifecycleEventId + 1; - case EngineState.OutOfService: - return _baseEngineLifecycleEventId + 2; - case EngineState.Stopped: - return _baseEngineLifecycleEventId + 3; - } - - return _invalidEventId; - } - - private const int _commandHealthEventId = 200; - - /// - /// Provider interface function for logging command health event. - /// - /// - /// - internal override void LogCommandHealthEvent(LogContext logContext, Exception exception) - { - int eventId = _commandHealthEventId; - - Hashtable mapArgs = new Hashtable(); - - IContainsErrorRecord icer = exception as IContainsErrorRecord; - if (icer != null && icer.ErrorRecord != null) - { - mapArgs["ExceptionClass"] = exception.GetType().Name; - mapArgs["ErrorCategory"] = icer.ErrorRecord.CategoryInfo.Category; - mapArgs["ErrorId"] = icer.ErrorRecord.FullyQualifiedErrorId; - - if (icer.ErrorRecord.ErrorDetails != null) - { - mapArgs["ErrorMessage"] = icer.ErrorRecord.ErrorDetails.Message; - } - else - { - mapArgs["ErrorMessage"] = exception.Message; - } - } - else - { - mapArgs["ExceptionClass"] = exception.GetType().Name; - mapArgs["ErrorCategory"] = string.Empty; - mapArgs["ErrorId"] = string.Empty; - mapArgs["ErrorMessage"] = exception.Message; - } - - FillEventArgs(mapArgs, logContext); - - EventInstance entry = new EventInstance(eventId, CommandHealthCategoryId); - - entry.EntryType = GetEventLogEntryType(logContext); - - string detail = GetEventDetail("CommandHealthContext", mapArgs); - - LogEvent(entry, mapArgs["ErrorMessage"], detail); - } - - /// - /// Log command life cycle event. - /// - /// - /// - internal override void LogCommandLifecycleEvent(Func getLogContext, CommandState newState) - { - LogContext logContext = getLogContext(); - - int eventId = GetCommandLifecycleEventId(newState); - - if (eventId == _invalidEventId) - return; - - Hashtable mapArgs = new Hashtable(); - - mapArgs["NewCommandState"] = newState.ToString(); - - FillEventArgs(mapArgs, logContext); - - EventInstance entry = new EventInstance(eventId, CommandLifecycleCategoryId); - - entry.EntryType = EventLogEntryType.Information; - - string detail = GetEventDetail("CommandLifecycleContext", mapArgs); - - LogEvent(entry, logContext.CommandName, newState, detail); - } - - private const int _baseCommandLifecycleEventId = 500; - - /// - /// Get command lifecycle event id based on command state. - /// - /// - /// - private static int GetCommandLifecycleEventId(CommandState commandState) - { - switch (commandState) - { - case CommandState.Started: - return _baseCommandLifecycleEventId; - case CommandState.Stopped: - return _baseCommandLifecycleEventId + 1; - case CommandState.Terminated: - return _baseCommandLifecycleEventId + 2; - } - - return _invalidEventId; - } - - private const int _pipelineExecutionDetailEventId = 800; - - /// - /// Log pipeline execution detail event. - /// - /// This may end of logging more than one event if the detail string is too long to be fit in 64K. - /// - /// - /// - internal override void LogPipelineExecutionDetailEvent(LogContext logContext, List pipelineExecutionDetail) - { - List details = GroupMessages(pipelineExecutionDetail); - - for (int i = 0; i < details.Count; i++) - { - LogPipelineExecutionDetailEvent(logContext, details[i], i + 1, details.Count); - } - } - - private const int MaxLength = 16000; - - private List GroupMessages(List messages) - { - List result = new List(); - - if (messages == null || messages.Count == 0) - return result; - - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < messages.Count; i++) - { - if (sb.Length + messages[i].Length < MaxLength) - { - sb.AppendLine(messages[i]); - continue; - } - - result.Add(sb.ToString()); - sb = new StringBuilder(); - sb.AppendLine(messages[i]); - } - - result.Add(sb.ToString()); - - return result; - } - - /// - /// Log one pipeline execution detail event. Detail message is already chopped up so that it will - /// fit in 64K. - /// - /// - /// - /// - /// - private void LogPipelineExecutionDetailEvent(LogContext logContext, string pipelineExecutionDetail, int detailSequence, int detailTotal) - { - int eventId = _pipelineExecutionDetailEventId; - - Hashtable mapArgs = new Hashtable(); - - mapArgs["PipelineExecutionDetail"] = pipelineExecutionDetail; - mapArgs["DetailSequence"] = detailSequence; - mapArgs["DetailTotal"] = detailTotal; - - FillEventArgs(mapArgs, logContext); - - EventInstance entry = new EventInstance(eventId, PipelineExecutionDetailCategoryId); - - entry.EntryType = EventLogEntryType.Information; - - string pipelineInfo = GetEventDetail("PipelineExecutionDetailContext", mapArgs); - - LogEvent(entry, logContext.CommandLine, pipelineInfo, pipelineExecutionDetail); - } - - private const int _providerHealthEventId = 300; - /// - /// Provider interface function for logging provider health event. - /// - /// - /// - /// - internal override void LogProviderHealthEvent(LogContext logContext, string providerName, Exception exception) - { - int eventId = _providerHealthEventId; - - Hashtable mapArgs = new Hashtable(); - - mapArgs["ProviderName"] = providerName; - - IContainsErrorRecord icer = exception as IContainsErrorRecord; - if (icer != null && icer.ErrorRecord != null) - { - mapArgs["ExceptionClass"] = exception.GetType().Name; - mapArgs["ErrorCategory"] = icer.ErrorRecord.CategoryInfo.Category; - mapArgs["ErrorId"] = icer.ErrorRecord.FullyQualifiedErrorId; - - if (icer.ErrorRecord.ErrorDetails != null - && !string.IsNullOrEmpty(icer.ErrorRecord.ErrorDetails.Message)) - { - mapArgs["ErrorMessage"] = icer.ErrorRecord.ErrorDetails.Message; - } - else - { - mapArgs["ErrorMessage"] = exception.Message; - } - } - else - { - mapArgs["ExceptionClass"] = exception.GetType().Name; - mapArgs["ErrorCategory"] = string.Empty; - mapArgs["ErrorId"] = string.Empty; - mapArgs["ErrorMessage"] = exception.Message; - } - - FillEventArgs(mapArgs, logContext); - - EventInstance entry = new EventInstance(eventId, ProviderHealthCategoryId); - - entry.EntryType = GetEventLogEntryType(logContext); - - string detail = GetEventDetail("ProviderHealthContext", mapArgs); - - LogEvent(entry, mapArgs["ErrorMessage"], detail); - } - - /// - /// Log provider lifecycle event. - /// - /// - /// - /// - internal override void LogProviderLifecycleEvent(LogContext logContext, string providerName, ProviderState newState) - { - int eventId = GetProviderLifecycleEventId(newState); - - if (eventId == _invalidEventId) - return; - - Hashtable mapArgs = new Hashtable(); - - mapArgs["ProviderName"] = providerName; - mapArgs["NewProviderState"] = newState.ToString(); - - FillEventArgs(mapArgs, logContext); - - EventInstance entry = new EventInstance(eventId, ProviderLifecycleCategoryId); - - entry.EntryType = EventLogEntryType.Information; - - string detail = GetEventDetail("ProviderLifecycleContext", mapArgs); - - LogEvent(entry, providerName, newState, detail); - } - - private const int _baseProviderLifecycleEventId = 600; - - /// - /// Get provider lifecycle event id based on provider state. - /// - /// - /// - private static int GetProviderLifecycleEventId(ProviderState providerState) - { - switch (providerState) - { - case ProviderState.Started: - return _baseProviderLifecycleEventId; - case ProviderState.Stopped: - return _baseProviderLifecycleEventId + 1; - } - - return _invalidEventId; - } - - private const int _settingsEventId = 700; - - /// - /// Log settings event. - /// - /// - /// - /// - /// - internal override void LogSettingsEvent(LogContext logContext, string variableName, string value, string previousValue) - { - int eventId = _settingsEventId; - - Hashtable mapArgs = new Hashtable(); - - mapArgs["VariableName"] = variableName; - mapArgs["NewValue"] = value; - mapArgs["PreviousValue"] = previousValue; - - FillEventArgs(mapArgs, logContext); - - EventInstance entry = new EventInstance(eventId, SettingsCategoryId); - - entry.EntryType = EventLogEntryType.Information; - - string detail = GetEventDetail("SettingsContext", mapArgs); - - LogEvent(entry, variableName, value, previousValue, detail); - } - - #endregion Log Provider Api - - #region EventLog helper functions - - /// - /// This is the helper function for logging an event with localizable message - /// to event log. It will trace all exception thrown by eventlog. - /// - /// - /// - private void LogEvent(EventInstance entry, params object[] args) - { - try - { - _eventLog.WriteEvent(entry, args); - } - catch (ArgumentException) - { - return; - } - catch (InvalidOperationException) - { - return; - } - catch (Win32Exception) - { - return; - } - } - - #endregion - - #region Event Arguments - - /// - /// Fill event arguments with logContext info. - /// - /// In EventLog Api, arguments are passed in as an array of objects. - /// - /// An ArrayList to contain the event arguments. - /// The log context containing the info to fill in. - private static void FillEventArgs(Hashtable mapArgs, LogContext logContext) - { - mapArgs["Severity"] = logContext.Severity; - mapArgs["SequenceNumber"] = logContext.SequenceNumber; - mapArgs["HostName"] = logContext.HostName; - mapArgs["HostVersion"] = logContext.HostVersion; - mapArgs["HostId"] = logContext.HostId; - mapArgs["HostApplication"] = logContext.HostApplication; - mapArgs["EngineVersion"] = logContext.EngineVersion; - mapArgs["RunspaceId"] = logContext.RunspaceId; - mapArgs["PipelineId"] = logContext.PipelineId; - mapArgs["CommandName"] = logContext.CommandName; - mapArgs["CommandType"] = logContext.CommandType; - mapArgs["ScriptName"] = logContext.ScriptName; - mapArgs["CommandPath"] = logContext.CommandPath; - mapArgs["CommandLine"] = logContext.CommandLine; - mapArgs["User"] = logContext.User; - mapArgs["Time"] = logContext.Time; - } - - /// - /// Fill event arguments with additionalInfo stored in a string dictionary. - /// - /// An arraylist to contain the event arguments. - /// A string dictionary to fill in. - private static void FillEventArgs(Hashtable mapArgs, Dictionary additionalInfo) - { - if (additionalInfo == null) - { - for (int i = 0; i < 3; i++) - { - string id = ((int)(i + 1)).ToString("d1", CultureInfo.CurrentCulture); - - mapArgs["AdditionalInfo_Name" + id] = string.Empty; - mapArgs["AdditionalInfo_Value" + id] = string.Empty; - } - - return; - } - - string[] keys = new string[additionalInfo.Count]; - string[] values = new string[additionalInfo.Count]; - - additionalInfo.Keys.CopyTo(keys, 0); - additionalInfo.Values.CopyTo(values, 0); - for (int i = 0; i < 3; i++) - { - string id = ((int)(i + 1)).ToString("d1", CultureInfo.CurrentCulture); - - if (i < keys.Length) - { - mapArgs["AdditionalInfo_Name" + id] = keys[i]; - mapArgs["AdditionalInfo_Value" + id] = values[i]; - } - else - { - mapArgs["AdditionalInfo_Name" + id] = string.Empty; - mapArgs["AdditionalInfo_Value" + id] = string.Empty; - } - } - - return; - } - - #endregion Event Arguments - - #region Event Message - - private string GetEventDetail(string contextId, Hashtable mapArgs) - { - return GetMessage(contextId, mapArgs); - } - - private string GetMessage(string messageId, Hashtable mapArgs) - { - if (_resourceManager == null) - return string.Empty; - - string messageTemplate = _resourceManager.GetString(messageId); - - if (string.IsNullOrEmpty(messageTemplate)) - return string.Empty; - - return FillMessageTemplate(messageTemplate, mapArgs); - } - - private static string FillMessageTemplate(string messageTemplate, Hashtable mapArgs) - { - StringBuilder message = new StringBuilder(); - - int cursor = 0; - - while (true) - { - int startIndex = messageTemplate.IndexOf('[', cursor); - - if (startIndex < 0) - { - message.Append(messageTemplate.Substring(cursor)); - return message.ToString(); - } - - int endIndex = messageTemplate.IndexOf(']', startIndex + 1); - - if (endIndex < 0) - { - message.Append(messageTemplate.Substring(cursor)); - return message.ToString(); - } - - message.Append(messageTemplate.Substring(cursor, startIndex - cursor)); - cursor = startIndex; - - string placeHolder = messageTemplate.Substring(startIndex + 1, endIndex - startIndex - 1); - - if (mapArgs.Contains(placeHolder)) - { - message.Append(mapArgs[placeHolder]); - cursor = endIndex + 1; - } - else - { - message.Append("["); - cursor++; - } - } - } - - #endregion Event Message - } -} diff --git a/src/System.Management.Automation/namespaces/AliasProvider.cs b/src/System.Management.Automation/namespaces/AliasProvider.cs index 8782a0d9bb5..c51d8e35128 100644 --- a/src/System.Management.Automation/namespaces/AliasProvider.cs +++ b/src/System.Management.Automation/namespaces/AliasProvider.cs @@ -194,11 +194,7 @@ internal override void SetSessionStateItem(string name, object value, bool write if (dynamicParametersSpecified) { item = (AliasInfo)GetSessionStateItem(name); - - if (item != null) - { - item.SetOptions(dynamicParameters.Options, Force); - } + item?.SetOptions(dynamicParameters.Options, Force); } else { diff --git a/src/System.Management.Automation/namespaces/CoreCommandContext.cs b/src/System.Management.Automation/namespaces/CoreCommandContext.cs index 5446b6b71cb..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. /// - [Dbg.TraceSourceAttribute( + [Dbg.TraceSource( "CmdletProviderContext", "The context under which a core command is being run.")] private static readonly Dbg.PSTraceSource s_tracer = @@ -390,13 +390,8 @@ private void CopyFilters(CmdletProviderContext context) Filter = context.Filter; } - internal void RemoveStopReferral() - { - if (_copiedContext != null) - { - _copiedContext.StopReferrals.Remove(this); - } - } + internal void RemoveStopReferral() => _copiedContext?.StopReferrals.Remove(this); + #endregion Internal properties #region Public properties @@ -556,7 +551,7 @@ internal SwitchParameter Force /// /// Name of the target resource being acted upon /// - /// true iff the action should be performed + /// true if-and-only-if the action should be performed /// /// The ActionPreference.Stop or ActionPreference.Inquire policy /// triggered a terminating error. The pipeline failure will be @@ -582,7 +577,7 @@ internal bool ShouldProcess( /// Name of the target resource being acted upon /// /// What action was being performed. - /// true iff the action should be performed + /// true if-and-only-if the action should be performed /// /// The ActionPreference.Stop or ActionPreference.Inquire policy /// triggered a terminating error. The pipeline failure will be @@ -621,7 +616,7 @@ internal bool ShouldProcess( /// if the user is prompted whether or not to perform the action. /// It may be displayed by some hosts, but not all. /// - /// true iff the action should be performed + /// true if-and-only-if the action should be performed /// /// The ActionPreference.Stop or ActionPreference.Inquire policy /// triggered a terminating error. The pipeline failure will be @@ -670,7 +665,7 @@ internal bool ShouldProcess( /// /// are returned. /// - /// true iff the action should be performed + /// true if-and-only-if the action should be performed /// /// The ActionPreference.Stop or ActionPreference.Inquire policy /// triggered a terminating error. The pipeline failure will be @@ -774,13 +769,7 @@ internal bool ShouldContinue( /// /// The string that needs to be written. /// - internal void WriteVerbose(string text) - { - if (_command != null) - { - _command.WriteVerbose(text); - } - } + internal void WriteVerbose(string text) => _command?.WriteVerbose(text); /// /// Writes the object to the Warning pipe. @@ -788,21 +777,9 @@ internal void WriteVerbose(string text) /// /// The string that needs to be written. /// - internal void WriteWarning(string text) - { - if (_command != null) - { - _command.WriteWarning(text); - } - } + internal void WriteWarning(string text) => _command?.WriteWarning(text); - internal void WriteProgress(ProgressRecord record) - { - if (_command != null) - { - _command.WriteProgress(record); - } - } + internal void WriteProgress(ProgressRecord record) => _command?.WriteProgress(record); /// /// Writes a debug string. @@ -810,29 +787,11 @@ internal void WriteProgress(ProgressRecord record) /// /// The String that needs to be written. /// - internal void WriteDebug(string text) - { - if (_command != null) - { - _command.WriteDebug(text); - } - } + internal void WriteDebug(string text) => _command?.WriteDebug(text); - internal void WriteInformation(InformationRecord record) - { - if (_command != null) - { - _command.WriteInformation(record); - } - } + internal void WriteInformation(InformationRecord record) => _command?.WriteInformation(record); - internal void WriteInformation(object messageData, string[] tags) - { - if (_command != null) - { - _command.WriteInformation(messageData, tags); - } - } + internal void WriteInformation(object messageData, string[] tags) => _command?.WriteInformation(messageData, tags); #endregion User feedback mechanisms @@ -1154,14 +1113,10 @@ internal void StopProcessing() { Stopping = true; - if (_providerInstance != null) - { - // We don't need to catch any of the exceptions here because - // we are terminating the pipeline and any exception will - // be caught by the engine. - - _providerInstance.StopProcessing(); - } + // We don't need to catch any of the exceptions here because + // we are terminating the pipeline and any exception will + // be caught by the engine. + _providerInstance?.StopProcessing(); // Call the stop referrals if any diff --git a/src/System.Management.Automation/namespaces/DriveProviderBase.cs b/src/System.Management.Automation/namespaces/DriveProviderBase.cs index 91b47619be7..2fc44e50bdc 100644 --- a/src/System.Management.Automation/namespaces/DriveProviderBase.cs +++ b/src/System.Management.Automation/namespaces/DriveProviderBase.cs @@ -9,7 +9,7 @@ namespace System.Management.Automation.Provider #region DriveCmdletProvider /// - /// The base class for Cmdlet providers that can be exposed through MSH drives. + /// The base class for Cmdlet providers that can be exposed through PSDrives. /// /// /// Although it is possible to derive from this base class to implement a Cmdlet Provider, in most diff --git a/src/System.Management.Automation/namespaces/FileSystemContentStream.cs b/src/System.Management.Automation/namespaces/FileSystemContentStream.cs index f5d299ecfd6..d95b31ed0fd 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. /// - [Dbg.TraceSourceAttribute( + [Dbg.TraceSource( "FileSystemContentStream", "The provider content reader and writer for the file system")] private static readonly Dbg.PSTraceSource s_tracer = @@ -413,7 +413,7 @@ public IList Read(long readCount) (e is UnauthorizedAccessException) || (e is ArgumentNullException)) { - // Exception contains specific message about the error occured and so no need for errordetails. + // Exception contains specific message about the error occurred and so no need for errordetails. _provider.WriteError(new ErrorRecord(e, "GetContentReaderIOError", ErrorCategory.ReadError, _path)); return null; } @@ -567,7 +567,7 @@ internal void SeekItemsBackward(int backCount) (e is UnauthorizedAccessException) || (e is ArgumentNullException)) { - // Exception contains specific message about the error occured and so no need for errordetails. + // Exception contains specific message about the error occurred and so no need for errordetails. _provider.WriteError(new ErrorRecord(e, "GetContentReaderIOError", ErrorCategory.ReadError, _path)); } else @@ -709,7 +709,7 @@ private bool ReadDelimited(bool waitChanges, List blocks, bool readBackw // We've reached the end of file or end of line. if (_currentLineContent.Length > 0) { - // Add the block read to the ouptut array list, trimming a trailing delimiter, if present. + // Add the block read to the output array list, trimming a trailing delimiter, if present. // Note: If -Tail was specified, we get here in the course of 2 distinct passes: // - Once while reading backward simply to determine the appropriate *start position* for later forward reading, ignoring the content of the blocks read (in reverse). // - Then again during forward reading, for regular output processing; it is only then that trimming the delimiter is necessary. @@ -794,7 +794,7 @@ private bool ReadByteEncoded(bool waitChanges, List blocks, bool readBac // the changes if (waitChanges) { - WaitForChanges(_path, _mode, _access, _share, ClrFacade.GetDefaultEncoding()); + WaitForChanges(_path, _mode, _access, _share, Encoding.Default); byteRead = _stream.ReadByte(); } } @@ -987,9 +987,8 @@ private void WaitForChanges(string filePath, FileMode fileMode, FileAccess fileA // Seek to the place we last left off. _stream.Seek(_fileOffset, SeekOrigin.Begin); - if (_reader != null) { _reader.DiscardBufferedData(); } - - if (_backReader != null) { _backReader.DiscardBufferedData(); } + _reader?.DiscardBufferedData(); + _backReader?.DiscardBufferedData(); } /// @@ -1003,15 +1002,13 @@ private void WaitForChanges(string filePath, FileMode fileMode, FileAccess fileA /// public void Seek(long offset, SeekOrigin origin) { - if (_writer != null) { _writer.Flush(); } + _writer?.Flush(); _stream.Seek(offset, origin); - if (_writer != null) { _writer.Flush(); } - - if (_reader != null) { _reader.DiscardBufferedData(); } - - if (_backReader != null) { _backReader.DiscardBufferedData(); } + _writer?.Flush(); + _reader?.DiscardBufferedData(); + _backReader?.DiscardBufferedData(); } /// @@ -1135,14 +1132,10 @@ internal void Dispose(bool isDisposing) { if (isDisposing) { - if (_stream != null) - _stream.Dispose(); - if (_reader != null) - _reader.Dispose(); - if (_backReader != null) - _backReader.Dispose(); - if (_writer != null) - _writer.Dispose(); + _stream?.Dispose(); + _reader?.Dispose(); + _backReader?.Dispose(); + _writer?.Dispose(); } } } @@ -1180,39 +1173,9 @@ internal FileStreamBackReader(FileStream fileStream, Encoding encoding) private int _byteCount = 0; private int _charCount = 0; private long _currentPosition = 0; - private bool? _singleByteCharSet = null; - private const byte BothTopBitsSet = 0xC0; private const byte TopBitUnset = 0x80; - /// - /// If the given encoding is OEM or Default, check to see if the code page - /// is SBCS(single byte character set). - /// - /// - private bool IsSingleByteCharacterSet() - { - if (_singleByteCharSet != null) - return (bool)_singleByteCharSet; - - // Porting note: only UTF-8 is supported on Linux, which is not an SBCS - if ((_currentEncoding.Equals(_oemEncoding) || - _currentEncoding.Equals(_defaultAnsiEncoding)) - && Platform.IsWindows) - { - NativeMethods.CPINFO cpInfo; - if (NativeMethods.GetCPInfo((uint)_currentEncoding.CodePage, out cpInfo) && - cpInfo.MaxCharSize == 1) - { - _singleByteCharSet = true; - return true; - } - } - - _singleByteCharSet = false; - return false; - } - /// /// We don't support this method because it is not used by the ReadBackward method in FileStreamContentReaderWriter. /// @@ -1481,8 +1444,7 @@ private int RefillByteBuff() } else if (_currentEncoding is UnicodeEncoding || _currentEncoding is UTF32Encoding || - _currentEncoding is ASCIIEncoding || - IsSingleByteCharacterSet()) + _currentEncoding.IsSingleByte) { // Unicode -- two bytes per character // UTF-32 -- four bytes per character @@ -1532,16 +1494,6 @@ internal struct CPINFO [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_LEADBYTES)] public byte[] LeadBytes; } - - /// - /// Get information on a named code page. - /// - /// - /// - /// - [DllImport(PinvokeDllNames.GetCPInfoDllName, CharSet = CharSet.Unicode, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool GetCPInfo(uint codePage, out CPINFO lpCpInfo); } } diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index 9499bf95289..3d13ac05d4d 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; @@ -18,6 +19,7 @@ using System.Security; using System.Security.AccessControl; using System.Text; +using System.Threading.Tasks; using System.Xml; using System.Xml.XPath; @@ -37,13 +39,14 @@ namespace Microsoft.PowerShell.Commands [OutputType(typeof(FileSecurity), ProviderCmdlet = ProviderCmdlet.SetAcl)] [OutputType(typeof(string), typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.ResolvePath)] [OutputType(typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.PushLocation)] + [OutputType(typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.PopLocation)] [OutputType(typeof(byte), typeof(string), ProviderCmdlet = ProviderCmdlet.GetContent)] [OutputType(typeof(FileInfo), ProviderCmdlet = ProviderCmdlet.GetItem)] [OutputType(typeof(FileInfo), typeof(DirectoryInfo), ProviderCmdlet = ProviderCmdlet.GetChildItem)] [OutputType(typeof(FileSecurity), typeof(DirectorySecurity), ProviderCmdlet = ProviderCmdlet.GetAcl)] [OutputType(typeof(bool), typeof(string), typeof(FileInfo), typeof(DirectoryInfo), ProviderCmdlet = ProviderCmdlet.GetItem)] [OutputType(typeof(bool), typeof(string), typeof(DateTime), typeof(System.IO.FileInfo), typeof(System.IO.DirectoryInfo), ProviderCmdlet = ProviderCmdlet.GetItemProperty)] - [OutputType(typeof(string), typeof(System.IO.FileInfo), ProviderCmdlet = ProviderCmdlet.NewItem)] + [OutputType(typeof(string), typeof(System.IO.FileInfo), typeof(DirectoryInfo), ProviderCmdlet = ProviderCmdlet.NewItem)] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "This coupling is required")] public sealed partial class FileSystemProvider : NavigationCmdletProvider, IContentCmdletProvider, @@ -51,21 +54,15 @@ public sealed partial class FileSystemProvider : NavigationCmdletProvider, ISecurityDescriptorCmdletProvider, ICmdletProviderSupportsHelp { -#if UNIX - // This is the errno returned by the rename() syscall - // when an item is attempted to be renamed across filesystem mount boundaries. - private const int MOVE_FAILED_ERROR = 18; -#else - // 0x80070005 ACCESS_DENIED is returned when trying to move files across volumes like DFS - private const int MOVE_FAILED_ERROR = -2147024891; -#endif - // 4MB gives the best results without spiking the resources on the remote connection for file transfers between pssessions. // NOTE: The script used to copy file data from session (PSCopyFromSessionHelper) has a // maximum fragment size value for security. If FILETRANSFERSIZE changes make sure the - // copy script will accomodate the new value. + // copy script will accommodate the new value. private const int FILETRANSFERSIZE = 4 * 1024 * 1024; + private const int COPY_FILE_ACTIVITY_ID = 0; + private const int REMOVE_FILE_ACTIVITY_ID = 0; + // The name of the key in an exception's Data dictionary when attempting // to copy an item onto itself. private const string SelfCopyDataKey = "SelfCopy"; @@ -74,7 +71,7 @@ public sealed partial class FileSystemProvider : NavigationCmdletProvider, /// An instance of the PSTraceSource class used for trace output /// using "FileSystemProvider" as the category. /// - [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"); @@ -109,7 +106,7 @@ public FileSystemProvider() /// /// The path with all / normalized to \ /// - private static string NormalizePath(string path) + internal static string NormalizePath(string path) { return GetCorrectCasedPath(path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator)); } @@ -140,20 +137,21 @@ private static string GetCorrectCasedPath(string path) itemsToSkip = 4; } - foreach (string item in path.Split(StringLiterals.DefaultPathSeparator)) + var items = path.Split(StringLiterals.DefaultPathSeparator); + for (int i = 0; i < items.Length; i++) { if (itemsToSkip-- > 0) { // This handles the UNC server and share and 8.3 short path syntax - exactPath += item + StringLiterals.DefaultPathSeparator; + exactPath += items[i] + StringLiterals.DefaultPathSeparator; continue; } else if (string.IsNullOrEmpty(exactPath)) { // This handles the drive letter or / root path start - exactPath = item + StringLiterals.DefaultPathSeparator; + exactPath = items[i] + StringLiterals.DefaultPathSeparator; } - else if (string.IsNullOrEmpty(item)) + else if (string.IsNullOrEmpty(items[i]) && i == items.Length - 1) { // This handles the trailing slash case if (!exactPath.EndsWith(StringLiterals.DefaultPathSeparator)) @@ -163,17 +161,17 @@ private static string GetCorrectCasedPath(string path) break; } - else if (item.Contains('~')) + else if (items[i].Contains('~')) { // This handles short path names - exactPath += StringLiterals.DefaultPathSeparator + item; + exactPath += StringLiterals.DefaultPathSeparator + items[i]; } else { // Use GetFileSystemEntries to get the correct casing of this element try { - var entries = Directory.GetFileSystemEntries(exactPath, item); + var entries = Directory.GetFileSystemEntries(exactPath, items[i]); if (entries.Length > 0) { exactPath = entries[0]; @@ -478,13 +476,12 @@ protected override ProviderInfo Start(ProviderInfo providerInfo) #if !UNIX // The placeholder mode management APIs Rtl(Set|Query)(Process|Thread)PlaceholderCompatibilityMode // are only supported starting with Windows 10 version 1803 (build 17134) - Version minBuildForPlaceHolderAPIs = new Version(10, 0, 17134, 0); - if (Environment.OSVersion.Version >= minBuildForPlaceHolderAPIs) + if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134, 0)) { // let's be safe, don't change the PlaceHolderCompatibilityMode if the current one is not what we expect - if (NativeMethods.PHCM_DISGUISE_PLACEHOLDER == NativeMethods.RtlQueryProcessPlaceholderCompatibilityMode()) + if (Interop.Windows.RtlQueryProcessPlaceholderCompatibilityMode() == Interop.Windows.PHCM_DISGUISE_PLACEHOLDER) { - NativeMethods.RtlSetProcessPlaceholderCompatibilityMode(NativeMethods.PHCM_EXPOSE_PLACEHOLDERS); + Interop.Windows.RtlSetProcessPlaceholderCompatibilityMode(Interop.Windows.PHCM_EXPOSE_PLACEHOLDERS); } } #endif @@ -536,7 +533,7 @@ protected override PSDriveInfo NewDrive(PSDriveInfo drive) { // MapNetworkDrive facilitates to map the newly // created PS Drive to a network share. - this.MapNetworkDrive(drive); + MapNetworkDrive(drive); } // The drive is valid if the item exists or the @@ -595,35 +592,18 @@ protected override PSDriveInfo NewDrive(PSDriveInfo drive) /// MapNetworkDrive facilitates to map the newly created PS Drive to a network share. /// /// The PSDrive info that would be used to create a new PS drive. + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Can be static on Unix but not on Windows.")] + private void MapNetworkDrive(PSDriveInfo drive) { +#if UNIX + throw new PlatformNotSupportedException(); +#else // Porting note: mapped network drives are only supported on Windows - if (Platform.IsWindows) - { - WinMapNetworkDrive(drive); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - private static bool _WNetApiAvailable = true; - - private void WinMapNetworkDrive(PSDriveInfo drive) - { if (drive != null && !string.IsNullOrEmpty(drive.Root)) { - const int CONNECT_UPDATE_PROFILE = 0x00000001; - const int CONNECT_NOPERSIST = 0x00000000; - const int RESOURCE_GLOBALNET = 0x00000002; - const int RESOURCETYPE_ANY = 0x00000000; - const int RESOURCEDISPLAYTYPE_GENERIC = 0x00000000; - const int RESOURCEUSAGE_CONNECTABLE = 0x00000001; - const int ERROR_NO_NETWORK = 1222; - // By default the connection is not persisted. - int CONNECT_TYPE = CONNECT_NOPERSIST; + int connectType = Interop.Windows.CONNECT_NOPERSIST; string driveName = null; byte[] passwd = null; @@ -633,13 +613,12 @@ private void WinMapNetworkDrive(PSDriveInfo drive) { if (IsSupportedDriveForPersistence(drive)) { - CONNECT_TYPE = CONNECT_UPDATE_PROFILE; + connectType = Interop.Windows.CONNECT_UPDATE_PROFILE; driveName = drive.Name + ":"; drive.DisplayRoot = drive.Root; } else { - // error. ErrorRecord er = new ErrorRecord(new InvalidOperationException(FileSystemProviderStrings.InvalidDriveName), "DriveNameNotSupportedForPersistence", ErrorCategory.InvalidOperation, drive); ThrowTerminatingError(er); } @@ -655,37 +634,15 @@ private void WinMapNetworkDrive(PSDriveInfo drive) try { - NetResource resource = new NetResource(); - resource.Comment = null; - resource.DisplayType = RESOURCEDISPLAYTYPE_GENERIC; - resource.LocalName = driveName; - resource.Provider = null; - resource.RemoteName = drive.Root; - resource.Scope = RESOURCE_GLOBALNET; - resource.Type = RESOURCETYPE_ANY; - resource.Usage = RESOURCEUSAGE_CONNECTABLE; - - int code = ERROR_NO_NETWORK; - - if (_WNetApiAvailable) - { - try - { - code = NativeMethods.WNetAddConnection2(ref resource, passwd, userName, CONNECT_TYPE); - } - catch (System.DllNotFoundException) - { - _WNetApiAvailable = false; - } - } + int errorCode = Interop.Windows.WNetAddConnection2(driveName, drive.Root, passwd, userName, connectType); - if (code != 0) + if (errorCode != Interop.Windows.ERROR_SUCCESS) { - ErrorRecord er = new ErrorRecord(new System.ComponentModel.Win32Exception(code), "CouldNotMapNetworkDrive", ErrorCategory.InvalidOperation, drive); + ErrorRecord er = new ErrorRecord(new System.ComponentModel.Win32Exception(errorCode), "CouldNotMapNetworkDrive", ErrorCategory.InvalidOperation, drive); ThrowTerminatingError(er); } - if (CONNECT_TYPE == CONNECT_UPDATE_PROFILE) + if (connectType == Interop.Windows.CONNECT_UPDATE_PROFILE) { // Update the current PSDrive to be a persisted drive. drive.IsNetworkDrive = true; @@ -700,10 +657,11 @@ private void WinMapNetworkDrive(PSDriveInfo drive) // Clear the password in the memory. if (passwd != null) { - Array.Clear(passwd, 0, passwd.Length - 1); + Array.Clear(passwd); } } } +#endif } /// @@ -733,23 +691,14 @@ protected override PSDriveInfo RemoveDrive(PSDriveInfo drive) #if UNIX return drive; #else - return WinRemoveDrive(drive); -#endif - } - - private PSDriveInfo WinRemoveDrive(PSDriveInfo drive) - { if (IsNetworkMappedDrive(drive)) { - const int CONNECT_UPDATE_PROFILE = 0x00000001; - const int ERROR_NO_NETWORK = 1222; - - int flags = 0; + int flags = Interop.Windows.CONNECT_NOPERSIST; string driveName; if (drive.IsNetworkDrive) { // Here we are removing only persisted network drives. - flags = CONNECT_UPDATE_PROFILE; + flags = Interop.Windows.CONNECT_UPDATE_PROFILE; driveName = drive.Name + ":"; } else @@ -760,28 +709,17 @@ private PSDriveInfo WinRemoveDrive(PSDriveInfo drive) } // You need to actually remove the drive. - int code = ERROR_NO_NETWORK; - - if (_WNetApiAvailable) - { - try - { - code = NativeMethods.WNetCancelConnection2(driveName, flags, true); - } - catch (System.DllNotFoundException) - { - _WNetApiAvailable = false; - } - } + int errorCode = Interop.Windows.WNetCancelConnection2(driveName, flags, force: true); - if (code != 0) + if (errorCode != Interop.Windows.ERROR_SUCCESS) { - ErrorRecord er = new ErrorRecord(new System.ComponentModel.Win32Exception(code), "CouldRemoveNetworkDrive", ErrorCategory.InvalidOperation, drive); + ErrorRecord er = new ErrorRecord(new System.ComponentModel.Win32Exception(errorCode), "CouldRemoveNetworkDrive", ErrorCategory.InvalidOperation, drive); ThrowTerminatingError(er); } } return drive; +#endif } /// @@ -819,57 +757,19 @@ internal static string GetUNCForNetworkDrive(string driveName) #if UNIX return driveName; #else - return WinGetUNCForNetworkDrive(driveName); -#endif - } - - private static string WinGetUNCForNetworkDrive(string driveName) - { - const int ERROR_NO_NETWORK = 1222; string uncPath = null; if (!string.IsNullOrEmpty(driveName) && driveName.Length == 1) { - // By default buffer size is set to 300 which would generally be sufficient in most of the cases. - int bufferSize = 300; -#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 + int errorCode = Interop.Windows.GetUNCForNetworkDrive(driveName[0], out uncPath); - StringBuilder uncBuffer = new StringBuilder(bufferSize); - driveName += ':'; - - // Call the windows API - int errorCode = ERROR_NO_NETWORK; - - try - { - errorCode = NativeMethods.WNetGetConnection(driveName, uncBuffer, ref bufferSize); - } - catch (System.DllNotFoundException) - { - return null; - } - - // error code 234 is returned whenever the required buffer size is greater - // than the specified buffer size. - if (errorCode == 234) - { - uncBuffer = new StringBuilder(bufferSize); - errorCode = NativeMethods.WNetGetConnection(driveName, uncBuffer, ref bufferSize); - } - - if (errorCode != 0) + if (errorCode != Interop.Windows.ERROR_SUCCESS) { throw new System.ComponentModel.Win32Exception(errorCode); } - - uncPath = uncBuffer.ToString(); } return uncPath; +#endif } /// @@ -887,9 +787,9 @@ internal static string GetSubstitutedPathForNetworkDosDevice(string driveName) { #if UNIX throw new PlatformNotSupportedException(); + } #else return WinGetSubstitutedPathForNetworkDosDevice(driveName); -#endif } private static string WinGetSubstitutedPathForNetworkDosDevice(string driveName) @@ -897,76 +797,12 @@ private static string WinGetSubstitutedPathForNetworkDosDevice(string driveName) string associatedPath = null; if (!string.IsNullOrEmpty(driveName) && driveName.Length == 1) { - // By default buffer size is set to 300 which would generally be sufficient in most of the cases. - int bufferSize = 300; - var pathInfo = new StringBuilder(bufferSize); - driveName += ':'; - - // Call the windows API - while (true) - { - pathInfo.EnsureCapacity(bufferSize); - int retValue = NativeMethods.QueryDosDevice(driveName, pathInfo, bufferSize); - if (retValue > 0) - { - // If the drive letter is a substed path, the result will be in the format of - // - "\??\C:\RealPath" for local path - // - "\??\UNC\RealPath" for network path - associatedPath = pathInfo.ToString(); - if (associatedPath.StartsWith("\\??\\", StringComparison.OrdinalIgnoreCase)) - { - associatedPath = associatedPath.Remove(0, 4); - if (associatedPath.StartsWith("UNC", StringComparison.OrdinalIgnoreCase)) - { - associatedPath = associatedPath.Remove(0, 3); - associatedPath = "\\" + associatedPath; - } - else if (associatedPath.EndsWith(':')) - { - // The substed path is the root path of a drive. For example: subst Y: C:\ - associatedPath += Path.DirectorySeparatorChar; - } - } - else - { - // The drive name is not a substed path, then we return the root path of the drive - associatedPath = driveName + "\\"; - } - - break; - } - - // Windows API call failed - int errorCode = Marshal.GetLastWin32Error(); - if (errorCode != 122) - { - // ERROR_INSUFFICIENT_BUFFER = 122 - // For an error other than "insufficient buffer", throw it - throw new Win32Exception((int)errorCode); - } - - // We got the "insufficient buffer" error. In this case we extend - // the buffer size, unless it's unreasonably too large. - if (bufferSize >= 32767) - { - // "The Windows API has many functions that also have Unicode versions to permit - // an extended-length path for a maximum total path length of 32,767 characters" - // See https://msdn.microsoft.com/library/aa365247.aspx#maxpath - string errorMsg = StringUtil.Format(FileSystemProviderStrings.SubstitutePathTooLong, driveName); - throw new InvalidOperationException(errorMsg); - } - - // Extend the buffer size and try again. - bufferSize *= 10; - if (bufferSize > 32767) - { - bufferSize = 32767; - } - } + associatedPath = Interop.Windows.GetDosDeviceForNetworkPath(driveName[0]); } return associatedPath; } +#endif /// /// Get the root path for a network drive or MS-DOS device. @@ -1245,6 +1081,20 @@ protected override bool IsValidPath(string path) } } + // .NET introduced a change where invalid characters are accepted https://learn.microsoft.com/en-us/dotnet/core/compatibility/2.1#path-apis-dont-throw-an-exception-for-invalid-characters + // We need to check for invalid characters ourselves. `Path.GetInvalidFileNameChars()` is a supserset of `Path.GetInvalidPathChars()` + + // Remove drive root first + string pathWithoutDriveRoot = path.Substring(Path.GetPathRoot(path).Length); + + foreach (string segment in pathWithoutDriveRoot.Split(Path.DirectorySeparatorChar)) + { + if (PathUtils.ContainsInvalidFileNameChars(segment)) + { + return false; + } + } + return true; } @@ -1367,7 +1217,7 @@ protected override void GetItem(string path) } catch (IOException ioError) { - // IOException contains specific message about the error occured and so no need for errordetails. + // IOException contains specific message about the error occurred and so no need for errordetails. ErrorRecord er = new ErrorRecord(ioError, "GetItemIOError", ErrorCategory.ReadError, path); WriteError(er); } @@ -1382,13 +1232,18 @@ private FileSystemInfo GetFileSystemItem(string path, ref bool isContainer, bool path = NormalizePath(path); FileInfo result = new FileInfo(path); - // FileInfo.Exists is always false for a directory path, so we check the attribute for existence. var attributes = result.Attributes; - if ((int)attributes == -1) { /* Path doesn't exist. */ return null; } - bool hidden = attributes.HasFlag(FileAttributes.Hidden); isContainer = attributes.HasFlag(FileAttributes.Directory); + // FileInfo allows for a file path to end in a trailing slash, but the resulting object + // is incomplete. A trailing slash should indicate a directory. So if the path ends in a + // trailing slash and is not a directory, return null + if (!isContainer && path.EndsWith(Path.DirectorySeparatorChar)) + { + return null; + } + FlagsExpression evaluator = null; FlagsExpression switchEvaluator = null; GetChildDynamicParameters fspDynamicParam = DynamicParameters as GetChildDynamicParameters; @@ -1790,7 +1645,7 @@ private void Dir( foreach (IEnumerable childList in target) { // On some systems, this is already sorted. For consistency, always sort again. - IEnumerable sortedChildList = childList.OrderBy(c => c.Name, StringComparer.CurrentCultureIgnoreCase); + IEnumerable sortedChildList = childList.OrderBy(static c => c.Name, StringComparer.CurrentCultureIgnoreCase); foreach (FileSystemInfo filesystemInfo in sortedChildList) { @@ -1899,9 +1754,14 @@ private void Dir( } bool hidden = false; + bool checkReparsePoint = true; if (!Force) { hidden = (recursiveDirectory.Attributes & FileAttributes.Hidden) != 0; + + // We've already taken the expense of initializing the Attributes property here, + // so we can use that to avoid needing to call IsReparsePointLikeSymlink() later. + checkReparsePoint = recursiveDirectory.Attributes.HasFlag(FileAttributes.ReparsePoint); } // if "Hidden" is explicitly specified anywhere in the attribute filter, then override @@ -1915,7 +1775,7 @@ private void Dir( // c) it is not a reparse point with a target (not OneDrive or an AppX link). if (tracker == null) { - if (InternalSymbolicLinkLinkCodeMethods.IsReparsePointWithTarget(recursiveDirectory)) + if (checkReparsePoint && InternalSymbolicLinkLinkCodeMethods.IsReparsePointLikeSymlink(recursiveDirectory)) { continue; } @@ -2066,11 +1926,32 @@ string ToModeString(FileSystemInfo fileSystemInfo) /// Name if a file or directory, Name -> Target if symlink. public static string NameString(PSObject instance) { - return instance?.BaseObject is FileSystemInfo fileInfo - ? InternalSymbolicLinkLinkCodeMethods.IsReparsePointWithTarget(fileInfo) - ? $"{fileInfo.Name} -> {InternalSymbolicLinkLinkCodeMethods.GetTarget(instance)}" - : fileInfo.Name - : string.Empty; + if (instance?.BaseObject is FileSystemInfo fileInfo) + { + if (InternalSymbolicLinkLinkCodeMethods.IsReparsePointLikeSymlink(fileInfo)) + { + return $"{PSStyle.Instance.FileInfo.SymbolicLink}{fileInfo.Name}{PSStyle.Instance.Reset} -> {fileInfo.LinkTarget}"; + } + else if (fileInfo.Attributes.HasFlag(FileAttributes.Directory)) + { + return $"{PSStyle.Instance.FileInfo.Directory}{fileInfo.Name}{PSStyle.Instance.Reset}"; + } + else if (PSStyle.Instance.FileInfo.Extension.ContainsKey(fileInfo.Extension)) + { + return $"{PSStyle.Instance.FileInfo.Extension[fileInfo.Extension]}{fileInfo.Name}{PSStyle.Instance.Reset}"; + } + else if ((Platform.IsWindows && CommandDiscovery.PathExtensions.Contains(fileInfo.Extension.ToLower())) || + (!Platform.IsWindows && Platform.NonWindowsIsExecutable(fileInfo.FullName))) + { + return $"{PSStyle.Instance.FileInfo.Executable}{fileInfo.Name}{PSStyle.Instance.Reset}"; + } + else + { + return fileInfo.Name; + } + } + + return string.Empty; } /// @@ -2095,7 +1976,7 @@ public static string LengthString(PSObject instance) public static string LastWriteTimeString(PSObject instance) { return instance?.BaseObject is FileSystemInfo fileInfo - ? string.Format(CultureInfo.CurrentCulture, "{0,10:d} {0,8:t}", fileInfo.LastWriteTime) + ? string.Create(CultureInfo.CurrentCulture, $"{fileInfo.LastWriteTime,10:d} {fileInfo.LastWriteTime,8:t}") : string.Empty; } @@ -2221,7 +2102,7 @@ protected override void RenameItem( } catch (IOException ioException) { - // IOException contains specific message about the error occured and so no need for errordetails. + // IOException contains specific message about the error occurred and so no need for errordetails. WriteError(new ErrorRecord(ioException, "RenameItemIOError", ErrorCategory.WriteError, path)); } catch (UnauthorizedAccessException accessException) @@ -2240,7 +2121,7 @@ protected override void RenameItem( /// /// The path of the file or directory to create. /// - /// + /// /// Specify "file" to create a file. /// Specify "directory" or "container" to create a directory. /// @@ -2333,7 +2214,7 @@ protected override void NewItem( } catch (IOException exception) { - // IOException contains specific message about the error occured and so no need for errordetails. + // IOException contains specific message about the error occurred and so no need for errordetails. WriteError(new ErrorRecord(exception, "NewItemIOError", ErrorCategory.WriteError, path)); } catch (UnauthorizedAccessException accessException) @@ -2376,19 +2257,23 @@ 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)) + { + strTargetPath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(strTargetPath); + } + exists = GetFileSystemInfo(strTargetPath, out isDirectory) != null; } } @@ -2431,6 +2316,13 @@ protected override void NewItem( if (Force) { + if (itemType == ItemType.HardLink && string.Equals(path, strTargetPath, StringComparison.OrdinalIgnoreCase)) + { + string message = StringUtil.Format(FileSystemProviderStrings.NewItemTargetIsSameAsLink, path); + WriteError(new ErrorRecord(new InvalidOperationException(message), "TargetIsSameAsLink", ErrorCategory.InvalidOperation, path)); + return; + } + try { if (!isSymLinkDirectory && symLinkExists) @@ -2487,7 +2379,7 @@ protected override void NewItem( #if UNIX success = Platform.NonWindowsCreateHardLink(path, strTargetPath); #else - success = WinCreateHardLink(path, strTargetPath); + success = Interop.Windows.CreateHardLink(path, strTargetPath, IntPtr.Zero); #endif } @@ -2554,6 +2446,13 @@ protected override void NewItem( bool exists = false; + // junctions require an absolute path + if (!Path.IsPathRooted(strTargetPath)) + { + WriteError(new ErrorRecord(new ArgumentException(FileSystemProviderStrings.JunctionAbsolutePath), "NotAbsolutePath", ErrorCategory.InvalidArgument, strTargetPath)); + return; + } + try { exists = GetFileSystemInfo(strTargetPath, out isDirectory) != null; @@ -2605,40 +2504,35 @@ protected override void NewItem( } // Junctions cannot have files - if (DirectoryInfoHasChildItems((DirectoryInfo)pathDirInfo)) + if (!Force && DirectoryInfoHasChildItems((DirectoryInfo)pathDirInfo)) { string message = StringUtil.Format(FileSystemProviderStrings.DirectoryNotEmpty, path); WriteError(new ErrorRecord(new IOException(message), "DirectoryNotEmpty", ErrorCategory.WriteError, path)); return; } - if (Force) + try { - try + pathDirInfo.Delete(); + } + catch (Exception exception) + { + if ((exception is DirectoryNotFoundException) || + (exception is UnauthorizedAccessException) || + (exception is System.Security.SecurityException) || + (exception is IOException)) { - pathDirInfo.Delete(); + WriteError(new ErrorRecord(exception, "NewItemDeleteIOError", ErrorCategory.WriteError, path)); } - catch (Exception exception) + else { - if ((exception is DirectoryNotFoundException) || - (exception is UnauthorizedAccessException) || - (exception is System.Security.SecurityException) || - (exception is IOException)) - { - WriteError(new ErrorRecord(exception, "NewItemDeleteIOError", ErrorCategory.WriteError, path)); - } - else - { - throw; - } + throw; } } } - else - { - CreateDirectory(path, false); - pathDirInfo = new DirectoryInfo(path); - } + + CreateDirectory(path, streamOutput: false); + pathDirInfo = new DirectoryInfo(path); try { @@ -2690,26 +2584,21 @@ protected override void NewItem( } } +#if !UNIX private static bool WinCreateSymbolicLink(string path, string strTargetPath, bool isDirectory) { // The new AllowUnprivilegedCreate is only available on Win10 build 14972 or newer - var flags = isDirectory ? NativeMethods.SymbolicLinkFlags.Directory : NativeMethods.SymbolicLinkFlags.File; + var flags = isDirectory ? Interop.Windows.SymbolicLinkFlags.Directory : Interop.Windows.SymbolicLinkFlags.File; - Version minBuildOfDeveloperMode = new Version(10, 0, 14972, 0); - if (Environment.OSVersion.Version >= minBuildOfDeveloperMode) + if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 14972, 0)) { - flags |= NativeMethods.SymbolicLinkFlags.AllowUnprivilegedCreate; + flags |= Interop.Windows.SymbolicLinkFlags.AllowUnprivilegedCreate; } - var created = NativeMethods.CreateSymbolicLink(path, strTargetPath, flags); + var created = Interop.Windows.CreateSymbolicLink(path, strTargetPath, flags); return created; } - - private static bool WinCreateHardLink(string path, string strTargetPath) - { - bool success = NativeMethods.CreateHardLink(path, strTargetPath, IntPtr.Zero); - return success; - } +#endif private static bool WinCreateJunction(string path, string strTargetPath) { @@ -2776,12 +2665,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)) { @@ -2811,7 +2694,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) { @@ -2826,10 +2709,17 @@ 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 occured and so no need for errordetails. + // IOException contains specific message about the error occurred and so no need for errordetails. WriteError(new ErrorRecord(ioException, "CreateDirectoryIOError", ErrorCategory.WriteError, path)); } } @@ -2904,7 +2794,7 @@ private bool CreateIntermediateDirectories(string path) } catch (IOException ioException) { - // IOException contains specific message about the error occured and so no need for errordetails. + // IOException contains specific message about the error occurred and so no need for errordetails. WriteError(new ErrorRecord(ioException, "CreateIntermediateDirectoriesIOError", ErrorCategory.WriteError, path)); } catch (UnauthorizedAccessException accessException) @@ -2983,6 +2873,19 @@ protected override void RemoveItem(string path, bool recurse) return; } + if (Context != null + && Context.ExecutionContext.SessionState.PSVariable.Get(SpecialVariables.ProgressPreferenceVarPath.UserPath).Value is ActionPreference progressPreference + && progressPreference == ActionPreference.Continue) + { + { + Task.Run(() => + { + GetTotalFiles(path, recurse); + }); + _removeStopwatch.Start(); + } + } + #if UNIX if (iscontainer) { @@ -3010,7 +2913,10 @@ protected override void RemoveItem(string path, bool recurse) foreach (AlternateStreamData stream in AlternateDataStreamUtilities.GetStreams(fsinfo.FullName)) { - if (!p.IsMatch(stream.Stream)) { continue; } + if (!p.IsMatch(stream.Stream)) + { + continue; + } foundStream = true; @@ -3043,11 +2949,21 @@ protected override void RemoveItem(string path, bool recurse) RemoveFileInfoItem((FileInfo)fsinfo, Force); } } + + if (Stopping || _removedFiles == _totalFiles) + { + _removeStopwatch.Stop(); + var progress = new ProgressRecord(REMOVE_FILE_ACTIVITY_ID, " ", " ") + { + RecordType = ProgressRecordType.Completed + }; + WriteProgress(progress); + } #endif } catch (IOException exception) { - // IOException contains specific message about the error occured and so no need for errordetails. + // IOException contains specific message about the error occurred and so no need for errordetails. WriteError(new ErrorRecord(exception, "RemoveItemIOError", ErrorCategory.WriteError, path)); } catch (UnauthorizedAccessException accessException) @@ -3107,22 +3023,31 @@ private void RemoveDirectoryInfoItem(DirectoryInfo directory, bool recurse, bool continueRemoval = ShouldProcess(directory.FullName, action); } - if (directory.Attributes.HasFlag(FileAttributes.ReparsePoint)) + if (InternalSymbolicLinkLinkCodeMethods.IsReparsePointLikeSymlink(directory)) { + void WriteErrorHelper(Exception exception) + { + WriteError(new ErrorRecord(exception, errorId: "DeleteSymbolicLinkFailed", ErrorCategory.WriteError, directory)); + } + try { - // TODO: - // Different symlinks seem to vary by behavior. - // In particular, OneDrive symlinks won't remove without recurse, - // but the .NET API here does not allow us to distinguish them. - // We may need to revisit using p/Invokes here to get the right behavior - directory.Delete(); + if (InternalTestHooks.OneDriveTestOn) + { + WriteErrorHelper(new IOException()); + return; + } + else + { + // Name surrogates should just be detached. + directory.Delete(); + } } catch (Exception e) { string error = StringUtil.Format(FileSystemProviderStrings.CannotRemoveItem, directory.FullName, e.Message); var exception = new IOException(error, e); - WriteError(new ErrorRecord(exception, errorId: "DeleteSymbolicLinkFailed", ErrorCategory.WriteError, directory)); + WriteErrorHelper(exception); } return; @@ -3168,6 +3093,8 @@ private void RemoveDirectoryInfoItem(DirectoryInfo directory, bool recurse, bool if (file != null) { + long fileBytesSize = file.Length; + if (recurse) { // When recurse is specified we need to confirm each @@ -3180,6 +3107,25 @@ private void RemoveDirectoryInfoItem(DirectoryInfo directory, bool recurse, bool // subitems without confirming with the user. RemoveFileSystemItem(file, force); } + + if (_totalFiles > 0) + { + _removedFiles++; + _removedBytes += fileBytesSize; + if (_removeStopwatch.Elapsed.TotalSeconds > ProgressBarDurationThreshold) + { + double speed = _removedBytes / 1024 / 1024 / _removeStopwatch.Elapsed.TotalSeconds; + var progress = new ProgressRecord( + REMOVE_FILE_ACTIVITY_ID, + StringUtil.Format(FileSystemProviderStrings.RemovingLocalFileActivity, _removedFiles, _totalFiles), + StringUtil.Format(FileSystemProviderStrings.RemovingLocalBytesStatus, Utils.DisplayHumanReadableFileSize(_removedBytes), Utils.DisplayHumanReadableFileSize(_totalBytes), speed) + ); + var percentComplete = _totalBytes != 0 ? (int)Math.Min(_removedBytes * 100 / _totalBytes, 100) : 100; + progress.PercentComplete = percentComplete; + progress.RecordType = ProgressRecordType.Processing; + WriteProgress(progress); + } + } } } @@ -3430,12 +3376,12 @@ private bool ItemExists(string path, out ErrorRecord error) if (itemExistsDynamicParameters.OlderThan.HasValue) { - result = lastWriteTime < itemExistsDynamicParameters.OlderThan.Value; + result &= lastWriteTime < itemExistsDynamicParameters.OlderThan.Value; } if (itemExistsDynamicParameters.NewerThan.HasValue) { - result = lastWriteTime > itemExistsDynamicParameters.NewerThan.Value; + result &= lastWriteTime > itemExistsDynamicParameters.NewerThan.Value; } } } @@ -3668,7 +3614,25 @@ protected override void CopyItem( } else // Copy-Item local { + if (Context != null && Context.ExecutionContext.SessionState.PSVariable.Get(SpecialVariables.ProgressPreferenceVarPath.UserPath).Value is ActionPreference progressPreference && progressPreference == ActionPreference.Continue) + { + { + Task.Run(() => + { + GetTotalFiles(path, recurse); + }); + _copyStopwatch.Start(); + } + } + CopyItemLocalOrToSession(path, destinationPath, recurse, Force, null); + if (Stopping || _copiedFiles == _totalFiles) + { + _copyStopwatch.Stop(); + var progress = new ProgressRecord(COPY_FILE_ACTIVITY_ID, " ", " "); + progress.RecordType = ProgressRecordType.Completed; + WriteProgress(progress); + } } } @@ -3676,6 +3640,47 @@ protected override void CopyItem( _excludeMatcher = null; } + private void GetTotalFiles(string path, bool recurse) + { + bool isContainer = IsItemContainer(path); + + try + { + if (isContainer) + { + var enumOptions = new EnumerationOptions() + { + IgnoreInaccessible = true, + AttributesToSkip = 0, + RecurseSubdirectories = recurse + }; + + var directory = new DirectoryInfo(path); + foreach (var file in directory.EnumerateFiles("*", enumOptions)) + { + if (!SessionStateUtilities.MatchesAnyWildcardPattern(file.Name, _excludeMatcher, defaultValue: false)) + { + _totalFiles++; + _totalBytes += file.Length; + } + } + } + else + { + var file = new FileInfo(path); + if (!SessionStateUtilities.MatchesAnyWildcardPattern(file.Name, _excludeMatcher, defaultValue: false)) + { + _totalFiles++; + _totalBytes += file.Length; + } + } + } + catch + { + // ignore exception + } + } + private void CopyItemFromRemoteSession(string path, string destinationPath, bool recurse, bool force, PSSession fromSession) { using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create()) @@ -3887,7 +3892,7 @@ private void CopyDirectoryInfoItem( } catch (IOException ioException) { - // IOException contains specific message about the error occured and so no need for errordetails. + // IOException contains specific message about the error occurred and so no need for errordetails. WriteError(new ErrorRecord(ioException, "CopyDirectoryInfoItemIOError", ErrorCategory.WriteError, file)); } catch (UnauthorizedAccessException accessException) @@ -3918,7 +3923,7 @@ private void CopyDirectoryInfoItem( } catch (IOException ioException) { - // IOException contains specific message about the error occured and so no need for errordetails. + // IOException contains specific message about the error occurred and so no need for errordetails. WriteError(new ErrorRecord(ioException, "CopyDirectoryInfoItemIOError", ErrorCategory.WriteError, childDir)); } catch (UnauthorizedAccessException accessException) @@ -3984,6 +3989,25 @@ private void CopyFileInfoItem(FileInfo file, string destinationPath, bool force, FileInfo result = new FileInfo(destinationPath); WriteItemObject(result, destinationPath, false); + + if (_totalFiles > 0) + { + _copiedFiles++; + _copiedBytes += file.Length; + if (_copyStopwatch.Elapsed.TotalSeconds > ProgressBarDurationThreshold) + { + double speed = (double)(_copiedBytes / 1024 / 1024) / _copyStopwatch.Elapsed.TotalSeconds; + var progress = new ProgressRecord( + COPY_FILE_ACTIVITY_ID, + StringUtil.Format(FileSystemProviderStrings.CopyingLocalFileActivity, _copiedFiles, _totalFiles), + StringUtil.Format(FileSystemProviderStrings.CopyingLocalBytesStatus, Utils.DisplayHumanReadableFileSize(_copiedBytes), Utils.DisplayHumanReadableFileSize(_totalBytes), speed) + ); + var percentComplete = _totalBytes != 0 ? (int)Math.Min(_copiedBytes * 100 / _totalBytes, 100) : 100; + progress.PercentComplete = percentComplete; + progress.RecordType = ProgressRecordType.Processing; + WriteProgress(progress); + } + } } else { @@ -4424,7 +4448,7 @@ private bool PerformCopyFileFromRemoteSession(string sourceFileFullName, FileInf } } - // To accomodate empty files + // To accommodate empty files string content = string.Empty; if (op["b64Fragment"] != null) { @@ -4478,10 +4502,7 @@ private bool PerformCopyFileFromRemoteSession(string sourceFileFullName, FileInf } finally { - if (wStream != null) - { - wStream.Dispose(); - } + wStream?.Dispose(); // If copying the file from the remote session failed, then remove it. if (errorWhileCopyRemoteFile && File.Exists(destinationFile.FullName)) @@ -4500,7 +4521,10 @@ private bool PerformCopyFileFromRemoteSession(string sourceFileFullName, FileInf private void InitializeFunctionsPSCopyFileToRemoteSession(System.Management.Automation.PowerShell ps) { - if ((ps == null) || !ValidRemoteSessionForScripting(ps.Runspace)) { return; } + if ((ps == null) || !ValidRemoteSessionForScripting(ps.Runspace)) + { + return; + } ps.AddScript(CopyFileRemoteUtils.AllCopyToRemoteScripts); SafeInvokeCommand.Invoke(ps, this, null, false); @@ -4508,7 +4532,10 @@ private void InitializeFunctionsPSCopyFileToRemoteSession(System.Management.Auto private void RemoveFunctionPSCopyFileToRemoteSession(System.Management.Automation.PowerShell ps) { - if ((ps == null) || !ValidRemoteSessionForScripting(ps.Runspace)) { return; } + if ((ps == null) || !ValidRemoteSessionForScripting(ps.Runspace)) + { + return; + } const string remoteScript = @" Microsoft.PowerShell.Management\Remove-Item function:PSCopyToSessionHelper -ea SilentlyContinue -Force @@ -4761,10 +4788,7 @@ private bool CopyFileStreamToRemoteSession(FileInfo file, string destinationPath } finally { - if (fStream != null) - { - fStream.Dispose(); - } + fStream?.Dispose(); } return success; @@ -4914,6 +4938,17 @@ private bool PathIsReservedDeviceName(string destinationPath, string errorId) return pathIsReservedDeviceName; } + private long _totalFiles; + private long _totalBytes; + private long _copiedFiles; + private long _copiedBytes; + private readonly Stopwatch _copyStopwatch = new Stopwatch(); + + private long _removedBytes; + private long _removedFiles; + private readonly Stopwatch _removeStopwatch = new(); + + private const double ProgressBarDurationThreshold = 2.0; #endregion CopyItem #endregion ContainerCmdletProvider members @@ -4945,30 +4980,30 @@ protected override string GetParentPath(string path, string root) // make sure we return two backslashes so it still results in a UNC path parentPath = "\\\\"; } + + if (!parentPath.EndsWith(StringLiterals.DefaultPathSeparator) + && Utils.PathIsDevicePath(parentPath) + && parentPath.Length - parentPath.Replace(StringLiterals.DefaultPathSeparatorString, string.Empty).Length == 3) + { + // Device paths start with either "\\.\" or "\\?\" + // When referring to the root, like: "\\.\CDROM0\" then it needs the trailing separator to be valid. + parentPath += StringLiterals.DefaultPathSeparator; + } #endif + s_tracer.WriteLine("GetParentPath returning '{0}'", parentPath); return parentPath; } // Note: we don't use IO.Path.IsPathRooted as this deals with "invalid" i.e. unnormalized paths private static bool IsAbsolutePath(string path) { - bool result = false; - // check if we're on a single root filesystem and it's an absolute path if (LocationGlobber.IsSingleFileSystemAbsolutePath(path)) { return true; } - // Find the drive separator - int index = path.IndexOf(':'); - - if (index != -1) - { - result = true; - } - - return result; + return path.Contains(':'); } /// @@ -5075,10 +5110,7 @@ protected override string NormalizeRelativePath( throw PSTraceSource.NewArgumentException(nameof(path)); } - if (basePath == null) - { - basePath = string.Empty; - } + basePath ??= string.Empty; s_tracer.WriteLine("basePath = {0}", basePath); @@ -5145,7 +5177,7 @@ protected override string NormalizeRelativePath( #if UNIX // We don't use the Directory.EnumerateFiles() for Unix because the path // may contain additional globbing patterns such as '[ab]' - // which Directory.EnumerateFiles() processes, giving undesireable + // which Directory.EnumerateFiles() processes, giving undesirable // results in this context. if (!File.Exists(result) && !Directory.Exists(result)) { @@ -5216,7 +5248,7 @@ protected override string NormalizeRelativePath( } catch (IOException ioError) { - // IOException contains specific message about the error occured and so no need for errordetails. + // IOException contains specific message about the error occurred and so no need for errordetails. WriteError(new ErrorRecord(ioError, "NormalizeRelativePathIOError", ErrorCategory.ReadError, path)); break; } @@ -5267,10 +5299,7 @@ private string NormalizeRelativePathHelper(string path, string basePath) return string.Empty; } - if (basePath == null) - { - basePath = string.Empty; - } + basePath ??= string.Empty; s_tracer.WriteLine("basePath = {0}", basePath); @@ -5813,6 +5842,17 @@ protected override void MoveItem( destination = MakePath(destination, dir.Name); } + // Don't allow moving a directory into itself or its sub-directory. + string pathWithoutEndingSeparator = Path.TrimEndingDirectorySeparator(path); + if (destination.StartsWith(pathWithoutEndingSeparator + Path.DirectorySeparatorChar) + || destination.Equals(pathWithoutEndingSeparator, StringComparison.OrdinalIgnoreCase)) + { + string error = StringUtil.Format(FileSystemProviderStrings.TargetCannotBeSubdirectoryOfSource, destination); + var e = new IOException(error); + WriteError(new ErrorRecord(e, "MoveItemArgumentError", ErrorCategory.InvalidArgument, destination)); + return; + } + // Get the confirmation text string action = FileSystemProviderStrings.MoveItemActionDirectory; @@ -5860,7 +5900,7 @@ protected override void MoveItem( } catch (IOException ioException) { - // IOException contains specific message about the error occured and so no need for errordetails. + // IOException contains specific message about the error occurred and so no need for errordetails. WriteError(new ErrorRecord(ioException, "MoveItemIOError", ErrorCategory.WriteError, path)); } catch (UnauthorizedAccessException accessException) @@ -5973,7 +6013,7 @@ private void MoveFileInfoItem( (exception is ArgumentNullException) || (exception is IOException)) { - // IOException contains specific message about the error occured and so no need for errordetails. + // IOException contains specific message about the error occurred and so no need for errordetails. WriteError(new ErrorRecord(ioException, "MoveFileInfoItemIOError", ErrorCategory.WriteError, destfile)); } else @@ -5982,13 +6022,13 @@ private void MoveFileInfoItem( } else { - // IOException contains specific message about the error occured and so no need for errordetails. + // IOException contains specific message about the error occurred and so no need for errordetails. WriteError(new ErrorRecord(ioException, "MoveFileInfoItemIOError", ErrorCategory.WriteError, file)); } } else { - // IOException contains specific message about the error occured and so no need for errordetails. + // IOException contains specific message about the error occurred and so no need for errordetails. WriteError(new ErrorRecord(ioException, "MoveFileInfoItemIOError", ErrorCategory.WriteError, file)); } } @@ -6059,7 +6099,7 @@ private void MoveDirectoryInfoItem( } catch (IOException ioException) { - // IOException contains specific message about the error occured and so no need for errordetails. + // IOException contains specific message about the error occurred and so no need for errordetails. WriteError(new ErrorRecord(ioException, "MoveDirectoryItemIOError", ErrorCategory.WriteError, directory)); } } @@ -6078,12 +6118,21 @@ private void MoveDirectoryInfoUnchecked(DirectoryInfo directory, string destinat { if (InternalTestHooks.ThrowExdevErrorOnMoveDirectory) { - throw new IOException("Invalid cross-device link", hresult: MOVE_FAILED_ERROR); + throw new IOException("Invalid cross-device link"); } directory.MoveTo(destinationPath); } - catch (IOException e) when (e.HResult == MOVE_FAILED_ERROR) +#if UNIX + // This is the errno returned by the rename() syscall + // when an item is attempted to be renamed across filesystem mount boundaries. + // 0x80131620 is returned if the source and destination do not have the same root path + catch (IOException e) when (e.HResult == 18 || e.HResult == -2146232800) +#else + // 0x80070005 ACCESS_DENIED is returned when trying to move files across volumes like DFS + // 0x80131620 is returned if the source and destination do not have the same root path + catch (IOException e) when (e.HResult == -2147024891 || e.HResult == -2146232800) +#endif { // Rather than try to ascertain whether we can rename a directory ahead of time, // it's both faster and more correct to try to rename it and fall back to copy/deleting it @@ -6189,10 +6238,7 @@ public void GetProperty(string path, Collection providerSpecificPickList if (member != null) { value = member.Value; - if (result == null) - { - result = new PSObject(); - } + result ??= new PSObject(); result.Properties.Add(new PSNoteProperty(property, value)); } @@ -6221,7 +6267,7 @@ public void GetProperty(string path, Collection providerSpecificPickList } catch (IOException ioException) { - // IOException contains specific message about the error occured and so no need for errordetails. + // IOException contains specific message about the error occurred and so no need for errordetails. WriteError(new ErrorRecord(ioException, "GetPropertyIOError", ErrorCategory.ReadError, path)); } catch (UnauthorizedAccessException accessException) @@ -6521,7 +6567,7 @@ public void ClearProperty( } catch (IOException ioException) { - // IOException contains specific message about the error occured and so no need for errordetails. + // IOException contains specific message about the error occurred and so no need for errordetails. WriteError(new ErrorRecord(ioException, "ClearPropertyIOError", ErrorCategory.WriteError, path)); } } @@ -6576,7 +6622,7 @@ public IContentReader GetContentReader(string path) // Defaults for the file read operation string delimiter = "\n"; - Encoding encoding = ClrFacade.GetDefaultEncoding(); + Encoding encoding = Encoding.Default; bool waitForChanges = false; bool streamTypeSpecified = false; @@ -6701,7 +6747,7 @@ public IContentReader GetContentReader(string path) } catch (IOException ioException) { - // IOException contains specific message about the error occured and so no need for errordetails. + // IOException contains specific message about the error occurred and so no need for errordetails. WriteError(new ErrorRecord(ioException, "GetContentReaderIOError", ErrorCategory.ReadError, path)); } catch (System.Security.SecurityException securityException) @@ -6758,7 +6804,7 @@ public IContentWriter GetContentWriter(string path) // If this is true, then the content will be read as bytes bool usingByteEncoding = false; bool streamTypeSpecified = false; - Encoding encoding = ClrFacade.GetDefaultEncoding(); + Encoding encoding = Encoding.Default; const FileMode filemode = FileMode.OpenOrCreate; string streamName = null; bool suppressNewline = false; @@ -6841,7 +6887,7 @@ public IContentWriter GetContentWriter(string path) } catch (IOException ioException) { - // IOException contains specific message about the error occured and so no need for errordetails. + // IOException contains specific message about the error occurred and so no need for errordetails. WriteError(new ErrorRecord(ioException, "GetContentWriterIOError", ErrorCategory.WriteError, path)); } catch (System.Security.SecurityException securityException) @@ -7007,7 +7053,7 @@ public void ClearContent(string path) } catch (IOException ioException) { - // IOException contains specific message about the error occured and so no need for errordetails. + // IOException contains specific message about the error occurred and so no need for errordetails. WriteError(new ErrorRecord(ioException, "ClearContentIOError", ErrorCategory.WriteError, path)); } catch (UnauthorizedAccessException accessException) @@ -7111,134 +7157,35 @@ internal static bool PathIsNetworkPath(string path) #endif } +#if !UNIX + /// + /// The API 'PathIsNetworkPath' is not available in CoreSystem. + /// This implementation is based on the 'PathIsNetworkPath' API. + /// + /// A file system path. + /// True if the path is a network path. internal static bool WinPathIsNetworkPath(string path) - { - return NativeMethods.PathIsNetworkPath(path); // call the native method - } - - private static class NativeMethods - { - /// - /// WNetAddConnection2 API makes a connection to a network resource - /// and can redirect a local device to the network resource. - /// This API simulates the "new Use" functionality used to connect to - /// network resource. - /// - /// - /// The netResource structure contains information - /// about a network resource. - /// - /// The password used to get connected to network resource. - /// - /// - /// The username used to get connected to network resource. - /// - /// - /// The flags parameter is used to indicate if the created network - /// resource has to be persisted or not. - /// - /// If connection is established to the network resource - /// then success is returned or else the error code describing the - /// type of failure that occured while establishing - /// the connection is returned. - [DllImport("mpr.dll", CharSet = CharSet.Unicode)] - internal static extern int WNetAddConnection2(ref NetResource netResource, byte[] password, string username, int flags); - - /// - /// WNetCancelConnection2 function cancels an existing network connection. - /// - /// - /// PSDrive Name. - /// - /// - /// Connection Type. - /// - /// - /// Specifies whether the disconnection should occur if there are open files or jobs - /// on the connection. If this parameter is FALSE, the function fails - /// if there are open files or jobs. - /// - /// If connection is removed then success is returned or - /// else the error code describing the type of failure that occured while - /// trying to remove the connection is returned. - /// - [DllImport("mpr.dll", CharSet = CharSet.Unicode)] - internal static extern int WNetCancelConnection2(string driveName, int flags, bool force); - - /// - /// WNetGetConnection function retrieves the name of the network resource associated with a local device. - /// - /// - /// Local name of the PSDrive. - /// - /// - /// The remote name to which the PSDrive is getting mapped to. - /// - /// - /// length of the remote name of the created PSDrive. - /// - /// - [DllImport("mpr.dll", CharSet = CharSet.Unicode)] - internal static extern int WNetGetConnection(string localName, StringBuilder remoteName, ref int remoteNameLength); - -#if CORECLR // TODO:CORECLR Win32 function 'PathIsNetworkPath' is in an extension API set which is currently not on CSS. - /// - /// Searches a path for a drive letter within the range of 'A' to 'Z' and returns the corresponding drive number. - /// - /// - /// Path of the file being executed - /// - /// Returns 0 through 25 (corresponding to 'A' through 'Z') if the path has a drive letter, or -1 otherwise. - [DllImport("api-ms-win-core-shlwapi-legacy-l1-1-0.dll", CharSet = CharSet.Unicode)] - internal static extern int PathGetDriveNumber(string path); - - private static bool _WNetApiAvailable = true; - - /// - /// The API 'PathIsNetworkPath' is not available in CoreSystem. - /// This implementation is based on the 'PathIsNetworkPath' API. - /// - /// - /// - internal static bool PathIsNetworkPath(string path) { if (string.IsNullOrEmpty(path)) { return false; } - if (Utils.PathIsUnc(path)) + if (Utils.PathIsUnc(path, networkOnly : true)) { return true; } - if (!_WNetApiAvailable) + if (path.Length > 1 && path[1] == ':' && char.IsAsciiLetter(path[0])) { - return false; - } - - // 0 - 25 corresponding to 'A' - 'Z' - int driveId = PathGetDriveNumber(path); - if (driveId >= 0 && driveId < 26) - { - string driveName = (char)('A' + driveId) + ":"; - - int bufferSize = 260; // MAX_PATH from EhStorIoctl.h - StringBuilder uncBuffer = new StringBuilder(bufferSize); - int errorCode = -1; - try - { - errorCode = WNetGetConnection(driveName, uncBuffer, ref bufferSize); - } - catch (System.DllNotFoundException) - { - _WNetApiAvailable = false; - return false; - } + // path[0] is ASCII letter, e.g. is in 'A'-'Z' or 'a'-'z'. + int errorCode = Interop.Windows.GetUNCForNetworkDrive(path[0], out string _); // From the 'IsNetDrive' API. // 0: success; 1201: connection closed; 31: device error - if (errorCode == 0 || errorCode == 1201 || errorCode == 31) + if (errorCode == Interop.Windows.ERROR_SUCCESS || + errorCode == Interop.Windows.ERROR_CONNECTION_UNAVAIL || + errorCode == Interop.Windows.ERROR_GEN_FAILURE) { return true; } @@ -7246,138 +7193,13 @@ internal static bool PathIsNetworkPath(string path) return false; } -#else - /// - /// Facilitates to validate if the supplied path exists locally or on the network share. - /// - /// - /// Path of the file being executed. - /// - /// True if the path is a network path or else returns false. - [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool PathIsNetworkPath(string path); #endif - /// - /// The function can obtain the current mapping for a particular MS-DOS device name. - /// - /// If lpDeviceName is non-NULL, the function retrieves information about the particular MS-DOS device specified by lpDeviceName. - /// The first null-terminated string stored into the buffer is the current mapping for the device. - /// The other null-terminated strings represent undeleted prior mappings for the device. - /// - /// - /// The particular MS-DOS device name. - /// - /// - /// The buffer to receive the result of the query. - /// - /// - /// The maximum number of characters that can be stored into the buffer - /// - /// - [DllImport(PinvokeDllNames.QueryDosDeviceDllName, CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern int QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax); - - /// - /// Creates a symbolic link using the native API. - /// - /// Path of the symbolic link. - /// Path of the target of the symbolic link. - /// Flag values from SymbolicLinkFlags enum. - /// 1 on successful creation. - [DllImport(PinvokeDllNames.CreateSymbolicLinkDllName, CharSet = CharSet.Unicode, SetLastError = true)] - [return: MarshalAs(UnmanagedType.I1)] - internal static extern bool CreateSymbolicLink(string name, string destination, SymbolicLinkFlags symbolicLinkFlags); - - /// - /// Flags used when creating a symbolic link. - /// - [Flags] - internal enum SymbolicLinkFlags - { - /// - /// Symbolic link is a file. - /// - File = 0, - - /// - /// Symbolic link is a directory. - /// - Directory = 1, - - /// - /// Allow creation of symbolic link without elevation. Requires Developer mode. - /// - AllowUnprivilegedCreate = 2, - } - - /// - /// Creates a hard link using the native API. - /// - /// Name of the hard link. - /// Path to the target of the hard link. - /// - /// - [DllImport(PinvokeDllNames.CreateHardLinkDllName, CharSet = CharSet.Unicode, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool CreateHardLink(string name, string existingFileName, IntPtr SecurityAttributes); - - // OneDrive placeholder support -#if !UNIX - /// - /// Returns the placeholder compatibility mode for the current process. - /// - /// The process's placeholder compatibily mode (PHCM_xxx), or a negative value on error (PCHM_ERROR_xxx). - [DllImport("ntdll.dll")] - internal static extern sbyte RtlQueryProcessPlaceholderCompatibilityMode(); - - /// - /// Sets the placeholder compatibility mode for the current process. - /// - /// The placeholder compatibility mode to set. - /// The process's previous placeholder compatibily mode (PHCM_xxx), or a negative value on error (PCHM_ERROR_xxx). - [DllImport("ntdll.dll")] - internal static extern sbyte RtlSetProcessPlaceholderCompatibilityMode(sbyte pcm); - - internal const sbyte PHCM_APPLICATION_DEFAULT = 0; - internal const sbyte PHCM_DISGUISE_PLACEHOLDER = 1; - internal const sbyte PHCM_EXPOSE_PLACEHOLDERS = 2; - internal const sbyte PHCM_MAX = 2; - internal const sbyte PHCM_ERROR_INVALID_PARAMETER = -1; - internal const sbyte PHCM_ERROR_NO_TEB = -2; -#endif - } - - /// - /// Managed equivalent of NETRESOURCE structure of WNet API. - /// - [StructLayout(LayoutKind.Sequential)] - private struct NetResource - { - public int Scope; - public int Type; - public int DisplayType; - public int Usage; - - [MarshalAs(UnmanagedType.LPWStr)] - public string LocalName; - - [MarshalAs(UnmanagedType.LPWStr)] - public string RemoteName; - - [MarshalAs(UnmanagedType.LPWStr)] - public string Comment; - - [MarshalAs(UnmanagedType.LPWStr)] - public string Provider; - } - #region InodeTracker /// /// Tracks visited files/directories by caching their device IDs and inodes. /// - private class InodeTracker + private sealed class InodeTracker { private readonly HashSet<(UInt64, UInt64)> _visitations; @@ -7526,7 +7348,7 @@ internal sealed class GetChildDynamicParameters /// Gets or sets the filter directory flag. /// [Parameter] - [Alias("ad", "d")] + [Alias("ad")] public SwitchParameter Directory { get { return _attributeDirectory; } @@ -7610,8 +7432,8 @@ internal FileSystemContentDynamicParametersBase(FileSystemProvider provider) /// reading data from the file. /// [Parameter] - [ArgumentToEncodingTransformationAttribute()] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] public Encoding Encoding { @@ -7623,7 +7445,7 @@ public Encoding Encoding set { // Check for UTF-7 by checking for code page 65000 - // See: https://docs.microsoft.com/en-us/dotnet/core/compatibility/corefx#utf-7-code-paths-are-obsolete + // See: https://learn.microsoft.com/dotnet/core/compatibility/corefx#utf-7-code-paths-are-obsolete if (value != null && value.CodePage == 65000) { _provider.WriteWarning(PathUtilsStrings.Utf7EncodingObsolete); @@ -7634,7 +7456,7 @@ public Encoding Encoding } } - private Encoding _encoding = ClrFacade.GetDefaultEncoding(); + private Encoding _encoding = Encoding.Default; /// /// Return file contents as a byte stream or create file from a series of bytes. @@ -7836,15 +7658,8 @@ public class FileSystemProviderRemoveItemDynamicParameters /// /// Class to find the symbolic link target. /// - public static class InternalSymbolicLinkLinkCodeMethods + public static partial class InternalSymbolicLinkLinkCodeMethods { - // This size comes from measuring the size of the header of REPARSE_GUID_DATA_BUFFER - private const int REPARSE_GUID_DATA_BUFFER_HEADER_SIZE = 24; - - // Maximum reparse buffer info size. The max user defined reparse - // data is 16KB, plus there's a header. - private const int MAX_REPARSE_SIZE = (16 * 1024) + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE; - private const int FSCTL_GET_REPARSE_POINT = 0x000900A8; private const int FSCTL_SET_REPARSE_POINT = 0x000900A4; @@ -7859,62 +7674,6 @@ public static class InternalSymbolicLinkLinkCodeMethods private const string NonInterpretedPathPrefix = @"\??\"; - private const int MAX_PATH = 260; - - [Flags] - // dwDesiredAccess of CreateFile - internal enum FileDesiredAccess : uint - { - GenericZero = 0, - GenericRead = 0x80000000, - GenericWrite = 0x40000000, - GenericExecute = 0x20000000, - GenericAll = 0x10000000, - } - - [Flags] - // dwShareMode of CreateFile - internal enum FileShareMode : uint - { - None = 0x00000000, - Read = 0x00000001, - Write = 0x00000002, - Delete = 0x00000004, - } - - // dwCreationDisposition of CreateFile - internal enum FileCreationDisposition : uint - { - New = 1, - CreateAlways = 2, - OpenExisting = 3, - OpenAlways = 4, - TruncateExisting = 5, - } - - [Flags] - // dwFlagsAndAttributes - internal enum FileAttributes : uint - { - Readonly = 0x00000001, - Hidden = 0x00000002, - System = 0x00000004, - Archive = 0x00000020, - Encrypted = 0x00004000, - Write_Through = 0x80000000, - Overlapped = 0x40000000, - NoBuffering = 0x20000000, - RandomAccess = 0x10000000, - SequentialScan = 0x08000000, - DeleteOnClose = 0x04000000, - BackupSemantics = 0x02000000, - PosixSemantics = 0x01000000, - OpenReparsePoint = 0x00200000, - OpenNoRecall = 0x00100000, - SessionAware = 0x00800000, - Normal = 0x00000080 - } - [StructLayout(LayoutKind.Sequential)] private struct REPARSE_DATA_BUFFER_SYMBOLICLINK { @@ -7946,25 +7705,13 @@ private struct REPARSE_DATA_BUFFER_MOUNTPOINT public byte[] PathBuffer; } - [StructLayout(LayoutKind.Sequential)] - private struct REPARSE_DATA_BUFFER_APPEXECLINK - { - public uint ReparseTag; - public ushort ReparseDataLength; - public ushort Reserved; - public uint StringCount; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3FF0)] - public byte[] StringList; - } - [StructLayout(LayoutKind.Sequential)] private struct BY_HANDLE_FILE_INFORMATION { public uint FileAttributes; - public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime; - public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime; - public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime; + public FILE_TIME CreationTime; + public FILE_TIME LastAccessTime; + public FILE_TIME LastWriteTime; public uint VolumeSerialNumber; public uint FileSizeHigh; public uint FileSizeLow; @@ -7973,113 +7720,58 @@ private struct BY_HANDLE_FILE_INFORMATION public uint FileIndexLow; } - [StructLayout(LayoutKind.Sequential)] - private struct GUID - { - public uint Data1; - public ushort Data2; - public ushort Data3; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public char[] Data4; - } - - [StructLayout(LayoutKind.Sequential)] - private struct REPARSE_GUID_DATA_BUFFER + internal struct FILE_TIME { - public uint ReparseTag; - public ushort ReparseDataLength; - public ushort Reserved; - public GUID ReparseGuid; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_REPARSE_SIZE)] - public char[] DataBuffer; + public uint dwLowDateTime; + public uint dwHighDateTime; } - [DllImport(PinvokeDllNames.DeviceIoControlDllName, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] - private static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode, + [LibraryImport(PinvokeDllNames.DeviceIoControlDllName, StringMarshalling = StringMarshalling.Utf16, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode, IntPtr InBuffer, int nInBufferSize, IntPtr OutBuffer, int nOutBufferSize, out int pBytesReturned, IntPtr lpOverlapped); - [DllImport(PinvokeDllNames.GetFileInformationByHandleDllName, SetLastError = true, CharSet = CharSet.Unicode)] + [LibraryImport(PinvokeDllNames.GetFileInformationByHandleDllName)] [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool GetFileInformationByHandle( + private static partial bool GetFileInformationByHandle( IntPtr hFile, out BY_HANDLE_FILE_INFORMATION lpFileInformation); - [DllImport(PinvokeDllNames.CreateFileDllName, SetLastError = true, CharSet = CharSet.Unicode)] - internal static extern IntPtr CreateFile( - string lpFileName, - FileDesiredAccess dwDesiredAccess, - FileShareMode dwShareMode, - IntPtr lpSecurityAttributes, - FileCreationDisposition dwCreationDisposition, - FileAttributes dwFlagsAndAttributes, - IntPtr hTemplateFile); - - internal sealed class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid + /// + /// Gets the target of the specified reparse point. + /// + /// The object of FileInfo or DirectoryInfo type. + /// The target of the reparse point. + [Obsolete("This method is now obsolete. Please use the .NET API 'FileSystemInfo.LinkTarget'", error: true)] + public static string GetTarget(PSObject instance) { - private SafeFindHandle() : base(true) { } - - protected override bool ReleaseHandle() + if (instance.BaseObject is FileSystemInfo fileSysInfo) { - return FindClose(this.handle); - } - - [DllImport(PinvokeDllNames.FindCloseDllName)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool FindClose(IntPtr handle); - } - - // SetLastError is false as the use of this API doesn't not require GetLastError() to be called - [DllImport(PinvokeDllNames.FindFirstFileDllName, EntryPoint = "FindFirstFileExW", SetLastError = false, CharSet = CharSet.Unicode)] - private static extern SafeFindHandle FindFirstFileEx(string lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, ref WIN32_FIND_DATA lpFindFileData, FINDEX_SEARCH_OPS fSearchOp, IntPtr lpSearchFilter, int dwAdditionalFlags); - - internal enum FINDEX_INFO_LEVELS : uint - { - FindExInfoStandard = 0x0u, - FindExInfoBasic = 0x1u, - FindExInfoMaxInfoLevel = 0x2u, - } + if (!fileSysInfo.Exists) + { + throw new ArgumentException( + StringUtil.Format(SessionStateStrings.PathNotFound, fileSysInfo.FullName)); + } - internal enum FINDEX_SEARCH_OPS : uint - { - FindExSearchNameMatch = 0x0u, - FindExSearchLimitToDirectories = 0x1u, - FindExSearchLimitToDevices = 0x2u, - FindExSearchMaxSearchOp = 0x3u, - } + return fileSysInfo.LinkTarget; + } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal unsafe struct WIN32_FIND_DATA - { - internal uint dwFileAttributes; - internal System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime; - internal System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime; - internal System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime; - internal uint nFileSizeHigh; - internal uint nFileSizeLow; - internal uint dwReserved0; - internal uint dwReserved1; - internal fixed char cFileName[MAX_PATH]; - internal fixed char cAlternateFileName[14]; + return null; } /// - /// Gets the target of the specified reparse point. + /// Gets the target for a given file or directory, resolving symbolic links. /// - /// The object of FileInfo or DirectoryInfo type. - /// The target of the reparse point. - public static string GetTarget(PSObject instance) + /// The FileInfo or DirectoryInfo type. + /// The file path the instance points to. + public static string ResolvedTarget(PSObject instance) { if (instance.BaseObject is FileSystemInfo fileSysInfo) { -#if !UNIX - return WinInternalGetTarget(fileSysInfo.FullName); -#else - return UnixInternalGetTarget(fileSysInfo.FullName); -#endif + FileSystemInfo linkTarget = fileSysInfo.ResolveLinkTarget(true); + return linkTarget is null ? fileSysInfo.FullName : linkTarget.FullName; } return null; @@ -8102,45 +7794,24 @@ public static string GetLinkType(PSObject instance) return null; } -#if UNIX - private static string UnixInternalGetTarget(string filePath) - { - string link = Platform.NonWindowsInternalGetTarget(filePath); - - if (string.IsNullOrEmpty(link)) - { - throw new Win32Exception(Marshal.GetLastWin32Error()); - } - - return link; - } -#endif - private static string InternalGetLinkType(FileSystemInfo fileInfo) { - if (Platform.IsWindows) - { - return WinInternalGetLinkType(fileInfo.FullName); - } - else - { - return Platform.NonWindowsInternalGetLinkType(fileInfo); - } +#if UNIX + return Platform.NonWindowsInternalGetLinkType(fileInfo); +#else + return WinInternalGetLinkType(fileInfo.FullName); +#endif } +#if !UNIX [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")] private static string WinInternalGetLinkType(string filePath) { - if (!Platform.IsWindows) - { - throw new PlatformNotSupportedException(); - } - // We set accessMode parameter to zero because documentation says: // If this parameter is zero, the application can query certain metadata // such as file, directory, or device attributes without accessing // that file or device, even if GENERIC_READ access would have been denied. - using (SafeFileHandle handle = OpenReparsePoint(filePath, FileDesiredAccess.GenericZero)) + using (SafeFileHandle handle = WinOpenReparsePoint(filePath, (FileAccess)0)) { int outBufferSize = Marshal.SizeOf(); @@ -8172,7 +7843,7 @@ private static string WinInternalGetLinkType(string filePath) if (!result) { // It's not a reparse point or the file system doesn't support reparse points. - return IsHardLink(ref dangerousHandle) ? "HardLink" : null; + return WinIsHardLink(ref dangerousHandle) ? "HardLink" : null; } REPARSE_DATA_BUFFER_SYMBOLICLINK reparseDataBuffer = Marshal.PtrToStructure(outBuffer); @@ -8187,10 +7858,6 @@ private static string WinInternalGetLinkType(string filePath) linkType = "Junction"; break; - case IO_REPARSE_TAG_APPEXECLINK: - linkType = "AppExeCLink"; - break; - default: linkType = null; break; @@ -8209,13 +7876,28 @@ private static string WinInternalGetLinkType(string filePath) } } } +#endif internal static bool IsHardLink(FileSystemInfo fileInfo) { #if UNIX return Platform.NonWindowsIsHardLink(fileInfo); #else - return WinIsHardLink(fileInfo); + bool isHardLink = false; + + // only check for hard link if the item is not directory + if ((fileInfo.Attributes & System.IO.FileAttributes.Directory) != System.IO.FileAttributes.Directory) + { + SafeFileHandle handle = Interop.Windows.CreateFileWithSafeFileHandle(fileInfo.FullName, FileAccess.Read, FileShare.Read, FileMode.Open, Interop.Windows.FileAttributes.Normal); + + using (handle) + { + var dangerousHandle = handle.DangerousGetHandle(); + isHardLink = InternalSymbolicLinkLinkCodeMethods.WinIsHardLink(ref dangerousHandle); + } + } + + return isHardLink; #endif } @@ -8224,65 +7906,49 @@ internal static bool IsReparsePoint(FileSystemInfo fileInfo) return fileInfo.Attributes.HasFlag(System.IO.FileAttributes.ReparsePoint); } - internal static bool IsReparsePointWithTarget(FileSystemInfo fileInfo) + internal static bool IsReparsePointLikeSymlink(FileSystemInfo fileInfo) { - if (!IsReparsePoint(fileInfo)) +#if UNIX + // Reparse point on Unix is a symlink. + return IsReparsePoint(fileInfo); +#else + if (InternalTestHooks.OneDriveTestOn && fileInfo.Name == InternalTestHooks.OneDriveTestSymlinkName) { - return false; + return !InternalTestHooks.OneDriveTestRecurseOn; } -#if !UNIX - // It is a reparse point and we should check some reparse point tags. - var data = new WIN32_FIND_DATA(); - using (var handle = FindFirstFileEx(fileInfo.FullName, FINDEX_INFO_LEVELS.FindExInfoBasic, ref data, FINDEX_SEARCH_OPS.FindExSearchNameMatch, IntPtr.Zero, 0)) + + Interop.Windows.WIN32_FIND_DATA data = default; + using (Interop.Windows.SafeFindHandle handle = Interop.Windows.FindFirstFile(fileInfo.FullName, ref data)) { - // The name surrogate bit 0x20000000 is defined in https://docs.microsoft.com/windows/win32/fileio/reparse-point-tags - // Name surrogates (0x20000000) are reparse points that point to other named entities local to the filesystem - // (like symlinks and mount points). - // In the case of OneDrive, they are not name surrogates and would be safe to recurse into. - if (!handle.IsInvalid && (data.dwReserved0 & 0x20000000) == 0 && (data.dwReserved0 != IO_REPARSE_TAG_APPEXECLINK)) + if (handle.IsInvalid) { - return false; + // Our handle could be invalidated by something else touching the filesystem, + // so ensure we deal with that possibility here + int lastError = Marshal.GetLastWin32Error(); + throw new Win32Exception(lastError); } - } -#endif - return true; - } - - internal static bool WinIsHardLink(FileSystemInfo fileInfo) - { - bool isHardLink = false; - - // only check for hard link if the item is not directory - if ((fileInfo.Attributes & System.IO.FileAttributes.Directory) != System.IO.FileAttributes.Directory) - { - IntPtr nativeHandle = InternalSymbolicLinkLinkCodeMethods.CreateFile( - fileInfo.FullName, - InternalSymbolicLinkLinkCodeMethods.FileDesiredAccess.GenericRead, - InternalSymbolicLinkLinkCodeMethods.FileShareMode.Read, - IntPtr.Zero, - InternalSymbolicLinkLinkCodeMethods.FileCreationDisposition.OpenExisting, - InternalSymbolicLinkLinkCodeMethods.FileAttributes.Normal, - IntPtr.Zero); - using (SafeFileHandle handle = new SafeFileHandle(nativeHandle, true)) + // We already have the file attribute information from our Win32 call, + // so no need to take the expense of the FileInfo.FileAttributes call + const int FILE_ATTRIBUTE_REPARSE_POINT = 0x0400; + if ((data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) { - bool success = false; + // Not a reparse point. + return false; + } - try - { - handle.DangerousAddRef(ref success); - IntPtr dangerousHandle = handle.DangerousGetHandle(); - isHardLink = InternalSymbolicLinkLinkCodeMethods.IsHardLink(ref dangerousHandle); - } - finally - { - if (success) - handle.DangerousRelease(); - } + // The name surrogate bit 0x20000000 is defined in https://learn.microsoft.com/windows/win32/fileio/reparse-point-tags + // Name surrogates (0x20000000) are reparse points that point to other named entities local to the filesystem + // (like symlinks and mount points). + // In the case of OneDrive, they are not name surrogates and would be safe to recurse into. + if ((data.dwReserved0 & 0x20000000) == 0 && (data.dwReserved0 != IO_REPARSE_TAG_APPEXECLINK)) + { + return false; } } - return isHardLink; + return true; +#endif } internal static bool IsSameFileSystemItem(string pathOne, string pathTwo) @@ -8297,13 +7963,10 @@ internal static bool IsSameFileSystemItem(string pathOne, string pathTwo) #if !UNIX private static bool WinIsSameFileSystemItem(string pathOne, string pathTwo) { - const FileAccess access = FileAccess.Read; - const FileShare share = FileShare.Read; - const FileMode creation = FileMode.Open; - const FileAttributes attributes = FileAttributes.BackupSemantics | FileAttributes.PosixSemantics; + const Interop.Windows.FileAttributes Attributes = Interop.Windows.FileAttributes.BackupSemantics | Interop.Windows.FileAttributes.PosixSemantics; - using (var sfOne = AlternateDataStreamUtilities.NativeMethods.CreateFile(pathOne, access, share, IntPtr.Zero, creation, (int)attributes, IntPtr.Zero)) - using (var sfTwo = AlternateDataStreamUtilities.NativeMethods.CreateFile(pathTwo, access, share, IntPtr.Zero, creation, (int)attributes, IntPtr.Zero)) + using (var sfOne = Interop.Windows.CreateFileWithSafeFileHandle(pathOne, FileAccess.Read, FileShare.Read, FileMode.Open, Attributes)) + using (var sfTwo = Interop.Windows.CreateFileWithSafeFileHandle(pathTwo, FileAccess.Read, FileShare.Read, FileMode.Open, Attributes)) { if (!sfOne.IsInvalid && !sfTwo.IsInvalid) { @@ -8336,12 +7999,9 @@ internal static bool GetInodeData(string path, out System.ValueTuple inodeData) { - const FileAccess access = FileAccess.Read; - const FileShare share = FileShare.Read; - const FileMode creation = FileMode.Open; - const FileAttributes attributes = FileAttributes.BackupSemantics | FileAttributes.PosixSemantics; + const Interop.Windows.FileAttributes Attributes = Interop.Windows.FileAttributes.BackupSemantics | Interop.Windows.FileAttributes.PosixSemantics; - using (var sf = AlternateDataStreamUtilities.NativeMethods.CreateFile(path, access, share, IntPtr.Zero, creation, (int)attributes, IntPtr.Zero)) + using (var sf = Interop.Windows.CreateFileWithSafeFileHandle(path, FileAccess.Read, FileShare.Read, FileMode.Open, Attributes)) { if (!sf.IsInvalid) { @@ -8360,17 +8020,10 @@ private static bool WinGetInodeData(string path, out System.ValueTuple 1); } -#if !UNIX - internal static string WinInternalGetTarget(string path) - { - // We set accessMode parameter to zero because documentation says: - // If this parameter is zero, the application can query certain metadata - // such as file, directory, or device attributes without accessing - // that file or device, even if GENERIC_READ access would have been denied. - using (SafeFileHandle handle = OpenReparsePoint(path, FileDesiredAccess.GenericZero)) - { - return WinInternalGetTarget(handle); - } - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")] - private static string WinInternalGetTarget(SafeFileHandle handle) + internal static bool CreateJunction(string path, string target) { - int outBufferSize = Marshal.SizeOf(); - - IntPtr outBuffer = Marshal.AllocHGlobal(outBufferSize); - bool success = false; +#if UNIX + return false; +#else + ArgumentException.ThrowIfNullOrEmpty(path); + ArgumentException.ThrowIfNullOrEmpty(target); - try + using (SafeHandle handle = WinOpenReparsePoint(path, FileAccess.Write)) { - int bytesReturned; + byte[] mountPointBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(target)); - // OACR warning 62001 about using DeviceIOControl has been disabled. - // According to MSDN guidance DangerousAddRef() and DangerousRelease() have been used. - handle.DangerousAddRef(ref success); + var mountPoint = new REPARSE_DATA_BUFFER_MOUNTPOINT(); + mountPoint.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; + mountPoint.ReparseDataLength = (ushort)(mountPointBytes.Length + 12); // Added space for the header and null endo + mountPoint.SubstituteNameOffset = 0; + mountPoint.SubstituteNameLength = (ushort)mountPointBytes.Length; + mountPoint.PrintNameOffset = (ushort)(mountPointBytes.Length + 2); // 2 as unicode null take 2 bytes. + mountPoint.PrintNameLength = 0; + mountPoint.PathBuffer = new byte[0x3FF0]; // Buffer for max size. + Array.Copy(mountPointBytes, mountPoint.PathBuffer, mountPointBytes.Length); - bool result = DeviceIoControl( - handle.DangerousGetHandle(), - FSCTL_GET_REPARSE_POINT, - InBuffer: IntPtr.Zero, - nInBufferSize: 0, - outBuffer, - outBufferSize, - out bytesReturned, - lpOverlapped: IntPtr.Zero); + int nativeBufferSize = Marshal.SizeOf(mountPoint); + IntPtr nativeBuffer = Marshal.AllocHGlobal(nativeBufferSize); + bool success = false; - if (!result) + try { - // It's not a reparse point or the file system doesn't support reparse points. - return null; - } - - string targetDir = null; - - REPARSE_DATA_BUFFER_SYMBOLICLINK reparseDataBuffer = Marshal.PtrToStructure(outBuffer); + Marshal.StructureToPtr(mountPoint, nativeBuffer, false); - switch (reparseDataBuffer.ReparseTag) - { - case IO_REPARSE_TAG_SYMLINK: - targetDir = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer, reparseDataBuffer.SubstituteNameOffset, reparseDataBuffer.SubstituteNameLength); - break; + int bytesReturned = 0; - case IO_REPARSE_TAG_MOUNT_POINT: - REPARSE_DATA_BUFFER_MOUNTPOINT reparseMountPointDataBuffer = Marshal.PtrToStructure(outBuffer); - targetDir = Encoding.Unicode.GetString(reparseMountPointDataBuffer.PathBuffer, reparseMountPointDataBuffer.SubstituteNameOffset, reparseMountPointDataBuffer.SubstituteNameLength); - break; + // OACR warning 62001 about using DeviceIOControl has been disabled. + // According to MSDN guidance DangerousAddRef() and DangerousRelease() have been used. + handle.DangerousAddRef(ref success); - case IO_REPARSE_TAG_APPEXECLINK: - REPARSE_DATA_BUFFER_APPEXECLINK reparseAppExeDataBuffer = Marshal.PtrToStructure(outBuffer); - // The target file is at index 2 - if (reparseAppExeDataBuffer.StringCount >= 3) - { - string temp = Encoding.Unicode.GetString(reparseAppExeDataBuffer.StringList); - targetDir = temp.Split('\0')[2]; - } - break; + bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_SET_REPARSE_POINT, nativeBuffer, mountPointBytes.Length + 20, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero); - default: - return null; - } + if (!result) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } - if (targetDir != null && targetDir.StartsWith(NonInterpretedPathPrefix, StringComparison.OrdinalIgnoreCase)) - { - targetDir = targetDir.Substring(NonInterpretedPathPrefix.Length); + return result; } - - return targetDir; - } - finally - { - if (success) + finally { - handle.DangerousRelease(); - } - - Marshal.FreeHGlobal(outBuffer); - } - } -#endif - - internal static bool CreateJunction(string path, string target) - { - // this is a purely Windows specific feature, no feature flag - // used for that reason - if (Platform.IsWindows) - { - return WinCreateJunction(path, target); - } - else - { - return false; - } - } + Marshal.FreeHGlobal(nativeBuffer); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")] - private static bool WinCreateJunction(string path, string target) - { - if (!string.IsNullOrEmpty(path)) - { - if (!string.IsNullOrEmpty(target)) - { - using (SafeHandle handle = OpenReparsePoint(path, FileDesiredAccess.GenericWrite)) + if (success) { - byte[] mountPointBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(target)); - - REPARSE_DATA_BUFFER_MOUNTPOINT mountPoint = new REPARSE_DATA_BUFFER_MOUNTPOINT(); - mountPoint.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; - mountPoint.ReparseDataLength = (ushort)(mountPointBytes.Length + 12); // Added space for the header and null endo - mountPoint.SubstituteNameOffset = 0; - mountPoint.SubstituteNameLength = (ushort)mountPointBytes.Length; - mountPoint.PrintNameOffset = (ushort)(mountPointBytes.Length + 2); // 2 as unicode null take 2 bytes. - mountPoint.PrintNameLength = 0; - mountPoint.PathBuffer = new byte[0x3FF0]; // Buffer for max size. - Array.Copy(mountPointBytes, mountPoint.PathBuffer, mountPointBytes.Length); - - int nativeBufferSize = Marshal.SizeOf(mountPoint); - IntPtr nativeBuffer = Marshal.AllocHGlobal(nativeBufferSize); - bool success = false; - - try - { - Marshal.StructureToPtr(mountPoint, nativeBuffer, false); - - int bytesReturned = 0; - - // OACR warning 62001 about using DeviceIOControl has been disabled. - // According to MSDN guidance DangerousAddRef() and DangerousRelease() have been used. - handle.DangerousAddRef(ref success); - - bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_SET_REPARSE_POINT, nativeBuffer, mountPointBytes.Length + 20, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero); - - if (!result) - { - throw new Win32Exception(Marshal.GetLastWin32Error()); - } - - return result; - } - finally - { - Marshal.FreeHGlobal(nativeBuffer); - - if (success) - { - handle.DangerousRelease(); - } - } + handle.DangerousRelease(); } } - else - { - throw new ArgumentNullException(nameof(target)); - } - } - else - { - throw new ArgumentNullException(nameof(path)); } - } - - private static SafeFileHandle OpenReparsePoint(string reparsePoint, FileDesiredAccess accessMode) - { -#if UNIX - throw new PlatformNotSupportedException(); -#else - return WinOpenReparsePoint(reparsePoint, accessMode); #endif } - private static SafeFileHandle WinOpenReparsePoint(string reparsePoint, FileDesiredAccess accessMode) +#if !UNIX + private static SafeFileHandle WinOpenReparsePoint(string reparsePoint, FileAccess accessMode) { - IntPtr nativeHandle = CreateFile(reparsePoint, accessMode, - FileShareMode.Read | FileShareMode.Write | FileShareMode.Delete, - IntPtr.Zero, FileCreationDisposition.OpenExisting, - FileAttributes.BackupSemantics | FileAttributes.OpenReparsePoint, - IntPtr.Zero); + const Interop.Windows.FileAttributes Attributes = Interop.Windows.FileAttributes.BackupSemantics | Interop.Windows.FileAttributes.OpenReparsePoint; - int lastError = Marshal.GetLastWin32Error(); + SafeFileHandle reparsePointHandle = Interop.Windows.CreateFileWithSafeFileHandle(reparsePoint, accessMode, FileShare.ReadWrite | FileShare.Delete, FileMode.Open, Attributes); - if (lastError != 0) + if (reparsePointHandle.IsInvalid) + { + // Save last error since Dispose() will do another pinvoke. + int lastError = Marshal.GetLastPInvokeError(); + reparsePointHandle.Dispose(); throw new Win32Exception(lastError); - - SafeFileHandle reparsePointHandle = new SafeFileHandle(nativeHandle, true); + } return reparsePointHandle; } +#endif } #endregion @@ -8615,7 +8145,7 @@ public class AlternateStreamData /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "Needed by both the FileSystem provider and Unblock-File cmdlet.")] - public static class AlternateDataStreamUtilities + public static partial class AlternateDataStreamUtilities { /// /// List all of the streams on a file. @@ -8624,7 +8154,7 @@ public static class AlternateDataStreamUtilities /// The list of streams (and their size) in the file. internal static List GetStreams(string path) { - if (path == null) throw new ArgumentNullException(nameof(path)); + ArgumentNullException.ThrowIfNull(path); List alternateStreams = new List(); @@ -8640,7 +8170,10 @@ internal static List GetStreams(string path) // Directories don't normally have alternate streams, so this is not an exceptional state. // If a directory has no alternate data streams, FindFirstStreamW returns ERROR_HANDLE_EOF. - if (error == NativeMethods.ERROR_HANDLE_EOF) + // If the file system (such as FAT32) does not support alternate streams, then + // ERROR_INVALID_PARAMETER is returned by FindFirstStreamW. See documentation: + // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirststreamw + if (error == NativeMethods.ERROR_HANDLE_EOF || error == NativeMethods.ERROR_INVALID_PARAMETER) { return alternateStreams; } @@ -8667,8 +8200,7 @@ internal static List 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(Utils.Separators.Colon); + data.FileName = path; alternateStreams.Add(data); findStreamData = new AlternateStreamNativeData(); @@ -8677,7 +8209,9 @@ internal static List GetStreams(string path) int lastError = Marshal.GetLastWin32Error(); if (lastError != NativeMethods.ERROR_HANDLE_EOF) + { throw new Win32Exception(lastError); + } } finally { handle.Dispose(); } @@ -8717,15 +8251,9 @@ internal static FileStream CreateFileStream(string path, string streamName, File /// True if the stream was successfully created, otherwise false. internal static bool TryCreateFileStream(string path, string streamName, FileMode mode, FileAccess access, FileShare share, out FileStream stream) { - if (path == null) - { - throw new ArgumentNullException(nameof(path)); - } + ArgumentNullException.ThrowIfNull(path); - if (streamName == null) - { - throw new ArgumentNullException(nameof(streamName)); - } + ArgumentNullException.ThrowIfNull(streamName); if (mode == FileMode.Append) { @@ -8752,8 +8280,9 @@ internal static bool TryCreateFileStream(string path, string streamName, FileMod /// The name of the alternate data stream to delete. internal static void DeleteFileStream(string path, string streamName) { - if (path == null) throw new ArgumentNullException(nameof(path)); - if (streamName == null) throw new ArgumentNullException(nameof(streamName)); + ArgumentNullException.ThrowIfNull(path); + + ArgumentNullException.ThrowIfNull(streamName); string adjustedStreamName = streamName.Trim(); if (adjustedStreamName.IndexOf(':') != 0) @@ -8780,15 +8309,15 @@ internal static void SetZoneOfOrigin(string path, SecurityZone securityZone) // the code above seems cleaner and more robust than the IAttachmentExecute approach } - internal static class NativeMethods + internal static partial class NativeMethods { internal const int ERROR_HANDLE_EOF = 38; internal const int ERROR_INVALID_PARAMETER = 87; internal enum StreamInfoLevels { FindStreamInfoStandard = 0 } - [DllImport(PinvokeDllNames.CreateFileDllName, CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern SafeFileHandle CreateFile(string lpFileName, + [LibraryImport(PinvokeDllNames.CreateFileDllName, EntryPoint = "CreateFileW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + internal static partial SafeFileHandle CreateFile(string lpFileName, FileAccess dwDesiredAccess, FileShare dwShareMode, IntPtr lpSecurityAttributes, FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile); @@ -8809,7 +8338,7 @@ internal static extern bool FindNextStreamW( AlternateStreamNativeData lpFindStreamData); } - internal sealed class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid + internal sealed partial class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid { private SafeFindHandle() : base(true) { } @@ -8818,9 +8347,9 @@ protected override bool ReleaseHandle() return FindClose(this.handle); } - [DllImport(PinvokeDllNames.FindCloseDllName)] + [LibraryImport(PinvokeDllNames.FindCloseDllName)] [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool FindClose(IntPtr handle); + private static partial bool FindClose(IntPtr handle); } /// @@ -9211,7 +8740,7 @@ function PSRemoteDestinationPathIsFile # Return a hash table in the following format: # DirectoryPath is the directory to be created. - # PathExists is a bool to to keep track of whether the directory already exist. + # PathExists is a bool to keep track of whether the directory already exist. # # 1) If DirectoryPath already exists: # a) If -Force is specified, force create the directory. Set DirectoryPath to the created directory path. diff --git a/src/System.Management.Automation/namespaces/FileSystemSecurity.cs b/src/System.Management.Automation/namespaces/FileSystemSecurity.cs index 97ab38158ae..9af94c2d604 100644 --- a/src/System.Management.Automation/namespaces/FileSystemSecurity.cs +++ b/src/System.Management.Automation/namespaces/FileSystemSecurity.cs @@ -141,7 +141,7 @@ public void SetSecurityDescriptor( // the solution is to: // // - First attempt to copy the entire security descriptor as we did in V1. - // This ensures backward compatability for administrator scripts that currently + // This ensures backward compatibility for administrator scripts that currently // work. // - If the attempt fails due to a PrivilegeNotHeld exception, try again with // an estimate of the minimum required subset. This is an estimate, since the @@ -168,13 +168,15 @@ public void SetSecurityDescriptor( { // Get the security descriptor of the destination path ObjectSecurity existingDescriptor = new FileInfo(path).GetAccessControl(); - Type ntAccountType = typeof(System.Security.Principal.NTAccount); + // Use SecurityIdentifier to avoid having the below comparison steps + // fail when dealing with an untranslatable SID in the SD + Type identityType = typeof(System.Security.Principal.SecurityIdentifier); AccessControlSections sections = AccessControlSections.All; // If they didn't modify any audit information, don't try to set // the audit section. - int auditRuleCount = sd.GetAuditRules(true, true, ntAccountType).Count; + int auditRuleCount = sd.GetAuditRules(true, true, identityType).Count; if ((auditRuleCount == 0) && (sd.AreAuditRulesProtected == existingDescriptor.AreAccessRulesProtected)) { @@ -182,13 +184,13 @@ public void SetSecurityDescriptor( } // If they didn't modify the owner, don't try to set that section. - if (sd.GetOwner(ntAccountType) == existingDescriptor.GetOwner(ntAccountType)) + if (sd.GetOwner(identityType) == existingDescriptor.GetOwner(identityType)) { sections &= ~AccessControlSections.Owner; } // If they didn't modify the group, don't try to set that section. - if (sd.GetGroup(ntAccountType) == existingDescriptor.GetGroup(ntAccountType)) + if (sd.GetGroup(identityType) == existingDescriptor.GetGroup(identityType)) { sections &= ~AccessControlSections.Group; } @@ -222,7 +224,7 @@ private void SetSecurityDescriptor(string path, ObjectSecurity sd, AccessControl // Transfer it to the new file / directory. // We keep these two code branches so that we can have more - // granular information when we ouput the object type via + // granular information when we output the object type via // WriteSecurityDescriptorObject. if (Directory.Exists(path)) { diff --git a/src/System.Management.Automation/namespaces/IContentReader.cs b/src/System.Management.Automation/namespaces/IContentReader.cs index 233d89b9b40..6046fee73ed 100644 --- a/src/System.Management.Automation/namespaces/IContentReader.cs +++ b/src/System.Management.Automation/namespaces/IContentReader.cs @@ -4,6 +4,7 @@ using System.Collections; using System.IO; +#nullable enable namespace System.Management.Automation.Provider { #region IContentReader diff --git a/src/System.Management.Automation/namespaces/IPermissionProvider.cs b/src/System.Management.Automation/namespaces/IPermissionProvider.cs index 13b41261a19..c4f548e08a7 100644 --- a/src/System.Management.Automation/namespaces/IPermissionProvider.cs +++ b/src/System.Management.Automation/namespaces/IPermissionProvider.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable using System.Security.AccessControl; namespace System.Management.Automation.Provider diff --git a/src/System.Management.Automation/namespaces/ItemProviderBase.cs b/src/System.Management.Automation/namespaces/ItemProviderBase.cs index eff6782f713..e282093de49 100644 --- a/src/System.Management.Automation/namespaces/ItemProviderBase.cs +++ b/src/System.Management.Automation/namespaces/ItemProviderBase.cs @@ -8,15 +8,15 @@ namespace System.Management.Automation.Provider #region ItemCmdletProvider /// - /// The base class for Cmdlet providers that expose an item as an MSH path. + /// The base class for Cmdlet providers that expose an item as a PowerShell path. /// /// /// The ItemCmdletProvider class is a base class that a provider derives from to - /// inherit a set of methods that allows the Monad engine + /// inherit a set of methods that allows the PowerShell engine /// to provide a core set of commands for getting and setting of data on one or /// more items. A provider should derive from this class if they want /// to take advantage of the item core commands that are - /// already implemented by the Monad engine. This allows users to have common + /// already implemented by the engine. This allows users to have common /// commands and semantics across multiple providers. /// public abstract class ItemCmdletProvider : DriveCmdletProvider diff --git a/src/System.Management.Automation/namespaces/LocationGlobber.cs b/src/System.Management.Automation/namespaces/LocationGlobber.cs index e4ab1f3cfc5..19377985c81 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. /// - [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 /// /// User level tracing for path resolution. /// - [Dbg.TraceSourceAttribute( + [Dbg.TraceSource( "PathResolution", "Traces the path resolution algorithm.")] private static readonly Dbg.PSTraceSource s_pathResolutionTracer = @@ -1967,7 +1967,7 @@ CmdletProviderContext context string driveRoot = drive.Root.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator); driveRoot = driveRoot.TrimEnd(StringLiterals.DefaultPathSeparator); - // Keep on lopping off children until the the remaining path + // Keep on lopping off children until the remaining path // is the drive root. while ((!string.IsNullOrEmpty(providerPath)) && (!providerPath.Equals(driveRoot, StringComparison.OrdinalIgnoreCase))) @@ -2065,7 +2065,10 @@ internal string GenerateRelativePath( driveRootRelativeWorkingPath = driveRootRelativeWorkingPath.Substring(drive.Root.Length); } - if (escapeCurrentLocation) { driveRootRelativeWorkingPath = WildcardPattern.Escape(driveRootRelativeWorkingPath); } + if (escapeCurrentLocation) + { + driveRootRelativeWorkingPath = WildcardPattern.Escape(driveRootRelativeWorkingPath); + } // These are static strings that we will parse and // interpret if they are leading the path. Otherwise @@ -4704,7 +4707,7 @@ private static void TraceFilters(CmdletProviderContext context) StringBuilder includeString = new StringBuilder(); foreach (string includeFilter in context.Include) { - includeString.AppendFormat("{0} ", includeFilter); + includeString.Append($"{includeFilter} "); } s_pathResolutionTracer.WriteLine("Include: {0}", includeString.ToString()); @@ -4716,7 +4719,7 @@ private static void TraceFilters(CmdletProviderContext context) StringBuilder excludeString = new StringBuilder(); foreach (string excludeFilter in context.Exclude) { - excludeString.AppendFormat("{0} ", excludeFilter); + excludeString.Append($"{excludeFilter} "); } s_pathResolutionTracer.WriteLine("Exclude: {0}", excludeString.ToString()); diff --git a/src/System.Management.Automation/namespaces/NavigationProviderBase.cs b/src/System.Management.Automation/namespaces/NavigationProviderBase.cs index da8a16869d1..cf656e633a3 100644 --- a/src/System.Management.Automation/namespaces/NavigationProviderBase.cs +++ b/src/System.Management.Automation/namespaces/NavigationProviderBase.cs @@ -515,10 +515,7 @@ internal string ContractRelativePath( return string.Empty; } - if (basePath == null) - { - basePath = string.Empty; - } + basePath ??= string.Empty; providerBaseTracer.WriteLine("basePath = {0}", basePath); diff --git a/src/System.Management.Automation/namespaces/PathInfo.cs b/src/System.Management.Automation/namespaces/PathInfo.cs index f49d478c5e1..0c5483ceffa 100644 --- a/src/System.Management.Automation/namespaces/PathInfo.cs +++ b/src/System.Management.Automation/namespaces/PathInfo.cs @@ -79,7 +79,7 @@ public string ProviderPath private readonly SessionState _sessionState; /// - /// Gets the MSH path that this object represents. + /// Gets the PowerShell path that this object represents. /// public string Path { @@ -94,10 +94,10 @@ public string Path private readonly string _path = string.Empty; /// - /// Gets a string representing the MSH path. + /// Gets a string representing the PowerShell path. /// /// - /// A string representing the MSH path. + /// A string representing the PowerShell path. /// public override string ToString() { diff --git a/src/System.Management.Automation/namespaces/ProviderBase.cs b/src/System.Management.Automation/namespaces/ProviderBase.cs index 4ffc26db093..ca4cb1de64c 100644 --- a/src/System.Management.Automation/namespaces/ProviderBase.cs +++ b/src/System.Management.Automation/namespaces/ProviderBase.cs @@ -15,10 +15,12 @@ namespace System.Management.Automation.Provider { + /// /// This interface needs to be implemented by providers that want users to see /// provider-specific help. /// +#nullable enable public interface ICmdletProviderSupportsHelp { /// @@ -37,7 +39,7 @@ public interface ICmdletProviderSupportsHelp [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Maml", Justification = "Maml is an acronym.")] string GetHelpMaml(string helpItemName, string path); } - +#nullable restore #region CmdletProvider /// @@ -74,7 +76,7 @@ public abstract partial class CmdletProvider : IResourceSupplier /// An instance of the PSTraceSource class used for trace output /// using "CmdletProviderClasses" as the category. /// - [TraceSourceAttribute( + [TraceSource( "CmdletProviderClasses", "The namespace provider base classes tracer")] internal static readonly PSTraceSource providerBaseTracer = PSTraceSource.GetTracer( @@ -221,7 +223,7 @@ internal object StartDynamicParameters(CmdletProviderContext cmdletProviderConte /// /// /// The context under which this method is being called. - /// + /// internal void Stop(CmdletProviderContext cmdletProviderContext) { Context = cmdletProviderContext; @@ -1350,7 +1352,7 @@ public PSHost Host /// public virtual char AltItemSeparator => #if UNIX - Utils.Separators.Backslash[0]; + '\\'; #else Path.AltDirectorySeparatorChar; #endif @@ -1417,6 +1419,7 @@ public virtual string GetResourceString(string baseName, string resourceId) #region ThrowTerminatingError /// + [System.Diagnostics.CodeAnalysis.DoesNotReturn] public void ThrowTerminatingError(ErrorRecord errorRecord) { using (PSTransactionManager.GetEngineProtectionScope()) @@ -1820,11 +1823,11 @@ private PSObject WrapOutputInPSObject( #if UNIX // Add a commonstat structure to file system objects - if (ExperimentalFeature.IsEnabled("PSUnixFileStat") && ProviderInfo.ImplementingType == typeof(Microsoft.PowerShell.Commands.FileSystemProvider)) + if (ProviderInfo.ImplementingType == typeof(Microsoft.PowerShell.Commands.FileSystemProvider)) { try { - // Use LStat because if you get a link, you want the information about the + // Use LStat because if you get a link, you want the information about the // link, not the file. var commonStat = Platform.Unix.GetLStat(path); result.AddOrSetProperty("UnixStat", commonStat); diff --git a/src/System.Management.Automation/namespaces/ProviderBaseSecurity.cs b/src/System.Management.Automation/namespaces/ProviderBaseSecurity.cs index c232b769fb3..7bc738c4e49 100644 --- a/src/System.Management.Automation/namespaces/ProviderBaseSecurity.cs +++ b/src/System.Management.Automation/namespaces/ProviderBaseSecurity.cs @@ -7,7 +7,7 @@ namespace System.Management.Automation.Provider { /// /// Defines the base class for all of the classes the provide implementations for a particular - /// data store or item for the MSH core commands. + /// data store or item for the PowerShell core commands. /// public abstract partial class CmdletProvider { diff --git a/src/System.Management.Automation/namespaces/ProviderDeclarationAttribute.cs b/src/System.Management.Automation/namespaces/ProviderDeclarationAttribute.cs index d0c7bd018a1..cd38b92c177 100644 --- a/src/System.Management.Automation/namespaces/ProviderDeclarationAttribute.cs +++ b/src/System.Management.Automation/namespaces/ProviderDeclarationAttribute.cs @@ -77,55 +77,63 @@ public enum ProviderCapabilities { /// /// The provider does not add any additional capabilities beyond what the - /// Monad engine provides. + /// PowerShell engine provides. /// None = 0x0, /// + /// /// The provider does the inclusion filtering for those commands that take an Include - /// parameter. The Monad engine should not try to do the filtering on behalf of this + /// parameter. The PowerShell engine should not try to do the filtering on behalf of this /// provider. - /// - /// - /// Note, the provider should make every effort to filter in a way that is consistent - /// with the Monad engine. This option is allowed because in many cases the provider + /// + /// + /// The implementer of the provider should make every effort to filter in a way that is consistent + /// with the PowerShell engine. This option is allowed because in many cases the provider /// can be much more efficient at filtering. - /// + /// + /// Include = 0x1, /// + /// /// The provider does the exclusion filtering for those commands that take an Exclude - /// parameter. The Monad engine should not try to do the filtering on behalf of this + /// parameter. The PowerShell engine should not try to do the filtering on behalf of this /// provider. - /// - /// - /// Note, the provider should make every effort to filter in a way that is consistent - /// with the Monad engine. This option is allowed because in many cases the provider + /// + /// + /// The implementer of the provider should make every effort to filter in a way that is consistent + /// with the PowerShell engine. This option is allowed because in many cases the provider /// can be much more efficient at filtering. - /// + /// + /// Exclude = 0x2, /// + /// /// The provider can take a provider specific filter string. - /// - /// - /// When this attribute is specified a provider specific filter can be passed from + /// + /// + /// For implementers of providers using this attribute, a provider specific filter can be passed from /// the Core Commands to the provider. This filter string is not interpreted in any - /// way by the Monad engine. - /// + /// way by the PowerShell engine. + /// + /// Filter = 0x4, /// - /// The provider does the wildcard matching for those commands that allow for it. The Monad + /// + /// The provider does the wildcard matching for those commands that allow for it. The PowerShell /// engine should not try to do the wildcard matching on behalf of the provider when this /// flag is set. - /// - /// - /// Note, the provider should make every effort to do the wildcard matching in a way that is consistent - /// with the Monad engine. This option is allowed because in many cases wildcard matching + /// + /// + /// The implementer of the provider should make every effort to do the wildcard matching in a way that is consistent + /// with the PowerShell engine. This option is allowed because in many cases wildcard matching /// cannot occur via the path name or because the provider can do the matching in a much more /// efficient manner. - /// + /// + /// ExpandWildcards = 0x8, /// diff --git a/src/System.Management.Automation/namespaces/RegistryProvider.cs b/src/System.Management.Automation/namespaces/RegistryProvider.cs index 1898c960620..db901160910 100644 --- a/src/System.Management.Automation/namespaces/RegistryProvider.cs +++ b/src/System.Management.Automation/namespaces/RegistryProvider.cs @@ -28,7 +28,7 @@ namespace Microsoft.PowerShell.Commands /// /// INSTALLATION: /// - /// Type the following at an msh prompt: + /// Type the following at a PowerShell prompt: /// /// new-PSProvider -Path "REG.cmdletprovider" -description "My registry navigation provider" /// @@ -61,6 +61,9 @@ namespace Microsoft.PowerShell.Commands [OutputType(typeof(RegistryKey), ProviderCmdlet = ProviderCmdlet.GetItem)] [OutputType(typeof(RegistryKey), typeof(string), typeof(Int32), typeof(Int64), ProviderCmdlet = ProviderCmdlet.GetItemProperty)] [OutputType(typeof(RegistryKey), ProviderCmdlet = ProviderCmdlet.NewItem)] + [OutputType(typeof(string), typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.ResolvePath)] + [OutputType(typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.PushLocation)] + [OutputType(typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.PopLocation)] public sealed partial class RegistryProvider : NavigationCmdletProvider, IPropertyCmdletProvider, @@ -783,7 +786,7 @@ private static string EscapeSpecialChars(string path) Dbg.Diagnostics.Assert( textEnumerator != null, - string.Format(CultureInfo.CurrentCulture, "Cannot get a text enumerator for name {0}", path)); + string.Create(CultureInfo.CurrentCulture, $"Cannot get a text enumerator for name {path}")); while (textEnumerator.MoveNext()) { @@ -798,7 +801,7 @@ private static string EscapeSpecialChars(string path) // should not be done. if (textElement.Contains(charactersThatNeedEscaping)) { - // This text element needs espacing + // This text element needs escaping result.Append('`'); } @@ -832,7 +835,7 @@ private static string EscapeChildName(string name) Dbg.Diagnostics.Assert( textEnumerator != null, - string.Format(CultureInfo.CurrentCulture, "Cannot get a text enumerator for name {0}", name)); + string.Create(CultureInfo.CurrentCulture, $"Cannot get a text enumerator for name {name}")); while (textEnumerator.MoveNext()) { @@ -847,7 +850,7 @@ private static string EscapeChildName(string name) // should not be done. if (textElement.Contains(charactersThatNeedEscaping)) { - // This text element needs espacing + // This text element needs escaping result.Append('`'); } @@ -1822,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(); @@ -2993,10 +3007,7 @@ private void GetFilteredRegistryKeyProperties(string path, // If properties were not specified, get all the values - if (propertyNames == null) - { - propertyNames = new Collection(); - } + propertyNames ??= new Collection(); if (propertyNames.Count == 0 && getAll) { diff --git a/src/System.Management.Automation/namespaces/RegistryWrapper.cs b/src/System.Management.Automation/namespaces/RegistryWrapper.cs index 0e8e0558312..ebdbe833839 100644 --- a/src/System.Management.Automation/namespaces/RegistryWrapper.cs +++ b/src/System.Management.Automation/namespaces/RegistryWrapper.cs @@ -17,11 +17,13 @@ namespace Microsoft.PowerShell.Commands { + +#nullable enable internal interface IRegistryWrapper { - void SetValue(string name, object value); + void SetValue(string? name, object value); - void SetValue(string name, object value, RegistryValueKind valueKind); + void SetValue(string? name, object value, RegistryValueKind valueKind); string[] GetValueNames(); @@ -29,17 +31,17 @@ internal interface IRegistryWrapper string[] GetSubKeyNames(); - IRegistryWrapper CreateSubKey(string subkey); + IRegistryWrapper? CreateSubKey(string subkey); - IRegistryWrapper OpenSubKey(string name, bool writable); + IRegistryWrapper? OpenSubKey(string name, bool writable); void DeleteSubKeyTree(string subkey); - object GetValue(string name); + object? GetValue(string? name); - object GetValue(string name, object defaultValue, RegistryValueOptions options); + object? GetValue(string? name, object? defaultValue, RegistryValueOptions options); - RegistryValueKind GetValueKind(string name); + RegistryValueKind GetValueKind(string? name); object RegistryKey { get; } @@ -53,6 +55,7 @@ internal interface IRegistryWrapper int SubKeyCount { get; } } +#nullable restore internal static class RegistryWrapperUtils { diff --git a/src/System.Management.Automation/namespaces/SafeRegistryHandle.cs b/src/System.Management.Automation/namespaces/SafeRegistryHandle.cs deleted file mode 100644 index 5e76b7cb3f3..00000000000 --- a/src/System.Management.Automation/namespaces/SafeRegistryHandle.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -// -// NOTE: A vast majority of this code was copied from BCL in -// Namespace: Microsoft.Win32.SafeHandles -// -/*============================================================ -** -** -** -** A wrapper for registry handles -** -** -===========================================================*/ - -using System; -using System.Management.Automation; -using System.Security; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; -using Microsoft.Win32.SafeHandles; -using System.Runtime.ConstrainedExecution; -using System.Security.Permissions; - -namespace Microsoft.PowerShell.Commands.Internal -{ - internal sealed class SafeRegistryHandle : SafeHandleZeroOrMinusOneIsInvalid - { - // Note: Officially -1 is the recommended invalid handle value for - // registry keys, but we'll also get back 0 as an invalid handle from - // RegOpenKeyEx. - - internal SafeRegistryHandle() : base(true) { } - - internal SafeRegistryHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHandle) - { - SetHandle(preexistingHandle); - } - - [DllImport(PinvokeDllNames.RegCloseKeyDllName), - SuppressUnmanagedCodeSecurity, - ResourceExposure(ResourceScope.None)] - internal static extern int RegCloseKey(IntPtr hKey); - - protected override bool ReleaseHandle() - { - // Returns a Win32 error code, 0 for success - int r = RegCloseKey(handle); - return r == 0; - } - } -} 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 /// /// An instance of the PSTraceSource class used for trace output. /// - [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/TransactedRegistryKey.cs b/src/System.Management.Automation/namespaces/TransactedRegistryKey.cs index dac6f6c0897..23316a31724 100644 --- a/src/System.Management.Automation/namespaces/TransactedRegistryKey.cs +++ b/src/System.Management.Automation/namespaces/TransactedRegistryKey.cs @@ -341,11 +341,11 @@ public void Dispose() /// /// Creates a new subkey, or opens an existing one. /// Utilizes Transaction.Current for its transaction. + /// /// Name or path to subkey to create or open. Cannot be null or an empty string, /// otherwise an ArgumentException is thrown. /// A TransactedRegistryKey object for the subkey, which is associated with Transaction.Current. /// returns null if the operation failed. - /// [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey @@ -358,13 +358,13 @@ public TransactedRegistryKey CreateSubKey(string subkey) /// /// Creates a new subkey, or opens an existing one. /// Utilizes Transaction.Current for its transaction. + /// + /// A TransactedRegistryKey object for the subkey, which is associated with Transaction.Current. + /// returns null if the operation failed. /// Name or path to subkey to create or open. Cannot be null or an empty string, /// otherwise an ArgumentException is thrown. /// One of the Microsoft.Win32.RegistryKeyPermissionCheck values that /// specifies whether the key is opened for read or read/write access. - /// A TransactedRegistryKey object for the subkey, which is associated with Transaction.Current. - /// returns null if the operation failed. - /// [ComVisible(false)] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] @@ -378,14 +378,14 @@ public TransactedRegistryKey CreateSubKey(string subkey, RegistryKeyPermissionCh /// /// Creates a new subkey, or opens an existing one. /// Utilizes Transaction.Current for its transaction. + /// + /// A TransactedRegistryKey object for the subkey, which is associated with Transaction.Current. + /// returns null if the operation failed. /// Name or path to subkey to create or open. Cannot be null or an empty string, /// otherwise an ArgumentException is thrown. /// One of the Microsoft.Win32.RegistryKeyPermissionCheck values that /// specifies whether the key is opened for read or read/write access. /// A TransactedRegistrySecurity object that specifies the access control security for the new key. - /// A TransactedRegistryKey object for the subkey, which is associated with Transaction.Current. - /// returns null if the operation failed. - /// [ComVisible(false)] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] @@ -486,9 +486,9 @@ private unsafe TransactedRegistryKey CreateSubKeyInternal(string subkey, Registr /// Deletes the specified subkey. Will throw an exception if the subkey has /// subkeys. To delete a tree of subkeys use, DeleteSubKeyTree. /// Utilizes Transaction.Current for its transaction. - /// The subkey to delete. /// Thrown if the subkey as child subkeys. /// + /// The subkey to delete. [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey @@ -502,14 +502,14 @@ public void DeleteSubKey(string subkey) /// Deletes the specified subkey. Will throw an exception if the subkey has /// subkeys. To delete a tree of subkeys use, DeleteSubKeyTree. /// Utilizes Transaction.Current for its transaction. - /// The subkey to delete. - /// 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. /// Thrown if the subkey as child subkeys. /// Thrown if true is specified for throwOnMissingSubKey and the /// specified subkey does not exist. /// + /// The subkey to delete. + /// 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. [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey @@ -567,8 +567,8 @@ public void DeleteSubKey(string subkey, bool throwOnMissingSubKey) /// /// Recursively deletes a subkey and any child subkeys. /// Utilizes Transaction.Current for its transaction. - /// The subkey to delete. /// + /// The subkey to delete. [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey @@ -613,7 +613,10 @@ public void DeleteSubKeyTree(string subkey) } ret = Win32Native.RegDeleteKeyTransacted(_hkey, subkey, 0, 0, safeTransactionHandle, IntPtr.Zero); - if (ret != 0) Win32Error(ret, null); + if (ret != 0) + { + Win32Error(ret, null); + } } else { @@ -653,7 +656,10 @@ private void DeleteSubKeyTreeInternal(string subkey) } ret = Win32Native.RegDeleteKeyTransacted(_hkey, subkey, 0, 0, safeTransactionHandle, IntPtr.Zero); - if (ret != 0) Win32Error(ret, null); + if (ret != 0) + { + Win32Error(ret, null); + } } else { @@ -664,8 +670,8 @@ private void DeleteSubKeyTreeInternal(string subkey) /// /// Deletes the specified value from this key. /// Utilizes Transaction.Current for its transaction. - /// Name of the value to delete. /// + /// Name of the value to delete. [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] public void DeleteValue(string name) @@ -676,11 +682,11 @@ public void DeleteValue(string name) /// /// Deletes the specified value from this key. /// Utilizes Transaction.Current for its transaction. + /// /// Name of the value to delete. /// 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. - /// [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] public void DeleteValue(string name, bool throwOnMissingValue) @@ -748,10 +754,10 @@ internal static TransactedRegistryKey GetBaseKey(IntPtr hKey) /// Retrieves a subkey. If readonly is true, then the subkey is opened with /// read-only access. /// Utilizes Transaction.Current for its transaction. + /// + /// The subkey requested or null if the operation failed. /// Name or path of the subkey to open. /// Set to true of you only need readonly access. - /// The subkey requested or null if the operation failed. - /// [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey @@ -791,11 +797,11 @@ public TransactedRegistryKey OpenSubKey(string name, bool writable) /// /// Retrieves a subkey. /// Utilizes Transaction.Current for its transaction. + /// + /// The subkey requested or null if the operation failed. /// Name or path of the subkey to open. /// One of the Microsoft.Win32.RegistryKeyPermissionCheck values that specifies /// whether the key is opened for read or read/write access. - /// The subkey requested or null if the operation failed. - /// [ComVisible(false)] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] @@ -810,12 +816,12 @@ public TransactedRegistryKey OpenSubKey(string name, RegistryKeyPermissionCheck /// /// Retrieves a subkey. /// Utilizes Transaction.Current for its transaction. + /// + /// The subkey requested or null if the operation failed. /// Name or path of the subkey to open. /// One of the Microsoft.Win32.RegistryKeyPermissionCheck values that specifies /// whether the key is opened for read or read/write access. /// A bitwise combination of Microsoft.Win32.RegistryRights values that specifies the desired security access. - /// The subkey requested or null if the operation failed. - /// [ComVisible(false)] [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] @@ -897,9 +903,9 @@ internal TransactedRegistryKey InternalOpenSubKey(string name, bool writable) /// /// Retrieves a subkey for readonly access. /// Utilizes Transaction.Current for its transaction. - /// Name or path of the subkey to open. - /// The subkey requested or null if the operation failed. /// + /// The subkey requested or null if the operation failed. + /// Name or path of the subkey to open. [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey @@ -912,8 +918,8 @@ public TransactedRegistryKey OpenSubKey(string name) /// /// Retrieves the count of subkeys. /// Utilizes Transaction.Current for its transaction. - /// The count of subkeys. /// + /// The count of subkeys. // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public int SubKeyCount @@ -955,8 +961,8 @@ internal int InternalSubKeyCount() /// /// Retrieves an array of strings containing all the subkey names. /// Utilizes Transaction.Current for its transaction. - /// A string array containing all the subkey names. /// + /// A string array containing all the subkey names. // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public string[] GetSubKeyNames() @@ -1002,8 +1008,8 @@ internal string[] InternalGetSubKeyNames() /// /// Retrieves the count of values. /// Utilizes Transaction.Current for its transaction. - /// A count of values. /// + /// A count of values. public int ValueCount { get @@ -1039,8 +1045,8 @@ internal int InternalValueCount() /// /// Retrieves an array of strings containing all the value names. /// Utilizes Transaction.Current for its transaction. - /// All the value names. /// + /// All the value names. public string[] GetValueNames() { CheckKeyReadPermission(); @@ -1107,9 +1113,9 @@ public string[] GetValueNames() /// doesn't exist. Utilizes Transaction.Current for its transaction. /// Note that name can be null or "", at which point the /// unnamed or default value of this Registry key is returned, if any. - /// Name of value to retrieve. - /// The data associated with the value. /// + /// The data associated with the value. + /// Name of value to retrieve. public object GetValue(string name) { CheckValueReadPermission(name); @@ -1121,10 +1127,10 @@ public object GetValue(string name) /// doesn't exist. Utilizes Transaction.Current for its transaction. /// Note that name can be null or "", at which point the /// unnamed or default value of this Registry key is returned, if any. + /// + /// The data associated with the value. /// Name of value to retrieve. /// Value to return if name doesn't exist. - /// The data associated with the value. - /// public object GetValue(string name, object defaultValue) { CheckValueReadPermission(name); @@ -1136,12 +1142,12 @@ public object GetValue(string name, object defaultValue) /// doesn't exist. Utilizes Transaction.Current for its transaction. /// Note that name can be null or "", at which point the /// unnamed or default value of this Registry key is returned, if any. + /// + /// The data associated with the value. /// Name of value to retrieve. /// Value to return if name doesn't exist. /// One of the Microsoft.Win32.RegistryValueOptions values that specifies /// optional processing of the retrieved value. - /// The data associated with the value. - /// [ComVisible(false)] public object GetValue(string name, object defaultValue, RegistryValueOptions options) { @@ -1307,9 +1313,9 @@ internal object InternalGetValue(string name, object defaultValue, bool doNotExp /// /// Retrieves the registry data type of the value associated with the specified name. /// Utilizes Transaction.Current for its transaction. - /// The value name whose data type is to be retrieved. - /// A RegistryValueKind value representing the registry data type of the value associated with name. /// + /// A RegistryValueKind value representing the registry data type of the value associated with name. + /// The value name whose data type is to be retrieved. [ComVisible(false)] public RegistryValueKind GetValueKind(string name) { @@ -1331,7 +1337,7 @@ public RegistryValueKind GetValueKind(string name) /** * Retrieves the current state of the dirty property. * - * A key is marked as dirty if any operation has occured that modifies the + * A key is marked as dirty if any operation has occurred that modifies the * contents of the key. * * @return true if the key has been modified. @@ -1353,8 +1359,8 @@ private bool IsWritable() /// /// Retrieves the name of the key. - /// The name of the key. /// + /// The name of the key. public string Name { get @@ -1371,9 +1377,9 @@ private void SetDirty() /// /// Sets the specified value. Utilizes Transaction.Current for its transaction. + /// /// Name of value to store data in. /// Data to store. - /// public void SetValue(string name, object value) { SetValue(name, value, RegistryValueKind.Unknown); @@ -1381,15 +1387,14 @@ public void SetValue(string name, object value) /// /// Sets the specified value. Utilizes Transaction.Current for its transaction. + /// /// Name of value to store data in. /// Data to store. /// The registry data type to use when storing the data. - /// [ComVisible(false)] public unsafe void SetValue(string name, object value, RegistryValueKind valueKind) { - if (value == null) - throw new ArgumentNullException(RegistryProviderStrings.Arg_Value); + ArgumentNullException.ThrowIfNull(value, RegistryProviderStrings.Arg_Value); if (name != null && name.Length > MaxValueNameLength) { @@ -1599,8 +1604,8 @@ private RegistryValueKind CalculateValueKind(object value) */ /// /// Retrieves a string representation of this key. - /// A string representing the key. /// + /// A string representing the key. public override string ToString() { EnsureNotDisposed(); @@ -1610,9 +1615,9 @@ public override string ToString() /// /// Returns the access control security for the current registry key. /// Utilizes Transaction.Current for its transaction. + /// /// A TransactedRegistrySecurity object that describes the access control /// permissions on the registry key represented by the current TransactedRegistryKey. - /// public TransactedRegistrySecurity GetAccessControl() { return GetAccessControl(AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group); @@ -1621,10 +1626,10 @@ public TransactedRegistrySecurity GetAccessControl() /// /// Returns the access control security for the current registry key. /// Utilizes Transaction.Current for its transaction. - /// A bitwise combination of AccessControlSections values that specifies the type of security information to get. + /// /// A TransactedRegistrySecurity object that describes the access control /// permissions on the registry key represented by the current TransactedRegistryKey. - /// + /// A bitwise combination of AccessControlSections values that specifies the type of security information to get. public TransactedRegistrySecurity GetAccessControl(AccessControlSections includeSections) { EnsureNotDisposed(); @@ -1635,13 +1640,12 @@ public TransactedRegistrySecurity GetAccessControl(AccessControlSections include /// /// Applies Windows access control security to an existing registry key. /// Utilizes Transaction.Current for its transaction. - /// A TransactedRegistrySecurity object that specifies the access control security to apply to the current subkey. /// + /// A TransactedRegistrySecurity object that specifies the access control security to apply to the current subkey. public void SetAccessControl(TransactedRegistrySecurity registrySecurity) { EnsureWriteable(); - if (registrySecurity == null) - throw new ArgumentNullException("registrySecurity"); + ArgumentNullException.ThrowIfNull(registrySecurity); // Require a transaction. This will throw for "Base" keys because they aren't associated with a transaction. VerifyTransaction(); @@ -2032,10 +2036,7 @@ private RegistryKeyPermissionCheck GetSubKeyPermissionCheck(bool subkeyWritable) private static void ValidateKeyName(string name) { - if (name == null) - { - throw new ArgumentNullException(RegistryProviderStrings.Arg_Name); - } + ArgumentNullException.ThrowIfNull(name, RegistryProviderStrings.Arg_Name); int nextSlash = name.IndexOf('\\'); int current = 0; diff --git a/src/System.Management.Automation/namespaces/TransactedRegistrySecurity.cs b/src/System.Management.Automation/namespaces/TransactedRegistrySecurity.cs index 46070ea27c8..8bcf8d76880 100644 --- a/src/System.Management.Automation/namespaces/TransactedRegistrySecurity.cs +++ b/src/System.Management.Automation/namespaces/TransactedRegistrySecurity.cs @@ -37,11 +37,11 @@ public sealed class TransactedRegistryAccessRule : AccessRule /// /// Initializes a new instance of the RegistryAccessRule class, specifying the user or group the rule applies to, /// the access rights, and whether the specified access rights are allowed or denied. + /// /// The user or group the rule applies to. Must be of type SecurityIdentifier or a type such as /// NTAccount that can be converted to type SecurityIdentifier. /// A bitwise combination of Microsoft.Win32.RegistryRights values indicating the rights allowed or denied. /// One of the AccessControlType values indicating whether the rights are allowed or denied. - /// internal TransactedRegistryAccessRule(IdentityReference identity, RegistryRights registryRights, AccessControlType type) : this(identity, (int)registryRights, false, InheritanceFlags.None, PropagationFlags.None, type) { @@ -50,10 +50,10 @@ internal TransactedRegistryAccessRule(IdentityReference identity, RegistryRights /// /// Initializes a new instance of the RegistryAccessRule class, specifying the user or group the rule applies to, /// the access rights, and whether the specified access rights are allowed or denied. + /// /// The name of the user or group the rule applies to. /// A bitwise combination of Microsoft.Win32.RegistryRights values indicating the rights allowed or denied. /// One of the AccessControlType values indicating whether the rights are allowed or denied. - /// internal TransactedRegistryAccessRule(string identity, RegistryRights registryRights, AccessControlType type) : this(new NTAccount(identity), (int)registryRights, false, InheritanceFlags.None, PropagationFlags.None, type) { @@ -62,13 +62,13 @@ internal TransactedRegistryAccessRule(string identity, RegistryRights registryRi /// /// Initializes a new instance of the RegistryAccessRule class, specifying the user or group the rule applies to, /// the access rights, and whether the specified access rights are allowed or denied. + /// /// The user or group the rule applies to. Must be of type SecurityIdentifier or a type such as /// NTAccount that can be converted to type SecurityIdentifier. /// A bitwise combination of Microsoft.Win32.RegistryRights values indicating the rights allowed or denied. /// A bitwise combination of InheritanceFlags flags specifying how access rights are inherited from other objects. /// A bitwise combination of PropagationFlags flags specifying how access rights are propagated to other objects. /// One of the AccessControlType values indicating whether the rights are allowed or denied. - /// public TransactedRegistryAccessRule(IdentityReference identity, RegistryRights registryRights, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type) : this(identity, (int)registryRights, false, inheritanceFlags, propagationFlags, type) { @@ -77,12 +77,12 @@ public TransactedRegistryAccessRule(IdentityReference identity, RegistryRights r /// /// Initializes a new instance of the RegistryAccessRule class, specifying the user or group the rule applies to, /// the access rights, and whether the specified access rights are allowed or denied. + /// /// The name of the user or group the rule applies to. /// A bitwise combination of Microsoft.Win32.RegistryRights values indicating the rights allowed or denied. /// A bitwise combination of InheritanceFlags flags specifying how access rights are inherited from other objects. /// A bitwise combination of PropagationFlags flags specifying how access rights are propagated to other objects. /// One of the AccessControlType values indicating whether the rights are allowed or denied. - /// internal TransactedRegistryAccessRule(string identity, RegistryRights registryRights, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type) : this(new NTAccount(identity), (int)registryRights, false, inheritanceFlags, propagationFlags, type) { @@ -128,13 +128,13 @@ public sealed class TransactedRegistryAuditRule : AuditRule /// /// Initializes a new instance of the RegistryAuditRule class, specifying the user or group to audit, the rights to /// audit, whether to take inheritance into account, and whether to audit success, failure, or both. + /// /// The user or group the rule applies to. Must be of type SecurityIdentifier or a type such as /// NTAccount that can be converted to type SecurityIdentifier. /// A bitwise combination of RegistryRights values specifying the kinds of access to audit. /// A bitwise combination of InheritanceFlags values specifying whether the audit rule applies to subkeys of the current key. /// A bitwise combination of PropagationFlags values that affect the way an inherited audit rule is propagated to subkeys of the current key. /// A bitwise combination of AuditFlags values specifying whether to audit success, failure, or both. - /// internal TransactedRegistryAuditRule(IdentityReference identity, RegistryRights registryRights, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AuditFlags flags) : this(identity, (int)registryRights, false, inheritanceFlags, propagationFlags, flags) { @@ -143,12 +143,12 @@ internal TransactedRegistryAuditRule(IdentityReference identity, RegistryRights /// /// Initializes a new instance of the RegistryAuditRule class, specifying the user or group to audit, the rights to /// audit, whether to take inheritance into account, and whether to audit success, failure, or both. + /// /// The name of the user or group the rule applies to. /// A bitwise combination of RegistryRights values specifying the kinds of access to audit. /// A bitwise combination of InheritanceFlags values specifying whether the audit rule applies to subkeys of the current key. /// A bitwise combination of PropagationFlags values that affect the way an inherited audit rule is propagated to subkeys of the current key. /// A bitwise combination of AuditFlags values specifying whether to audit success, failure, or both. - /// internal TransactedRegistryAuditRule(string identity, RegistryRights registryRights, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AuditFlags flags) : this(new NTAccount(identity), (int)registryRights, false, inheritanceFlags, propagationFlags, flags) { @@ -232,14 +232,14 @@ private static Exception _HandleErrorCode(int errorCode, string name, SafeHandle /// /// Creates a new access control rule for the specified user, with the specified access rights, access control, and flags. + /// + /// A TransactedRegistryAccessRule object representing the specified rights for the specified user. /// An IdentityReference that identifies the user or group the rule applies to. /// A bitwise combination of RegistryRights values specifying the access rights to allow or deny, cast to an integer. /// A Boolean value specifying whether the rule is inherited. /// A bitwise combination of InheritanceFlags values specifying how the rule is inherited by subkeys. /// A bitwise combination of PropagationFlags values that modify the way the rule is inherited by subkeys. Meaningless if the value of inheritanceFlags is InheritanceFlags.None. /// One of the AccessControlType values specifying whether the rights are allowed or denied. - /// A TransactedRegistryAccessRule object representing the specified rights for the specified user. - /// public override AccessRule AccessRuleFactory(IdentityReference identityReference, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type) { return new TransactedRegistryAccessRule(identityReference, accessMask, isInherited, inheritanceFlags, propagationFlags, type); @@ -248,15 +248,15 @@ public override AccessRule AccessRuleFactory(IdentityReference identityReference /// /// Creates a new audit rule, specifying the user the rule applies to, the access rights to audit, the inheritance and propagation of the /// rule, and the outcome that triggers the rule. + /// + /// A TransactedRegistryAuditRule object representing the specified audit rule for the specified user, with the specified flags. + /// The return type of the method is the base class, AuditRule, but the return value can be cast safely to the derived class. /// An IdentityReference that identifies the user or group the rule applies to. /// A bitwise combination of RegistryRights values specifying the access rights to audit, cast to an integer. /// A Boolean value specifying whether the rule is inherited. /// A bitwise combination of InheritanceFlags values specifying how the rule is inherited by subkeys. /// A bitwise combination of PropagationFlags values that modify the way the rule is inherited by subkeys. Meaningless if the value of inheritanceFlags is InheritanceFlags.None. /// A bitwise combination of AuditFlags values specifying whether to audit successful access, failed access, or both. - /// A TransactedRegistryAuditRule object representing the specified audit rule for the specified user, with the specified flags. - /// The return type of the method is the base class, AuditRule, but the return value can be cast safely to the derived class. - /// public override AuditRule AuditRuleFactory(IdentityReference identityReference, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AuditFlags flags) { return new TransactedRegistryAuditRule(identityReference, accessMask, isInherited, inheritanceFlags, propagationFlags, flags); @@ -301,8 +301,8 @@ internal void Persist(SafeRegistryHandle hKey, string keyName) /// /// Searches for a matching access control with which the new rule can be merged. If none are found, adds the new rule. - /// The access control rule to add. /// + /// The access control rule to add. // Suppressed because we want to ensure TransactedRegistry* objects. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] public void AddAccessRule(TransactedRegistryAccessRule rule) @@ -312,8 +312,8 @@ public void AddAccessRule(TransactedRegistryAccessRule rule) /// /// Removes all access control rules with the same user and AccessControlType (allow or deny) as the specified rule, and then adds the specified rule. - /// The TransactedRegistryAccessRule to add. The user and AccessControlType of this rule determine the rules to remove before this rule is added. /// + /// The TransactedRegistryAccessRule to add. The user and AccessControlType of this rule determine the rules to remove before this rule is added. // Suppressed because we want to ensure TransactedRegistry* objects. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] public void SetAccessRule(TransactedRegistryAccessRule rule) @@ -323,8 +323,8 @@ public void SetAccessRule(TransactedRegistryAccessRule rule) /// /// Removes all access control rules with the same user as the specified rule, regardless of AccessControlType, and then adds the specified rule. - /// The TransactedRegistryAccessRule to add. The user specified by this rule determines the rules to remove before this rule is added. /// + /// The TransactedRegistryAccessRule to add. The user specified by this rule determines the rules to remove before this rule is added. // Suppressed because we want to ensure TransactedRegistry* objects. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] public void ResetAccessRule(TransactedRegistryAccessRule rule) @@ -335,9 +335,9 @@ public void ResetAccessRule(TransactedRegistryAccessRule rule) /// /// Searches for an access control rule with the same user and AccessControlType (allow or deny) as the specified access rule, and with compatible /// inheritance and propagation flags; if such a rule is found, the rights contained in the specified access rule are removed from it. + /// /// A TransactedRegistryAccessRule that specifies the user and AccessControlType to search for, and a set of inheritance /// and propagation flags that a matching rule, if found, must be compatible with. Specifies the rights to remove from the compatible rule, if found. - /// // Suppressed because we want to ensure TransactedRegistry* objects. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] public bool RemoveAccessRule(TransactedRegistryAccessRule rule) @@ -347,9 +347,9 @@ public bool RemoveAccessRule(TransactedRegistryAccessRule rule) /// /// Searches for all access control rules with the same user and AccessControlType (allow or deny) as the specified rule and, if found, removes them. + /// /// A TransactedRegistryAccessRule that specifies the user and AccessControlType to search for. Any rights, inheritance flags, or /// propagation flags specified by this rule are ignored. - /// // Suppressed because we want to ensure TransactedRegistry* objects. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] public void RemoveAccessRuleAll(TransactedRegistryAccessRule rule) @@ -359,8 +359,8 @@ public void RemoveAccessRuleAll(TransactedRegistryAccessRule rule) /// /// Searches for an access control rule that exactly matches the specified rule and, if found, removes it. - /// The TransactedRegistryAccessRule to remove. /// + /// The TransactedRegistryAccessRule to remove. // Suppressed because we want to ensure TransactedRegistry* objects. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] public void RemoveAccessRuleSpecific(TransactedRegistryAccessRule rule) @@ -370,8 +370,8 @@ public void RemoveAccessRuleSpecific(TransactedRegistryAccessRule rule) /// /// Searches for an audit rule with which the new rule can be merged. If none are found, adds the new rule. - /// The audit rule to add. The user specified by this rule determines the search. /// + /// The audit rule to add. The user specified by this rule determines the search. // Suppressed because we want to ensure TransactedRegistry* objects. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] public void AddAuditRule(TransactedRegistryAuditRule rule) @@ -381,8 +381,8 @@ public void AddAuditRule(TransactedRegistryAuditRule rule) /// /// Removes all audit rules with the same user as the specified rule, regardless of the AuditFlags value, and then adds the specified rule. - /// The TransactedRegistryAuditRule to add. The user specified by this rule determines the rules to remove before this rule is added. /// + /// The TransactedRegistryAuditRule to add. The user specified by this rule determines the rules to remove before this rule is added. // Suppressed because we want to ensure TransactedRegistry* objects. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] public void SetAuditRule(TransactedRegistryAuditRule rule) @@ -393,9 +393,9 @@ public void SetAuditRule(TransactedRegistryAuditRule rule) /// /// Searches for an audit control rule with the same user as the specified rule, and with compatible inheritance and propagation flags; /// if a compatible rule is found, the rights contained in the specified rule are removed from it. + /// /// A TransactedRegistryAuditRule that specifies the user to search for, and a set of inheritance and propagation flags that /// a matching rule, if found, must be compatible with. Specifies the rights to remove from the compatible rule, if found. - /// // Suppressed because we want to ensure TransactedRegistry* objects. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] public bool RemoveAuditRule(TransactedRegistryAuditRule rule) @@ -405,9 +405,9 @@ public bool RemoveAuditRule(TransactedRegistryAuditRule rule) /// /// Searches for all audit rules with the same user as the specified rule and, if found, removes them. + /// /// A TransactedRegistryAuditRule that specifies the user to search for. Any rights, inheritance /// flags, or propagation flags specified by this rule are ignored. - /// // Suppressed because we want to ensure TransactedRegistry* objects. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] public void RemoveAuditRuleAll(TransactedRegistryAuditRule rule) @@ -417,8 +417,8 @@ public void RemoveAuditRuleAll(TransactedRegistryAuditRule rule) /// /// Searches for an audit rule that exactly matches the specified rule and, if found, removes it. - /// The TransactedRegistryAuditRule to be removed. /// + /// The TransactedRegistryAuditRule to be removed. // Suppressed because we want to ensure TransactedRegistry* objects. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] public void RemoveAuditRuleSpecific(TransactedRegistryAuditRule rule) @@ -428,8 +428,8 @@ public void RemoveAuditRuleSpecific(TransactedRegistryAuditRule rule) /// /// Gets the enumeration type that the TransactedRegistrySecurity class uses to represent access rights. - /// A Type object representing the RegistryRights enumeration. /// + /// A Type object representing the RegistryRights enumeration. public override Type AccessRightType { get { return typeof(RegistryRights); } @@ -437,8 +437,8 @@ public override Type AccessRightType /// /// Gets the type that the TransactedRegistrySecurity class uses to represent access rules. - /// A Type object representing the TransactedRegistryAccessRule class. /// + /// A Type object representing the TransactedRegistryAccessRule class. public override Type AccessRuleType { get { return typeof(TransactedRegistryAccessRule); } @@ -446,8 +446,8 @@ public override Type AccessRuleType /// /// Gets the type that the TransactedRegistrySecurity class uses to represent audit rules. - /// A Type object representing the TransactedRegistryAuditRule class. /// + /// A Type object representing the TransactedRegistryAuditRule class. public override Type AuditRuleType { get { return typeof(TransactedRegistryAuditRule); } diff --git a/src/System.Management.Automation/namespaces/Win32Native.cs b/src/System.Management.Automation/namespaces/Win32Native.cs index b9c7d8a7a71..b785cd5fbb8 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 diff --git a/src/System.Management.Automation/resources/Authenticode.resx b/src/System.Management.Automation/resources/Authenticode.resx index c196f2b0d65..d180743c34d 100644 --- a/src/System.Management.Automation/resources/Authenticode.resx +++ b/src/System.Management.Automation/resources/Authenticode.resx @@ -142,7 +142,7 @@ Cannot sign code. The specified certificate is not suitable for code signing. - Cannot sign code. The TimeStamp server URL must be fully qualified, and in the format http://<server url>. + Cannot sign code. The TimeStamp server URL must be fully qualified, and in the format http://<server url> or https://<server url>. Cannot sign code. The hash algorithm is not supported. diff --git a/src/System.Management.Automation/resources/AutomationExceptions.resx b/src/System.Management.Automation/resources/AutomationExceptions.resx index 6c4c8fe0d92..899d9e57ac8 100644 --- a/src/System.Management.Automation/resources/AutomationExceptions.resx +++ b/src/System.Management.Automation/resources/AutomationExceptions.resx @@ -201,4 +201,10 @@ Cannot get the value of the Using expression '{0}' in the specified variable dictionary. When creating a PowerShell instance from a script block, the Using expression cannot contain an indexing operation or member-accessing operation. + + Compiled Script Block Dot Source + + + Script block '{0}' invocation into current scope will be disallowed in Constrained Language mode. Script language mode: {1}, Context language mode: {2}. + diff --git a/src/System.Management.Automation/resources/CommandBaseStrings.resx b/src/System.Management.Automation/resources/CommandBaseStrings.resx index 656dc226f22..0ff1aac4716 100644 --- a/src/System.Management.Automation/resources/CommandBaseStrings.resx +++ b/src/System.Management.Automation/resources/CommandBaseStrings.resx @@ -156,6 +156,15 @@ Pause the current pipeline and return to the command prompt. Type "{0}" to resume the pipeline. + + + Program "{0}" ended with non-zero exit code: {1}. + Performing the operation "{0}" on target "{1}". @@ -213,4 +222,22 @@ Reviewed by TArcher on 2010-07-20 The {0} is obsolete. {1} + + Exec call failed with errorno {0} for command line: {1} + + + Command '{0}' was not found. The specified command must be an executable. + + + Script Block Processing Dot-Source Check + + + Dot-Source processing for script block '{0}' will fail in Constrained Language mode because its language mode '{1}' does not match the current language mode '{2}'. + + + Command Searcher + + + Command '{0}' in module '{1}' is untrusted and will not be accessible in ConstrainedLanguage mode. + diff --git a/src/System.Management.Automation/resources/ConsoleInfoErrorStrings.resx b/src/System.Management.Automation/resources/ConsoleInfoErrorStrings.resx index 201dde0e4ac..c795fe2b8f7 100644 --- a/src/System.Management.Automation/resources/ConsoleInfoErrorStrings.resx +++ b/src/System.Management.Automation/resources/ConsoleInfoErrorStrings.resx @@ -117,63 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Required element "PSConsoleFile" in {0} is missing or incorrect. - - - Required element "PSVersion" in {0} is missing or incorrect. - - - Required element "ConsoleSchemaVersion" in {0} is missing or incorrect. - - - The console file is not valid. Multiple entries were found for the element PSConsoleFile. Only one entry is supported for this version. - - - The console file is not valid because the element {0} is not valid. - - - The console file is not valid because the PowerShell snap-in name is missing. - - - Attempting to save a console file with no name. Use Export-Console with the Path parameter to save the console file. - - - Unknown element {0} found. "{1}" should have "{2}" and "{3}" elements only. - - - The console file is not valid. Only one occurrence of the element "{0}" is allowed. - - - The path {0} is not an absolute path. - - - The console file name extension is not valid. A console file name extension must be psc1. - Incorrect PowerShell version {0}. PowerShell version {1} is supported on this computer. - - Cannot find any PowerShell snap-in information for {0}. - - - This is a system PowerShell snap-in that is loaded by PowerShell. - - - Cannot add PowerShell snap-in {0} because it is already added. Verify the name of the snap-in, and then try again. - - - Cannot remove the PowerShell snap-in {0} because it is not loaded. Verify the name of the snap-in that you want to remove, and then try again. - - - Cannot remove the PowerShell snap-in {0} because it is a system snap-in. Verify the name of the snap-in that you want to remove, and then try again. - - - Cannot load the PowerShell snap-in because an error occurred while reading the registry information for the snap-in. - - - An error occurred while attempting to load the system PowerShell snap-ins. Please contact Microsoft Customer Support Services. - The following errors occurred when loading console {0}: {1} @@ -195,43 +141,13 @@ PowerShell {0} is not supported in the current console. PowerShell {1} is supported in the current console. - - The cmdlet is not supported by the custom shell. - - - Cannot export to this file because file {0} is read-only. Change the read-only attribute of the file to read-write, or export to a different file. - File {0} already exists and {1} was specified. - - Cannot export to a console because no console is loaded or no name is specified. - - - Cmdlet {0} - - - Supply values for the following parameters: - - - Cannot export a console file because no console file has been specified. Do you want to continue with the export operation? - - - Cannot save the file because the file name format is not valid. Specify a file name using the command: export-console -path. - - - Cannot save the specified file. The Save operation was canceled. - - - Cannot save the console file because wildcard characters were used. Specify a console file without wildcard characters. - - - You can only save a file when you are working in a file provider. The current provider '{0}' is not a file provider. - - - Cannot set the ConsoleFileName variable to {0}. File {0} was saved. + + The provided configuration file '{0}' does not exist. - - The Save operation failed. Cannot remove the file {0}. + + The provided configuration file '{0}' must have a .pssc file extension. diff --git a/src/System.Management.Automation/resources/DiscoveryExceptions.resx b/src/System.Management.Automation/resources/DiscoveryExceptions.resx index d3923969e8d..2d8445deb4f 100644 --- a/src/System.Management.Automation/resources/DiscoveryExceptions.resx +++ b/src/System.Management.Automation/resources/DiscoveryExceptions.resx @@ -206,6 +206,10 @@ The #requires statement must be in one of the following formats: The '{0}' command was found in the module '{1}', but the module could not be loaded. For more information, run 'Import-Module {1}'. + + The '{0}' command was found in the module '{1}', but the module could not be loaded due to the following error: [{2}] +For more information, run 'Import-Module {1}'. + The module '{0}' could not be loaded. For more information, run 'Import-Module {0}'. diff --git a/src/System.Management.Automation/resources/EventResource.resx b/src/System.Management.Automation/resources/EventResource.resx index a4f245cde93..8e2cef78c3e 100644 --- a/src/System.Management.Automation/resources/EventResource.resx +++ b/src/System.Management.Automation/resources/EventResource.resx @@ -550,7 +550,7 @@ Parameters = {7} Test analytic message - Connection Paramters are + Connection Parameters are Connection URI: {0} Resource URI: {1} User: {2} @@ -640,7 +640,7 @@ Exception StackTrace: {2} Runspace Id: {0} Pipeline Id: {1}. Server is sending data of size {2} to client. DataType: {3} TargetInterface: {4} - Request {0}. Creating a server remote session. UserName: {1} Custome Shell Id: {2} + Request {0}. Creating a server remote session. UserName: {1} Custom Shell Id: {2} Reporting context for request: {0} Context Reported: {0} @@ -707,16 +707,16 @@ Exception StackTrace: {2} Type cast inner exception: {3} - Serialization depth has been overriden. + Serialization depth has been overridden. Serialized type name: {0} Original depth: {1} - Overriden depth: {2} + Overridden depth: {2} Current depth below top level: {3} - Serialization mode has been overriden. + Serialization mode has been overridden. Serialized type name: {0} - Overriden mode: {1} + Overridden mode: {1} Serialization of a script property has been skipped, because there is no runspace to use for evaluation of the property. diff --git a/src/System.Management.Automation/resources/ExtendedTypeSystem.resx b/src/System.Management.Automation/resources/ExtendedTypeSystem.resx index 6a697cba888..f8f9ca714cb 100644 --- a/src/System.Management.Automation/resources/ExtendedTypeSystem.resx +++ b/src/System.Management.Automation/resources/ExtendedTypeSystem.resx @@ -156,6 +156,9 @@ Cannot find an overload for "{0}" and the argument count: "{1}". + + Could not find a suitable generic method overload for "{0}" with "{1}" type parameters, and the argument count: "{2}". + Multiple ambiguous overloads found for "{0}" and the argument count: "{1}". @@ -186,6 +189,9 @@ Cannot convert the "{0}" value of type "{1}" to type "{2}". + + Cannot convert the value of type "{0}" to type "{1}". + Cannot convert value "{0}" to type "{1}". Error: "{2}" @@ -349,7 +355,10 @@ "{0}" returned a null value. - The {0} property was not found for the {1} object. The available property is: {2} + The property '{0}' was not found for the '{1}' object. The settable properties are: {2}. + + + The property '{0}' was not found for the '{1}' object. There is no settable property available. Cannot create object of type "{0}". {1} @@ -382,4 +391,16 @@ PS> [System.Collections.Generic.Comparer``1]::get_Default() Cannot create an instance of the ByRef-like type "{0}". ByRef-like types are not supported in PowerShell. + + Extended Type System Hashtable Conversion + + + Type conversion from HashTable to '{0}' will not be allowed in ConstrainedLanguage mode. + + + Extended Type System Hashtable Conversion + + + Type conversion from '{0}' to '{1}' will not be allowed in ConstrainedLanguage mode. + diff --git a/src/System.Management.Automation/resources/FileSystemProviderStrings.resx b/src/System.Management.Automation/resources/FileSystemProviderStrings.resx index 1b4d1159e59..360c2eceb7e 100644 --- a/src/System.Management.Automation/resources/FileSystemProviderStrings.resx +++ b/src/System.Management.Automation/resources/FileSystemProviderStrings.resx @@ -216,9 +216,6 @@ An item with the specified name {0} already exists. - - The path length is too short. The character length of a path cannot be less than the character length of the basePath. - A delimiter cannot be specified when reading the stream one byte at a time. @@ -276,9 +273,6 @@ The '{0}' and '{1}' parameters cannot be specified in the same command. - - The substitute path for the DOS device '{0}' is too long. It exceeds the maximum total path length (32,767 characters) that is valid for the Windows API. - A directory is required for the operation. The item '{0}' is not a directory. @@ -324,15 +318,9 @@ Failed to read remote file '{0}'. - - Failed to validate remote destination '{0}'. - Cannot validate if remote destination {0} is a file. - - Remote copy with {0} is not supported. - Failed to create directory '{0}' on remote destination. @@ -340,9 +328,30 @@ Maximum size for drive has been exceeded: {0}. - Cannot create symbolic link because the path {0} already exists. + Cannot create link because the path already exists: {0}. Skip already-visited directory {0}. + + Destination path cannot be a subdirectory of the source or the source itself: {0}. + + + The target and path cannot be the same. + + + Copied {0} of {1} files + + + {0} of {1} ({2:0.0} MB/s) + + + Removed {0} of {1} files + + + {0} of {1} ({2:0.0} MB/s) + + + Creating a junction requires an absolute path for the target. + diff --git a/src/System.Management.Automation/resources/HelpDisplayStrings.resx b/src/System.Management.Automation/resources/HelpDisplayStrings.resx index 8866eea8d61..1bacf906961 100644 --- a/src/System.Management.Automation/resources/HelpDisplayStrings.resx +++ b/src/System.Management.Automation/resources/HelpDisplayStrings.resx @@ -183,9 +183,6 @@ Content: - - Output: - PROVIDER NAME @@ -267,9 +264,6 @@ Cmdlets Supported: - - CMDLETS SUPPORTED - ALIASES @@ -306,6 +300,10 @@ The specified culture is not supported: {0}. Specify a culture from the following list: {{{1}}}. + + Postponing error and trying fallback cultures, will show as error if none of fallbacks are supported: +{0} + The ModuleBase directory cannot be found. Verify the directory and try again. @@ -357,9 +355,6 @@ Error extracting Help content. - - Error installing help content. - Unable to connect to Help content. The server on which Help content is stored might not be available. Verify that the server is available, or wait until the server is back online, and then try the command again. @@ -397,6 +392,9 @@ English-US help content is available and can be saved using: Save-Help -UICultur Failed to update Help for the module(s) '{0}' with UI culture(s) {{{1}}} : {2}. English-US help content is available and can be installed using: Update-Help -UICulture en-US. + + Your current culture is ({0}), which is not associated with any language, consider changing your system culture or install the English-US help content using: Update-Help -UICulture en-US. + false diff --git a/src/System.Management.Automation/resources/HelpErrors.resx b/src/System.Management.Automation/resources/HelpErrors.resx index 851e457cf27..27634de2995 100644 --- a/src/System.Management.Automation/resources/HelpErrors.resx +++ b/src/System.Management.Automation/resources/HelpErrors.resx @@ -187,4 +187,7 @@ To update these Help topics, start PowerShell by using the "Run as Administrator ForwardHelpTargetName cannot refer to the function itself. + + Cannot get help from a network location when in a restricted session. + diff --git a/src/System.Management.Automation/resources/HistoryStrings.resx b/src/System.Management.Automation/resources/HistoryStrings.resx index c20b5bc6855..7bc4c076521 100644 --- a/src/System.Management.Automation/resources/HistoryStrings.resx +++ b/src/System.Management.Automation/resources/HistoryStrings.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Cannot locate history. - The identifier {0} is not a valid value for a History identifier. Specify a positive number, and then try again. @@ -147,9 +144,6 @@ The identifier {0} is not valid. Specify a positive number, and then try again. - - Note: {0} entries were cleared from the session history. - This command will clear all the entries from the session history. diff --git a/src/System.Management.Automation/resources/InternalCommandStrings.resx b/src/System.Management.Automation/resources/InternalCommandStrings.resx index 4f2705670bc..81658d60d4a 100644 --- a/src/System.Management.Automation/resources/InternalCommandStrings.resx +++ b/src/System.Management.Automation/resources/InternalCommandStrings.resx @@ -184,4 +184,10 @@ ErrorAction, WarningAction, InformationAction, PipelineVariable An unexpected error has occurred while processing ForEach-Object -Parallel input. This may mean that some of the piped input did not get processed. Error: {0}. + + ForEach-Object Cmdlet + + + Method invocation on type '{0}' will not be allowed when run in Constrained Language mode. + \ No newline at end of file diff --git a/src/System.Management.Automation/resources/InternalHostStrings.resx b/src/System.Management.Automation/resources/InternalHostStrings.resx index 861c7609d05..cf0a17e16e8 100644 --- a/src/System.Management.Automation/resources/InternalHostStrings.resx +++ b/src/System.Management.Automation/resources/InternalHostStrings.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Cannot exit a nested prompt because no nested prompts exist. - EnterNestedPrompt has not been called as many times as ExitNestedPrompt. diff --git a/src/System.Management.Automation/resources/Metadata.resx b/src/System.Management.Automation/resources/Metadata.resx index 61aad9f57b5..de0a30f77e2 100644 --- a/src/System.Management.Automation/resources/Metadata.resx +++ b/src/System.Management.Automation/resources/Metadata.resx @@ -210,6 +210,12 @@ The argument is null, empty, or an element of the argument collection contains a null value. Supply a collection that does not contain any null values and then try the command again. + + The argument is null, empty, or consists of only white-space characters. Provide an argument that contains non white-space characters, and then try the command again. + + + An element of the argument collection is null, empty, or consists of only white-space characters. Supply a collection that does not contain any those values and then try the command again. + A parameter with the name '{0}' was defined multiple times for the command. @@ -252,4 +258,10 @@ Cannot process input. The argument "{0}" is not trusted. + + ValidateTrustedData Attribute Check Failure + + + The parameter argument '{0}' is not trusted and will fail the ValidateTrustedData parameter attribute check in Constrained Language mode. + diff --git a/src/System.Management.Automation/resources/Modules.resx b/src/System.Management.Automation/resources/Modules.resx index 345811502b6..4afcaa2585f 100644 --- a/src/System.Management.Automation/resources/Modules.resx +++ b/src/System.Management.Automation/resources/Modules.resx @@ -231,6 +231,21 @@ The required module '{1}' with MinimumVersion '{2}' and MaximumVersion '{3}' is not loaded. Load the module or remove the module from 'RequiredModules' in the file '{0}'. + + The module '{0}' cannot be found with ModuleVersion '{1}'. + + + The module '{0}' cannot be found with RequiredVersion '{1}'. + + + The module '{0}' cannot be found with MaximumVersion '{1}'. + + + The module '{0}' cannot be found with ModuleVersion '{1}' and MaximumVersion '{2}'. + + + The module '{0}' cannot be found. + No modules were removed. Verify that the specification of modules to remove is correct and those modules exist in the runspace. @@ -624,4 +639,49 @@ Cannot create new module while the session is in ConstrainedLanguage mode. + + Cannot find the built-in module '{0}' that is compatible with the 'Core' edition. Please make sure the PowerShell built-in modules are available. They usually come with the PowerShell package under the $PSHOME module path, and are required for PowerShell to function properly. + + + Export-ModuleMember Cmdlet + + + Export of module members will fail in Constrained Language mode because module '{0}', has a language mode '{1}' that is different from the current session '{2}'. + + + Module Implicit Function Export + + + Implicit function export for module '{0}' will be denied because it is trusted (runs in Full Language mode) but the session is not trusted (runs in Constrained Language mode). It is best practice to always export module functions individually by full name. + + + Importing Script File as Module + + + Importing the script file '{0}' as a module will be disallowed in ConstrainedLanguage mode. + + + Module Contains Dot-Source Operator + + + Module '{0}' import will in fail Constrained Language mode because it exports functions using wildcard characters while also using the dot-source operator. + + + "Module Exporting Functions + + + Module '{0}' exports functions using name wildcard characters. Any nested module function names will be removed when running in Constrained Language mode. + + + "New-Module Cmdlet + + + A new module from an untrusted Constrained Language session will be blocked from providing the FullLanguage script block. + + + "Module Mismatched Language Modes + + + A dependent module is being loaded that has a different language mode than the parent. This will be disallowed when in Constrained Language mode. + diff --git a/src/System.Management.Automation/resources/PSStyleStrings.resx b/src/System.Management.Automation/resources/PSStyleStrings.resx new file mode 100644 index 00000000000..bc7acb1c200 --- /dev/null +++ b/src/System.Management.Automation/resources/PSStyleStrings.resx @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The specified string contains printable content when it should only contain ANSI escape sequences: {0} + + + The MaxWidth for the Progress rendering must be at least 18 to render correctly. + + + When adding or removing extensions, the extension must start with a period. + + diff --git a/src/System.Management.Automation/resources/ParameterBinderStrings.resx b/src/System.Management.Automation/resources/ParameterBinderStrings.resx index 8c49becc278..5dc76271e35 100644 --- a/src/System.Management.Automation/resources/ParameterBinderStrings.resx +++ b/src/System.Management.Automation/resources/ParameterBinderStrings.resx @@ -174,9 +174,6 @@ Cannot retrieve the dynamic parameters for the cmdlet. {6} - - Cannot retrieve dynamic parameters for the cmdlet. Dynamic parameter '{1}' specified parameter set '{6}' which was not statically defined for this cmdlet. New parameter sets may not be defined as dynamic parameters, although dynamic parameters may join parameter sets which were statically defined. - Supply values for the following parameters: @@ -222,9 +219,6 @@ Multiple different default values are defined in $PSDefaultParameterValues for the parameter matching the following name or alias: {0}. These defaults have been ignored. - - The automatic variable $PSDefaultParameterValues was ignored because it is not a valid hashtable object. It must be of the type IDictionary. - The following name or alias defined in $PSDefaultParameterValues for this cmdlet resolves to multiple parameters: {0}. The default has been ignored. @@ -252,4 +246,16 @@ The key '{0}' has already been added to the dictionary. + + Method or Property Invocation Not Allowed + + + Invocation of Method or Property '{0}' on type '{1}' will not be allowed in Constrained Language mode for untrusted scripts. + + + Type Creation Not Allowed + + + Creation of Type '{0}' will not be allowed during parameter binding in Constrained Language mode for untrusted scripts. + diff --git a/src/System.Management.Automation/resources/ParserStrings.resx b/src/System.Management.Automation/resources/ParserStrings.resx index dd9ce1b8c55..a76527cfbab 100644 --- a/src/System.Management.Automation/resources/ParserStrings.resx +++ b/src/System.Management.Automation/resources/ParserStrings.resx @@ -123,9 +123,6 @@ Unable to find type [{0}]. Details: {1} - - Incomplete variable reference token. - Incomplete string token. @@ -141,9 +138,6 @@ The Unicode escape sequence contains more than the maximum of six hex digits between braces. - - A number cannot be both a long and floating point. - Cannot use [ref] with other types in a type constraint. @@ -183,14 +177,6 @@ Parameter '{0}' is not valid - - Ambiguous parameter '-{0}' -Possible matches are - - - - {0} ({1}) - Missing expression after '{0}' in pipeline element. @@ -227,15 +213,9 @@ Possible matches are An empty pipe element is not allowed. - - Unknown assignment operator '{0}'. - The assignment expression is not valid. The input to an assignment operator must be an object that is able to accept assignments, such as a variable or a property. - - Cannot expand the splatted variable '@{0}'. Splatted variables cannot be used as part of a property or array expression. Assign the result of the expression to a temporary variable then splat the temporary variable instead. - A hash table can only be added to another hash table. @@ -260,9 +240,6 @@ Possible matches are You must provide a value expression following the '{0}' operator. - - A regular expression that was provided to '{0}' is not valid: {1}. - The '{0}' operator works only on variables or on properties. @@ -275,9 +252,6 @@ Possible matches are Missing property name after reference operator. - - Property reference or expression is missing or not valid. - The property '{0}' cannot be found on this object. Verify that the property exists and can be set. @@ -296,8 +270,8 @@ Possible matches are Unable to index into an object of type "{0}" with the ByRef-like return type "{1}". ByRef-like types are not supported in PowerShell. - - Array assignment to [{0}] failed: {1}. + + The array has too many dimensions: {0}. The number of dimensions for an array must be less than or equal to 32. Array assignment to [{0}] failed because assignment to slices is not supported. @@ -305,18 +279,9 @@ Possible matches are You cannot index into a {0} dimensional array with index [{1}]. - - Unable to assign to a dictionary of type {0} when the key is of type {1}. - - - Unable to assign to an index into an object of type {0}. - Array assignment failed because index '{0}' was out of range. - - Assigning to array element at index [{0}] failed: {1}. - Missing expression after '{0}'. @@ -347,15 +312,9 @@ Possible matches are An expression was expected after '('. - - Missing key before '=' in hash literal. - Missing '=' operator after key in hash literal. - - The "=" operator is missing after a named argument. - Missing statement after '=' in hash literal. @@ -389,9 +348,6 @@ Possible matches are The path cannot be processed because it resolved to more than one file; only one file at a time can be processed. - - The switch statement was incomplete. - The {0} '-{1}' parameter is reserved for future use. @@ -410,9 +366,6 @@ Possible matches are A switch statement must have one of the following: '-file file_name' or '( expression )'. - - Missing expression after '(' in switch statement. - Missing condition in switch statement clause. @@ -457,15 +410,9 @@ The correct form is: foreach ($a in $b) {...} Options are not allowed on the -split operator with a predicate. - - The terminator '{0}' is missing from the multiline comment. - The token '{0}' is not a valid statement separator in this version. - - The 'from' keyword is not supported in this version of the language. - The '{0}' keyword is not supported in this version of the language. @@ -481,23 +428,17 @@ The correct form is: foreach ($a in $b) {...} Incomplete 'try' statement. A try statement requires a body. - - The character '{0}' is not valid. Labels can contain only alphanumeric characters, numbers, and underscores ('_'). - Parameter declarations are a comma-separated list of variable names with optional initializer expressions. Missing function body in function declaration. - - Could not process combined Begin/Process/End clauses with command text. A script or function can either have begin/process/end clauses or command text but not both. - Script command clause '{0}' has already been defined. - unexpected token '{0}', expected 'begin', 'process', 'end', or 'dynamicparam'. + unexpected token '{0}', expected 'begin', 'process', 'end', 'clean', or 'dynamicparam'. Missing closing '}' in statement block or type definition. @@ -611,45 +552,18 @@ The correct form is: foreach ($a in $b) {...} At {0}:{1} char:{2} + {3} - - At char:{0} - {0,4}+ {1} - - ! Trap or Catch on matching exception [{0}] - - - ! Trap or Catch on [{0}]; subclass of exception [{1}] - - - ! Trap or Catch generic; caught [{0}] - - - ! SET-MULTIPLE ${0} assigned remaining {1} values. - - - ! SET-MULTIPLE ${0} = '{1}'. - ! SET ${0} = '{1}'. - - ! CALL scriptblock. - - - ! CALL script '{0}' - ! CALL function '{0}' ! CALL function '{0}' (defined in file '{1}') - - ! Setting parameterized property '{0}' - ! CALL method '{0}' @@ -662,42 +576,15 @@ The correct form is: foreach ($a in $b) {...} Missing ] at end of type token. - - Missing ) at end of subexpression. - Use `{ instead of { in variable names. - - Missing } at end of variable name. - - - Braced variable name cannot be empty. - The Data section is missing its statement block. - - Missing the opening brace "{" in the Data section. - - - Missing closing brace in the data section statement. - - - The body of the Data section is not valid. The Data section body can be only a convert-* command invocation optionally enclosed by an If statement. - The "{0}" parameter of the Data section is not valid. The valid Data section parameter is SupportedCommand. - - Expandable strings are not allowed in the list of supported commands for the Data section. - - - A token that is not valid was found in the list of supported commands for the Data section. - - - The Data section variable "{0}" has already been used for an existing variable or another Data section. - Array references are not allowed in restricted language mode or a Data section. @@ -707,9 +594,6 @@ The correct form is: foreach ($a in $b) {...} Redirection is not allowed in restricted language mode or a Data section. - - A command is referenced that is not allowed. Only convertfrom-* commands are supported in restricted language mode or a Data section. - The Do and While statements are not allowed in restricted language mode or a Data section. @@ -782,21 +666,12 @@ The correct form is: foreach ($a in $b) {...} Cannot find the type for custom attribute '{0}'. Make sure that the assembly that contains this type is loaded. - - Cannot find an appropriate constructor to instantiate the custom attribute object for type '{0}'. - - - The custom attribute type '{0}' is not derived from System.Attribute. - Property '{0}' cannot be found for type '{1}'. Unexpected attribute '{0}'. - - Operator '{0}' is not supported for type '{1}'. - Missing ] at end of attribute or type literal. @@ -893,9 +768,6 @@ The correct form is: foreach ($a in $b) {...} error stream - - host stream - output stream @@ -956,12 +828,6 @@ The correct form is: foreach ($a in $b) {...} An attribute name for resource '{0}' was found that is not valid. An attribute name must be a simple string, and cannot contain variables or expressions. Replace '{1}' with a simple string. - - The configuration name is missing or '{' was not found for a default definition. - - - The parameter {0} is not valid for the configuration statement. - The member '{0}' is not valid. Valid members are '{1}'. @@ -969,9 +835,6 @@ The correct form is: foreach ($a in $b) {...} Missing '{' in object definition. - - Parameter {0} can only be specified once for a configuration. - A required name or expression was missing. @@ -990,14 +853,6 @@ The correct form is: foreach ($a in $b) {...} The name for the configuration is missing. Provide the missing name as a simple name, string, or string-valued expression. - - Missing argument to -Resources. The argument to the -Resource parameter must be a comma-separated list of names or constant strings naming modules to reference for resource type definitions. - -Required Resources should not be localized - - - Unexpected token '{0}'. The argument to the -Resource parameter must be a comma-separated list of names or constant strings naming modules to reference for resource type definitions. - -Resource parameter should not be localized - Could not find the module '{0}'. @@ -1126,6 +981,9 @@ ModuleVersion : Version of module to import. If used, ModuleName must represent Unable to convert input to the target type [{0}] passed to the ForEach() operator. Please check the specified type and try running your script again. + + Script block with a 'clean' block is not supported by the 'ForEach' method. + The 'numberToReturn' value provided to the third argument of the Where() operator must be greater than zero. Please correct the argument's value and try running your script again. @@ -1150,15 +1008,9 @@ ModuleVersion : Version of module to import. If used, ModuleName must represent Missing using directive - - Missing property name - Missing namespace alias - - Missing type alias - Missing '=' operator @@ -1377,6 +1229,9 @@ ModuleVersion : Version of module to import. If used, ModuleName must represent This syntax of the 'using' statement is not supported. + + The specified namespace in the 'using' statement contains invalid characters. + information stream @@ -1455,33 +1310,9 @@ ModuleVersion : Version of module to import. If used, ModuleName must represent Unable to find DSC schema store at "{0}". Please ensure PSDesiredStateConfiguration v3 module is installed. - - PS7DscSupport experimental feature is disabled; use Enable-ExperimentalFeature or update powershell.config.json to enable this feature. - {0} - - Command pipeline not supported for implicit remoting batching. - - - Command is not a simple pipeline and cannot be batched. - - - The pipeline command '{0}' is not an implicit remoting command or an approved batching command. - - - The pipeline command '{0}' is for a different remote session and cannot be batched. - - - The implicit remoting PSSession for batching could not be retrieved. - - - Exception while checking the command for implicit remoting batching: {0} - - - Implicit remoting command pipeline has been batched for execution on remote target. - This script contains content that has been flagged as suspicious through a policy setting and has been blocked with error code {0}. Contact your administrator for more information. @@ -1500,4 +1331,46 @@ ModuleVersion : Version of module to import. If used, ModuleName must represent Background operators can only be used at the end of a pipeline chain. + + Directly invoking the 'clean' block of a script block is not supported. + + + Parser Configuration Keyword + + + The Configuration keyword will not be allowed in Constrained Language mode for untrusted script. + + + Parser Class Keyword + + + The Class keyword will not be allowed in Constrained Language mode for untrusted script. + + + Parser Data Section SupportedCommand + + + The Data Section that includes the SupportedCommand parameter would be disallowed in Constrained Language mode for untrusted script. + + + Module Scope Call Operator + + + The module scope call operator will be denied in Constrained Language mode. + + + ForEach Keyword Method Invocation + + + The ForEach keyword will fail '{0}' iteration item method invocation when run in Constrained Language mode. + + + Expression Evaluation May Fail + + + Creating a steppable pipeline from a script block may require evaluating some expressions within the script block. The expression evaluation will silently fail and return 'null' in Constrained Language mode, unless the expression represents a constant value. + + + Configuration keyword is not supported on ARM64 processors. + diff --git a/src/System.Management.Automation/resources/PathUtilsStrings.resx b/src/System.Management.Automation/resources/PathUtilsStrings.resx index 217f2522ac8..07a4677fa2f 100644 --- a/src/System.Management.Automation/resources/PathUtilsStrings.resx +++ b/src/System.Management.Automation/resources/PathUtilsStrings.resx @@ -138,6 +138,9 @@ The directory '{0}' already exists. Use the -Force parameter if you want to overwrite the directory and files within the directory. + + The user module path does not exist, and hence a module folder cannot be created for the provided module name '{0}'. + Cannot create the module {0} due to the following: {1}. Use a different argument for the -OutputModule parameter and retry. {StrContains="OutputModule"} diff --git a/src/System.Management.Automation/resources/PowerShellStrings.resx b/src/System.Management.Automation/resources/PowerShellStrings.resx index 230ac1b2685..563479c2d62 100644 --- a/src/System.Management.Automation/resources/PowerShellStrings.resx +++ b/src/System.Management.Automation/resources/PowerShellStrings.resx @@ -132,12 +132,6 @@ Cannot perform operation because the runspace is not in the '{0}' state. Current state of runspace is '{1}'. - - The runspace pool specified is not in an opened state. - - - This operation is currently not supported in the remoting scenario. - Nested PowerShell instances cannot be invoked asynchronously. Use the Invoke method. @@ -159,24 +153,6 @@ There is no Runspace available to run commands in this thread. You can provide one in the DefaultRunspace property of the System.Management.Automation.Runspaces.Runspace type. The command you attempted to invoke was: {0} - - GetJobForCommand is not supported when there is more than one command in the PowerShell instance. - - - The Command property of a PowerShell object cannot be empty. - - - A job cannot be started when it is already running. - - - Support for interactive jobs is not available - - - A job object can be used only once. - - - A job object cannot be reused. - This PowerShell object cannot be connected because it is not associated with a remote runspace or runspace pool. @@ -192,10 +168,6 @@ The connection attempt to the remote command failed. - - PSChildJobProxy does not support control methods. - PSChildJobProxy is the name of a class and should not be localized - The operation cannot be performed because a command is currently stopping. Wait for the command to complete stopping, and then try the operation again. diff --git a/src/System.Management.Automation/resources/RegistryProviderStrings.resx b/src/System.Management.Automation/resources/RegistryProviderStrings.resx index 046d28b3c10..901c944fd77 100644 --- a/src/System.Management.Automation/resources/RegistryProviderStrings.resx +++ b/src/System.Management.Automation/resources/RegistryProviderStrings.resx @@ -132,9 +132,6 @@ New Item - - Item: {0} Type: {1} - Item: {0} @@ -174,24 +171,6 @@ Item: {0} Property: {1} - - Set Property Value At - - - Item: {0} Property: {1} At: {2} - - - Add Property Value At - - - Item: {0} Property: {1} At: {2} - - - Remove Property Value At - - - Item: {0} Property: {1} At: {2} - New Property @@ -222,9 +201,6 @@ Item: {0} SourceProperty: {1} DestinationItem: {2} DestinationProperty: {3} - - (default) - The operation was not processed. The location that was provided does not allow this operation. @@ -246,15 +222,6 @@ The operation cannot be performed because the destination path is subordinate to the source path. - - The at parameter must be an integer to index a specific property value. - - - The property is not a multi-valued property. To remove this property, use Remove-ItemProperty. - - - The property is not a multi-valued property and values cannot be added to it. To change the value use Set-ItemProperty. - The property already exists. @@ -330,24 +297,12 @@ The specified RegistryKeyPermissionCheck value is not valid. - - A transaction argument must be specified. - - - The specified SafeHandle value is not valid. - The registry key has subkeys; recursive removals are not supported by this method. - - Remote registry operations are not allowed with transactions. - Cannot create a KTM handle without a Transaction.Current or specified transaction. - - The object does not contain a security descriptor. - The specified transaction or Transaction.Current must match the transaction used to create or open this TransactedRegistryKey. @@ -357,21 +312,6 @@ Requested registry access is not allowed. - - The security identifier is not allowed to be the owner of this object. - - - The security identifier is not allowed to be the primary group of this object. - - - Method failed with unexpected error code {0}. - - - Unable to perform a security operation on an object that has no associated security. This can happen when trying to get an ACL of an anonymous kernel object. - - - Transaction related error {0} occurred. - Access to the registry key '{0}' is denied. @@ -387,15 +327,6 @@ Registry transactions are not supported on this platform. - - The specified permission name is not valid. - - - Incorrect thread for enabling or disabling a privilege. - - - The permission must be reverted before changing its state again. - The specified handle is not valid. diff --git a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx index b4f9dc40a89..9e572797bd2 100644 --- a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx +++ b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx @@ -123,6 +123,9 @@ Out of process memory. + + Remote PSSession enumeration with -ComputerName is only supported on Windows and not "{0}". + Pipeline ID "{0}" does not match the InstanceId of the pipeline that is currently running, "{1}". @@ -837,8 +840,7 @@ Note that 'Start-Job' is not supported by design in scenarios where PowerShell i A {1} job source adapter threw an exception with the following message: {0} - The value {0} is not valid for the {1} parameter. The available values are 2.0, 3.0, 4.0, 5.0, 5.1. - {StrContains="2.0"} {StrContains="3.0"} + The value {0} is not valid for the {1} parameter. The only allowed value is 5.1. The Wait and Keep parameters cannot be used together in the same command. @@ -846,8 +848,8 @@ Note that 'Start-Job' is not supported by design in scenarios where PowerShell i The WriteEvents parameter cannot be used without the Wait parameter. - - PowerShell {0} is not installed. Install PowerShell {0}, and then try again. + + PowerShell remoting endpoint versioning is not supported on PowerShell Core. The following type cannot be instantiated because its constructor is not public: {0}. @@ -1361,7 +1363,7 @@ All WinRM sessions connected to PowerShell session configurations, such as Micro The remote session command is currently stopped in the debugger. Use the Enter-PSSession cmdlet to connect interactively to the remote session and automatically enter into the console debugger. - The remote session to which you are connected does not support remote debugging. You must connect to a remote computer that is running PowerShell {0} or greater. + The remote session to which you are connected does not support remote debugging. You must connect to a remote computer that is running PowerShell 4.0 or greater. Because the session state for session {0}, {1}, {2} is not equal to Open, you cannot run a command in the session. The session state is {3}. @@ -1409,7 +1411,7 @@ All WinRM sessions connected to PowerShell session configurations, such as Micro Multiple processes were found with this name {0}. Use the process Id to specify a single process to enter. - Cannot enter process {0} because it has not loaded the PowerShell engine. + Cannot enter process with Id '{0}' because it has not loaded the PowerShell engine. No process was found with Id: {0}. @@ -1625,6 +1627,13 @@ All WinRM sessions connected to PowerShell session configurations, such as Micro The SSH client session has ended with error message: {0} + + SSH connection attempt failed after time out: {0} seconds. + + + +SSH client process terminated before connection could be established. + The provided SSHConnection hashtable is missing the required ComputerName or HostName parameter. @@ -1696,4 +1705,25 @@ All WinRM sessions connected to PowerShell session configurations, such as Micro Unable to create Windows PowerShell process because Windows PowerShell could not be found on this machine. + + The Runspace argument to Create must be a non-null RemoteRunspace object. + + + The session configuration hash table contains an invalid key type. Keys should be string types. + + + The session configuration file contains an unsupported configuration option: {0}. This is a remoting endpoint configuration option, that does not apply to PowerShell session state. + + + The session configuration file contains an unknown configuration option: {0}. + + + Expression Evaluation May Fail + + + Creating a PowerShell object from a script block may require evaluating some expressions within the script block. The expression evaluation will silently fail and return 'null' in Constrained Language mode, unless the expression represents a constant 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. + diff --git a/src/System.Management.Automation/resources/RunspaceInit.resx b/src/System.Management.Automation/resources/RunspaceInit.resx index ac900465d7d..f12ba3f71c5 100644 --- a/src/System.Management.Automation/resources/RunspaceInit.resx +++ b/src/System.Management.Automation/resources/RunspaceInit.resx @@ -186,9 +186,15 @@ Dictates what type of prompt should be displayed for the current nesting level + + If true, $ErrorActionPreference applies to native executables, so that non-zero exit codes will generate cmdlet-style errors governed by error action settings + If true, WhatIf is considered to be enabled for all commands. + + Dictates how arguments are passed to native executables. + Dictates the limit of enumeration on formatting IEnumerable objects diff --git a/src/System.Management.Automation/resources/RunspacePoolStrings.resx b/src/System.Management.Automation/resources/RunspacePoolStrings.resx index 515bcece698..1b44e1ea3b0 100644 --- a/src/System.Management.Automation/resources/RunspacePoolStrings.resx +++ b/src/System.Management.Automation/resources/RunspacePoolStrings.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - The runspace pool is closed. - The maximum pool size cannot be less than 1. @@ -150,9 +147,6 @@ This runspace does not support disconnect and connect operations. - - Cannot set the TypeTable data unless the runspace pool is in either the Disconnected or BeforeOpen states. Current state is {0}. - Cannot perform the operation because the runspace pool is in the Disconnected state. diff --git a/src/System.Management.Automation/resources/RunspaceStrings.resx b/src/System.Management.Automation/resources/RunspaceStrings.resx index fa98177bc95..e3a0f93bd7e 100644 --- a/src/System.Management.Automation/resources/RunspaceStrings.resx +++ b/src/System.Management.Automation/resources/RunspaceStrings.resx @@ -162,9 +162,6 @@ A pipeline is already running. Concurrent SessionStateProxy method calls are not allowed. - - Parameter name or value must be specified. - This property cannot be changed after the runspace has been opened. @@ -207,9 +204,6 @@ Cannot connect the PSSession because the session is not in the Disconnected state, or is not available for connection. - - One or more errors occurred while processing the module '{0}' that is specified in the InitialSessionState object used to create this runspace. For a complete list of errors, see the ErrorRecords property. - Value for parameter cannot be PipelineResultTypes.None or PipelineResultTypes.Output. diff --git a/src/System.Management.Automation/resources/SecuritySupportStrings.resx b/src/System.Management.Automation/resources/SecuritySupportStrings.resx index cd8a65a5c90..c49261af24c 100644 --- a/src/System.Management.Automation/resources/SecuritySupportStrings.resx +++ b/src/System.Management.Automation/resources/SecuritySupportStrings.resx @@ -153,4 +153,16 @@ Invalid session key data. + + Script file, '{0}', is blocked from running by system policy. + + + An unknown script file policy enforcement value was returned: {0}. + + + Script File Read + + + Script file '{0}' is not trusted by policy and will run in ConstrainedLanguage mode. + diff --git a/src/System.Management.Automation/resources/SessionStateStrings.resx b/src/System.Management.Automation/resources/SessionStateStrings.resx index 39dd766d749..35bcd936435 100644 --- a/src/System.Management.Automation/resources/SessionStateStrings.resx +++ b/src/System.Management.Automation/resources/SessionStateStrings.resx @@ -138,9 +138,6 @@ Attempting to perform the ClearItem operation on the '{0}' provider failed for path '{1}'. {2} - - The dynamic parameters for the ClearItem operation cannot be retrieved from the '{0}' provider for path '{1}'. {2} - Attempting to perform the InvokeDefaultAction operation on the '{0}' provider failed for path '{1}'. {2} @@ -159,15 +156,9 @@ Attempting to perform the IsItemContainer operation on the '{0}' provider failed for path '{1}'. {2} - - The dynamic parameters for the IsItemContainer cannot be retrieved from the '{0}' provider for path '{1}'. {2} - Attempting to perform the RemoveItem operation on the '{0}' provider failed for path '{1}'. {2} - - The dynamic parameters for the RemoveItem operation cannot be retrieved from the '{0}' provider for path '{1}'. {2} - Attempting to perform the GetChildItems operation on the '{0}' provider failed for path '{1}'. {2} @@ -309,15 +300,9 @@ Attempting to perform the SetSecurityDescriptor operation on the '{0}' provider failed for path '{1}'. {2} - - This provider does not support security descriptor related operations. - Attempting to perform the Start operation on the '{0}' provider failed. {1} - - Attempting to perform the StartDynamicParameters operation on the '{0}' provider failed for the path '{1}'. {2} - Attempting to perform the InitializeDefaultDrives operation on the '{0}' provider failed. @@ -333,9 +318,6 @@ Drive '{0}' cannot be removed because the provider '{1}' prevented it. - - The path '{0}' is shorter than the base path '{1}'. - The path '{0}' referred to an item that was outside the base '{1}'. @@ -393,27 +375,15 @@ Alias {0} cannot be modified because it is read-only. - - Filter {0} cannot be modified because it is constant. - - - Filter {0} cannot be modified because it is read-only. - Cannot modify function {0} because it is constant. Cannot modify function {0} because it is read-only. - - Cannot modify variable {0} because it is a constant. - Alias {0} cannot be made constant after it has been created. Aliases can only be made constant at creation time. - - Existing filter {0} cannot be made constant. Filters can be made constant only at creation time. - Existing function {0} cannot be made constant. Functions can be made constant only at creation time. @@ -423,9 +393,6 @@ The AllScope option cannot be removed from the alias '{0}'. - - The AllScope option cannot be removed from the filter '{0}'. - The AllScope option cannot be removed from the function '{0}'. @@ -457,7 +424,7 @@ Cannot find alias because alias '{0}' does not exist. - Cannot set the location because path '{0}' resolved to multiple containers. You can only the set location to a single container at a time. + Cannot set the location because path '{0}' resolved to multiple containers. You can only set the location to a single container at a time. Cannot process variable because variable path '{0}' resolved to multiple items. You can get or set the variable value only one item at a time. @@ -507,9 +474,6 @@ Global scope cannot be removed. - - Too many scopes have been created. - The scope number '{0}' exceeds the number of active scopes. @@ -627,72 +591,18 @@ Drive that maps to the temporary directory path for the current user - - The path is not in the correct format. Paths can contain only provider and drive names separated by slashes or backslashes. - - - Cannot remove provider because removal of providers is not supported. - - - Cannot create provider because creation of new providers is not supported. - - - Drive that contains the list of loaded providers and their drives - - - The root of the drive '{0}' cannot be modified. - - - Cannot create a new provider because type '{0}' is not of type "provider". - - - Cannot set the new item value because the parameter "value" must be of the type ProviderInfo when "type" is specified as "provider". - - - Cannot create a new drive because type '{0}' is not of type "drive". - - - Cannot set new item value because the parameter "value" must be of type PSDriveInfo when "type" is specified as "drive". - - - Cannot create new drive because the name specified in the PSDriveInfo '{0}' does not match the drive name specified in the path '{1}'. - - - The provider name specified in the PSDriveInfo '{0}' does not match the provider name specified in the path '{1}'. - - - Cannot remove the drive root in this way. Use "Remove-PSDrive" to remove this drive. - - Link '{0}' cannot be created because Target was not specified. + Link '{0}' cannot be created because the target Value was not specified. References to the null variable always return the null value. Assignments have no effect. - - Maximum number of errors to retain in a session - - - Maximum number of drives allowed in a session - - - Maximum number of aliases allowed in a session - - - Maximum number of functions allowed in a session - - - Maximum number of variables allowed in a session - Maximum number of history objects to retain in a session Cannot rename function because function {0} is read-only or constant. - - Cannot rename filter because filter {0} is read-only or constant. - Cannot rename alias because alias {0} is read-only or constant. @@ -738,4 +648,10 @@ '{0}' parameter cannot be null or empty. + + Session State Variables + + + Changing or creating the variable '{0}' scope to AllScope will be prevented in ConstrainedLanguage mode. + diff --git a/src/System.Management.Automation/resources/StringDecoratedStrings.resx b/src/System.Management.Automation/resources/StringDecoratedStrings.resx new file mode 100644 index 00000000000..e16aa999992 --- /dev/null +++ b/src/System.Management.Automation/resources/StringDecoratedStrings.resx @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Only 'ANSI' or 'PlainText' is supported for this method. + + diff --git a/src/System.Management.Automation/resources/SubsystemStrings.resx b/src/System.Management.Automation/resources/SubsystemStrings.resx index a2819da8682..14ff340bed1 100644 --- a/src/System.Management.Automation/resources/SubsystemStrings.resx +++ b/src/System.Management.Automation/resources/SubsystemStrings.resx @@ -135,11 +135,14 @@ The specified subsystem type '{0}' is unknown. + + You must specify a concrete subsystem type instead of the base interface 'ISubsystem'. + The specified subsystem kind '{0}' is unknown. - - The specified implementation instance implements the subsystem '{0}', which does not match the target subsystem '{1}'. + + For the target subsystem kind '{0}', the specified subsystem instance needs to implement the corresponding concrete interface or abstract class '{1}'. The declared metadata for subsystem kind '{0}' is invalid. A subsystem that requires cmdlets or functions to be defined cannot allow multiple registrations because that would result in one implementation overwriting the commands defined by another implementation. diff --git a/src/System.Management.Automation/resources/SuggestionStrings.resx b/src/System.Management.Automation/resources/SuggestionStrings.resx index cd44c33e759..9e325b2616c 100644 --- a/src/System.Management.Automation/resources/SuggestionStrings.resx +++ b/src/System.Management.Automation/resources/SuggestionStrings.resx @@ -117,17 +117,17 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Once a transaction is started, only commands that get called with the -UseTransaction flag become part of that transaction. - - - The Use-Transaction cmdlet is intended for scripting of transaction-enabled .NET objects. Its ScriptBlock should contain nothing else. - - The command {0} was not found, but does exist in the current location. PowerShell does not load commands from the current location by default. If you trust this command, instead type: "{1}". See "get-help about_Command_Precedence" for more details. + The command "{0}" was not found, but does exist in the current location. +PowerShell does not load commands from the current location by default (see 'Get-Help about_Command_Precedence'). + +If you trust this command, run the following command instead: + + + The command "{0}" was not found, but does exist in the current location. PowerShell does not load commands from the current location by default. If you trust this command, instead type: "{1}". See "get-help about_Command_Precedence" for more details. - The most similar commands are: {0}. + The most similar commands are: Rule must be a ScriptBlock for dynamic match types. diff --git a/src/System.Management.Automation/resources/TabCompletionStrings.resx b/src/System.Management.Automation/resources/TabCompletionStrings.resx index 33710a77176..751e04cfd2b 100644 --- a/src/System.Management.Automation/resources/TabCompletionStrings.resx +++ b/src/System.Management.Automation/resources/TabCompletionStrings.resx @@ -329,4 +329,282 @@ Shift Right bit operator. Inserts zero in the left-most bit position. For signed values, sign bit is preserved. + + [string] +Specifies the name of the property being created. + + + [string] +Specifies the name of the property being created. + + + [scriptblock] +A script block used to calculate the value of the new property. + + + [string] +Define how the values are displayed in a column. +Valid values are 'left', 'center', or 'right'. + + + [string] +Specifies a format string that defines how the value is formatted for output. + + + [int] +Specifies the maximum column width in a table when the value is displayed. +The value must be greater than 0. + + + [int] +The depth key specifies the depth of expansion per property. + + + [bool] +Specifies the order of sorting for one or more properties. + + + [bool] +Specifies the order of sorting for one or more properties. + + + [String[]] +Specifies the log names to get events from. +Supports wildcards. + + + [String[]] +Specifies the event log providers to get events from. +Supports wildcards. + + + [String[]] +Specifies file paths to log files to get events from. +Valid file formats are: .etl, .evt, and .evtx + + + [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 + + + [int[]] +Selects events with the specified event IDs. + + + [int[]] +Selects events with the specified log levels. +The following log levels are valid: +1: Critical +2: Error +3: Warning +4: Informational +5: Verbose + + + [datetime] +Selects events created after the specified date and time. + + + [datetime] +Selects events created before the specified date and time. + + + [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 + + + [string[]] +Selects events with any of the specified values in the EventData section. + + + [hashtable] +Excludes events that match the values specified in the hashtable. + + + [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. + + + [string] +Specifies a PowerShell edition that the script requires. +Valid values are "Core" and "Desktop" + + + [switch] +Specifies that PowerShell must be running as administrator on Windows. +This must be the last parameter on the #requires statement line. + + + [version] +Specifies the minimum version of PowerShell that the script requires. + + + Specifies that the script requires PowerShell Core to run. + + + Specifies that the script requires Windows PowerShell to run. + + + [string] +Required. Specifies the module name. + + + [string] +Optional. Specifies the GUID of the module. + + + [string] +Specifies a minimum acceptable version of the module. + + + [string] +Specifies an exact, required version of the module. + + + [string] +Specifies the maximum acceptable version of the module. + + + A brief description of the function or script. +This keyword can be used only once in each topic. + + + A detailed description of the function or script. +This keyword can be used only once in each topic. + + + .PARAMETER <Parameter-Name> +The description of a parameter. +Add a .PARAMETER keyword for each parameter in the function or script syntax. + + + A sample command that uses the function or script, optionally followed by sample output and a description. +Repeat this keyword for each example. + + + The .NET types of objects that can be piped to the function or script. +You can also include a description of the input objects. + + + The .NET type of the objects that the cmdlet returns. +You can also include a description of the returned objects. + + + Additional information about the function or script. + + + 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. + + + The name of the technology or feature that the function or script uses, or to which it is related. + + + The name of the user role for the help topic. + + + The keywords that describe the intended use of the function. + + + .FORWARDHELPTARGETNAME <Command-Name> +Redirects to the help topic for the specified command. + + + .FORWARDHELPCATEGORY <Category> +Specifies the help category of the item in .ForwardHelpTargetName + + + .REMOTEHELPRUNSPACE <PSSession-variable> +Specifies a session that contains the help topic. +Enter a variable that contains a PSSession object. + + + .EXTERNALHELP <XML Help File> +The .ExternalHelp keyword is required when a function or script is documented in XML files. + + + Specifies the path to a .NET assembly to load. + +using assembly <.NET-assembly-path> + + + Specifies a PowerShell module to load classes from. + +using module <ModuleName or Path> + +using module <ModuleSpecification hashtable> + + + Specifies a .NET namespace to resolve types from or a namespace alias. + +using namespace <.NET-namespace> + +using namespace <AliasName> = <.NET-namespace> + + + Specifies an alias for a .NET Type. + +using type <AliasName> = <.NET-type> + + + A normal string. + + + A string that contains unexpanded references to environment variables that are expanded when the value is retrieved. + + + Binary data in any form. + + + A 32-bit binary number. + + + An array of strings. + + + A 64-bit binary number. + + + An unsupported registry data type. + + + ',' - Comma + + + ', ' - Comma-Space + + + ';' - Semi-Colon + + + '; ' - Semi-Colon-Space + + + {0} - Newline + + + '-' - Dash + + + ' ' - Space + diff --git a/src/System.Management.Automation/resources/TypesXmlStrings.resx b/src/System.Management.Automation/resources/TypesXmlStrings.resx index 02c77e45424..10eb08a7351 100644 --- a/src/System.Management.Automation/resources/TypesXmlStrings.resx +++ b/src/System.Management.Automation/resources/TypesXmlStrings.resx @@ -126,9 +126,6 @@ Node "{0}" must occur only once under "{1}". The parent node, "{1}", will be ignored. - - Node "{0}" must have a maximum of one occurrence under "{1}". The parent node, "{1}", will be ignored. - The node {0} is not allowed. The following nodes are allowed: {1}. @@ -141,18 +138,6 @@ Node "{0}" was not found. It should occur only once under "{1}". The parent node, "{1}", will be ignored. - - Node "{0}" was not found. It should occur at least once under "{1}". The parent node, "{1}", will be ignored. - - - Expected XML tag "{0}" instead of node of type "{1}". - - - Node of type "{0}" was not expected. - - - Expected XML tag "{0}" instead of "{1}". - The "Type" node must have "Members", "TypeConverters", or "TypeAdapters". @@ -201,9 +186,6 @@ Node "{0}" should not have "{1}" attribute. - - Value should be "true" or "false" instead of "{0}" for "{1}" attribute. - {0}, {1}: The file was not found. @@ -261,12 +243,6 @@ "{0}" should not have null or an empty string in its property "{1}". - - More than one member with the name "{0}" is defined in the type file. - - - {0}: The file was skipped because it already occurred. - The type "{0}" was not found. The type name value must be the full name of the type. Verify the type name and run the command again. diff --git a/src/System.Management.Automation/security/Authenticode.cs b/src/System.Management.Automation/security/Authenticode.cs index 70b6778c37e..591f64d6298 100644 --- a/src/System.Management.Automation/security/Authenticode.cs +++ b/src/System.Management.Automation/security/Authenticode.cs @@ -4,13 +4,18 @@ #pragma warning disable 1634, 1691 #pragma warning disable 56523 -using Dbg = System.Management.Automation; +#if !UNIX +using Microsoft.Security.Extensions; +#endif +using System.ComponentModel; using System.IO; using System.Management.Automation.Internal; using System.Management.Automation.Security; +using System.Management.Automation.Win32Native; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; -using DWORD = System.UInt32; + +using Dbg = System.Management.Automation; namespace System.Management.Automation { @@ -48,6 +53,8 @@ public enum SigningOption /// internal static class SignatureHelper { + private static Guid WINTRUST_ACTION_GENERIC_VERIFY_V2 = new Guid("00AAC56B-CD44-11d0-8CC2-00C04FC295EE"); + /// /// Tracer for SignatureHelper. /// @@ -89,7 +96,6 @@ internal static class SignatureHelper /// /// Thrown if the file specified by argument fileName is not found /// - [ArchitectureSensitive] internal static Signature SignFile(SigningOption option, string fileName, X509Certificate2 certificate, @@ -99,17 +105,18 @@ internal static Signature SignFile(SigningOption option, bool result = false; Signature signature = null; IntPtr pSignInfo = IntPtr.Zero; - DWORD error = 0; + uint error = 0; string hashOid = null; Utils.CheckArgForNullOrEmpty(fileName, "fileName"); Utils.CheckArgForNull(certificate, "certificate"); - // If given, TimeStamp server URLs must begin with http:// + // If given, TimeStamp server URLs must begin with http:// or https:// if (!string.IsNullOrEmpty(timeStampServerUrl)) { - if ((timeStampServerUrl.Length <= 7) || - (timeStampServerUrl.IndexOf("http://", StringComparison.OrdinalIgnoreCase) != 0)) + if ((timeStampServerUrl.Length <= 7) || ( + (timeStampServerUrl.IndexOf("http://", StringComparison.OrdinalIgnoreCase) != 0) && + (timeStampServerUrl.IndexOf("https://", StringComparison.OrdinalIgnoreCase) != 0))) { throw PSTraceSource.NewArgumentException( nameof(certificate), @@ -185,7 +192,7 @@ internal static Signature SignFile(SigningOption option, // able to see that. #pragma warning disable 56523 result = NativeMethods.CryptUIWizDigitalSign( - (DWORD)NativeMethods.CryptUIFlags.CRYPTUI_WIZ_NO_UI, + (uint)NativeMethods.CryptUIFlags.CRYPTUI_WIZ_NO_UI, IntPtr.Zero, IntPtr.Zero, pSignInfo, @@ -241,7 +248,7 @@ internal static Signature SignFile(SigningOption option, } else { - signature = new Signature(fileName, (DWORD)error); + signature = new Signature(fileName, (uint)error); } } finally @@ -268,19 +275,18 @@ internal static Signature SignFile(SigningOption option, /// /// Thrown if the file specified by argument fileName is not found. /// - [ArchitectureSensitive] - internal static Signature GetSignature(string fileName, string fileContent) + internal static Signature GetSignature(string fileName, byte[] fileContent) { Signature signature = null; if (fileContent == null) { - // First, try to get the signature from the catalog signature APIs. - signature = GetSignatureFromCatalog(fileName); + // First, try to get the signature from the latest dotNet signing API. + signature = GetSignatureFromMSSecurityExtensions(fileName); } // If there is no signature or it is invalid, go by the file content - // with the older WinVerifyTrust APIs + // with the older WinVerifyTrust APIs. if ((signature == null) || (signature.Status != SignatureStatus.Valid)) { signature = GetSignatureFromWinVerifyTrust(fileName, fileContent); @@ -289,159 +295,121 @@ internal static Signature GetSignature(string fileName, string fileContent) return signature; } + /// + /// Gets the file signature using the dotNet Microsoft.Security.Extensions package. + /// This supports both Windows catalog file signatures and embedded file signatures. + /// But it is not supported on all Windows platforms/skus, noteably Win7 and nanoserver. + /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")] - private static Signature GetSignatureFromCatalog(string filename) + private static Signature GetSignatureFromMSSecurityExtensions(string filename) { +#if UNIX + return null; +#else if (Signature.CatalogApiAvailable.HasValue && !Signature.CatalogApiAvailable.Value) { - // Signature.CatalogApiAvailable would be set to false the first time it is detected that - // WTGetSignatureInfo API does not exist on the platform, or if the API is not functional on the target platform. - // Just return from the function instead of revalidating. return null; } - Signature signature = null; - Utils.CheckArgForNullOrEmpty(filename, "fileName"); SecuritySupport.CheckIfFileExists(filename); - try + Signature signature = null; + FileSignatureInfo fileSigInfo; + using (FileStream fileStream = File.OpenRead(filename)) { - using (FileStream stream = File.OpenRead(filename)) + try + { + fileSigInfo = FileSignatureInfo.GetFromFileStream(fileStream); + System.Diagnostics.Debug.Assert(fileSigInfo is not null, "Returned FileSignatureInfo should never be null."); + } + catch (Exception) { - NativeMethods.SIGNATURE_INFO sigInfo = new NativeMethods.SIGNATURE_INFO(); - sigInfo.cbSize = (uint)Marshal.SizeOf(sigInfo); + // For any API error, enable fallback to WinVerifyTrust APIs. + Signature.CatalogApiAvailable = false; + return null; + } + } - IntPtr ppCertContext = IntPtr.Zero; - IntPtr phStateData = IntPtr.Zero; + uint error = GetErrorFromSignatureState(fileSigInfo.State); - try - { - int hresult = NativeMethods.WTGetSignatureInfo(filename, stream.SafeFileHandle.DangerousGetHandle(), - NativeMethods.SIGNATURE_INFO_FLAGS.SIF_CATALOG_SIGNED | - NativeMethods.SIGNATURE_INFO_FLAGS.SIF_CATALOG_FIRST | - NativeMethods.SIGNATURE_INFO_FLAGS.SIF_AUTHENTICODE_SIGNED | - NativeMethods.SIGNATURE_INFO_FLAGS.SIF_BASE_VERIFICATION | - NativeMethods.SIGNATURE_INFO_FLAGS.SIF_CHECK_OS_BINARY, - ref sigInfo, ref ppCertContext, ref phStateData); - - if (Utils.Succeeded(hresult)) - { - DWORD error = GetErrorFromSignatureState(sigInfo.nSignatureState); - - X509Certificate2 cert = null; - - if (ppCertContext != IntPtr.Zero) - { - cert = new X509Certificate2(ppCertContext); - - // Get the time stamper certificate if available - TryGetProviderSigner(phStateData, out IntPtr pProvSigner, out X509Certificate2 timestamperCert); - if (timestamperCert != null) - { - signature = new Signature(filename, error, cert, timestamperCert); - } - else - { - signature = new Signature(filename, error, cert); - } - - switch (sigInfo.nSignatureType) - { - case NativeMethods.SIGNATURE_INFO_TYPE.SIT_AUTHENTICODE: signature.SignatureType = SignatureType.Authenticode; break; - case NativeMethods.SIGNATURE_INFO_TYPE.SIT_CATALOG: signature.SignatureType = SignatureType.Catalog; break; - } - - if (sigInfo.fOSBinary == 1) - { - signature.IsOSBinary = true; - } - } - else - { - signature = new Signature(filename, error); - } - - if (!Signature.CatalogApiAvailable.HasValue) - { - string productFile = Path.Combine(Utils.DefaultPowerShellAppBase, "Modules\\PSDiagnostics\\PSDiagnostics.psm1"); - if (signature.Status != SignatureStatus.Valid) - { - if (string.Equals(filename, productFile, StringComparison.OrdinalIgnoreCase)) - { - Signature.CatalogApiAvailable = false; - } - else - { - // ProductFile has to be Catalog signed. Hence validating - // to see if the Catalog API is functional using the ProductFile. - Signature productFileSignature = GetSignatureFromCatalog(productFile); - Signature.CatalogApiAvailable = (productFileSignature != null && productFileSignature.Status == SignatureStatus.Valid); - } - } - } - } - else - { - // If calling NativeMethods.WTGetSignatureInfo failed (returned a non-zero value), we still want to set Signature.CatalogApiAvailable to false. - Signature.CatalogApiAvailable = false; - } - } - finally - { - if (phStateData != IntPtr.Zero) - { - NativeMethods.FreeWVTStateData(phStateData); - } + if (fileSigInfo.SigningCertificate is null) + { + signature = new Signature(filename, error); + } + else + { + signature = fileSigInfo.TimestampCertificate is null ? + new Signature(filename, error, fileSigInfo.SigningCertificate) : + new Signature(filename, error, fileSigInfo.SigningCertificate, fileSigInfo.TimestampCertificate); + } - if (ppCertContext != IntPtr.Zero) - { - NativeMethods.CertFreeCertificateContext(ppCertContext); - } - } - } + switch (fileSigInfo.Kind) + { + case SignatureKind.None: + signature.SignatureType = SignatureType.None; + break; + + case SignatureKind.Embedded: + signature.SignatureType = SignatureType.Authenticode; + break; + + case SignatureKind.Catalog: + signature.SignatureType = SignatureType.Catalog; + break; + + default: + System.Diagnostics.Debug.Fail("Signature type can only be None, Authenticode or Catalog."); + break; } - catch (TypeLoadException) + + signature.IsOSBinary = fileSigInfo.IsOSBinary; + + if (signature.SignatureType == SignatureType.Catalog && !Signature.CatalogApiAvailable.HasValue) { - // If we don't have WTGetSignatureInfo, don't return a Signature. - Signature.CatalogApiAvailable = false; - return null; + Signature.CatalogApiAvailable = fileSigInfo.State != SignatureState.Invalid; } return signature; +#endif } - private static DWORD GetErrorFromSignatureState(NativeMethods.SIGNATURE_STATE state) +#if !UNIX + private static uint GetErrorFromSignatureState(SignatureState signatureState) { - switch (state) + switch (signatureState) { - case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_UNSIGNED_MISSING: return Win32Errors.TRUST_E_NOSIGNATURE; - case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_UNSIGNED_UNSUPPORTED: return Win32Errors.TRUST_E_NOSIGNATURE; - case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_UNSIGNED_POLICY: return Win32Errors.TRUST_E_NOSIGNATURE; - case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_INVALID_CORRUPT: return Win32Errors.TRUST_E_BAD_DIGEST; - case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_INVALID_POLICY: return Win32Errors.CRYPT_E_BAD_MSG; - case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_VALID: return Win32Errors.NO_ERROR; - case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_TRUSTED: return Win32Errors.NO_ERROR; - case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_UNTRUSTED: return Win32Errors.TRUST_E_EXPLICIT_DISTRUST; - - // Should not happen + case SignatureState.Unsigned: + return Win32Errors.TRUST_E_NOSIGNATURE; + + case SignatureState.SignedAndTrusted: + return Win32Errors.NO_ERROR; + + case SignatureState.SignedAndNotTrusted: + return Win32Errors.TRUST_E_EXPLICIT_DISTRUST; + + case SignatureState.Invalid: + return Win32Errors.TRUST_E_BAD_DIGEST; + default: - System.Diagnostics.Debug.Fail("Should not get here - could not map SIGNATURE_STATE"); + System.Diagnostics.Debug.Fail("Should not get here - could not map FileSignatureInfo.State"); return Win32Errors.TRUST_E_NOSIGNATURE; } } +#endif - private static Signature GetSignatureFromWinVerifyTrust(string fileName, string fileContent) + private static Signature GetSignatureFromWinVerifyTrust(string fileName, byte[] fileContent) { Signature signature = null; - NativeMethods.WINTRUST_DATA wtd; - DWORD error = Win32Errors.E_FAIL; + WinTrustMethods.WINTRUST_DATA wtd; + uint error = Win32Errors.E_FAIL; if (fileContent == null) { Utils.CheckArgForNullOrEmpty(fileName, "fileName"); SecuritySupport.CheckIfFileExists(fileName); + // SecurityUtils.CheckIfFileSmallerThan4Bytes(fileName); } @@ -456,7 +424,11 @@ private static Signature GetSignatureFromWinVerifyTrust(string fileName, string signature = GetSignatureFromWintrustData(fileName, error, wtd); - error = NativeMethods.DestroyWintrustDataStruct(wtd); + wtd.dwStateAction = WinTrustAction.WTD_STATEACTION_CLOSE; + error = WinTrustMethods.WinVerifyTrust( + IntPtr.Zero, + ref WINTRUST_ACTION_GENERIC_VERIFY_V2, + ref wtd); if (error != Win32Errors.NO_ERROR) { @@ -471,90 +443,82 @@ private static Signature GetSignatureFromWinVerifyTrust(string fileName, string return signature; } - [ArchitectureSensitive] - private static DWORD GetWinTrustData(string fileName, string fileContent, - out NativeMethods.WINTRUST_DATA wtData) + private static uint GetWinTrustData( + string fileName, + byte[] fileContent, + out WinTrustMethods.WINTRUST_DATA wtData) { - DWORD dwResult = Win32Errors.E_FAIL; - IntPtr WINTRUST_ACTION_GENERIC_VERIFY_V2 = IntPtr.Zero; - IntPtr wtdBuffer = IntPtr.Zero; - - Guid actionVerify = - new Guid("00AAC56B-CD44-11d0-8CC2-00C04FC295EE"); - - try + wtData = new() { - WINTRUST_ACTION_GENERIC_VERIFY_V2 = - Marshal.AllocCoTaskMem(Marshal.SizeOf(actionVerify)); - Marshal.StructureToPtr(actionVerify, - WINTRUST_ACTION_GENERIC_VERIFY_V2, - false); - - NativeMethods.WINTRUST_DATA wtd; + cbStruct = (uint)Marshal.SizeOf(), + dwUIChoice = WinTrustUIChoice.WTD_UI_NONE, + dwStateAction = WinTrustAction.WTD_STATEACTION_VERIFY, + }; - if (fileContent == null) - { - NativeMethods.WINTRUST_FILE_INFO wfi = NativeMethods.InitWintrustFileInfoStruct(fileName); - wtd = NativeMethods.InitWintrustDataStructFromFile(wfi); - } - else + unsafe + { + fixed (char* fileNamePtr = fileName) { - NativeMethods.WINTRUST_BLOB_INFO wbi = NativeMethods.InitWintrustBlobInfoStruct(fileName, fileContent); - wtd = NativeMethods.InitWintrustDataStructFromBlob(wbi); - } - - wtdBuffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(wtd)); - Marshal.StructureToPtr(wtd, wtdBuffer, false); - - // The result is returned to the caller, and handled generically. - // Disable the PreFast check for Win32 error codes, as we don't care. -#pragma warning disable 56523 - dwResult = NativeMethods.WinVerifyTrust( - IntPtr.Zero, - WINTRUST_ACTION_GENERIC_VERIFY_V2, - wtdBuffer); -#pragma warning restore 56523 + if (fileContent == null) + { + WinTrustMethods.WINTRUST_FILE_INFO wfi = new() + { + cbStruct = (uint)Marshal.SizeOf(), + pcwszFilePath = fileNamePtr, + }; + wtData.dwUnionChoice = WinTrustUnionChoice.WTD_CHOICE_FILE; + wtData.pChoice = &wfi; + + return WinTrustMethods.WinVerifyTrust( + IntPtr.Zero, + ref WINTRUST_ACTION_GENERIC_VERIFY_V2, + ref wtData); + } - wtData = Marshal.PtrToStructure(wtdBuffer); - } - finally - { - Marshal.DestroyStructure(WINTRUST_ACTION_GENERIC_VERIFY_V2); - Marshal.FreeCoTaskMem(WINTRUST_ACTION_GENERIC_VERIFY_V2); - Marshal.DestroyStructure(wtdBuffer); - Marshal.FreeCoTaskMem(wtdBuffer); + fixed (byte* contentPtr = fileContent) + { + Guid pwshSIP = new("603BCC1F-4B59-4E08-B724-D2C6297EF351"); + WinTrustMethods.WINTRUST_BLOB_INFO wbi = new() + { + cbStruct = (uint)Marshal.SizeOf(), + gSubject = pwshSIP, + pcwszDisplayName = fileNamePtr, + cbMemObject = (uint)fileContent.Length, + pbMemObject = contentPtr, + }; + wtData.dwUnionChoice = WinTrustUnionChoice.WTD_CHOICE_BLOB; + wtData.pChoice = &wbi; + + return WinTrustMethods.WinVerifyTrust( + IntPtr.Zero, + ref WINTRUST_ACTION_GENERIC_VERIFY_V2, + ref wtData); + } + } } - - return dwResult; } - [ArchitectureSensitive] private static X509Certificate2 GetCertFromChain(IntPtr pSigner) { - X509Certificate2 signerCert = null; - - // We don't care about the Win32 error code here, so disable - // the PreFast complaint that we're not retrieving it. -#pragma warning disable 56523 - IntPtr pCert = - NativeMethods.WTHelperGetProvCertFromChain(pSigner, 0); -#pragma warning restore 56523 - - if (pCert != IntPtr.Zero) + try { + IntPtr pCert = WinTrustMethods.WTHelperGetProvCertFromChain(pSigner, 0); NativeMethods.CRYPT_PROVIDER_CERT provCert = Marshal.PtrToStructure(pCert); - signerCert = new X509Certificate2(provCert.pCert); + return new X509Certificate2(provCert.pCert); + } + catch (Win32Exception) + { + // We don't care about the Win32 error code here, so return + // null on a failure and let the caller handle it. + return null; } - - return signerCert; } - [ArchitectureSensitive] private static Signature GetSignatureFromWintrustData( string filePath, - DWORD error, - NativeMethods.WINTRUST_DATA wtd) + uint error, + WinTrustMethods.WINTRUST_DATA wtd) { s_tracer.WriteLine("GetSignatureFromWintrustData: error: {0}", error); @@ -596,45 +560,40 @@ private static Signature GetSignatureFromWintrustData( return signature; } - [ArchitectureSensitive] private static bool TryGetProviderSigner(IntPtr wvtStateData, out IntPtr pProvSigner, out X509Certificate2 timestamperCert) { pProvSigner = IntPtr.Zero; timestamperCert = null; - // The GetLastWin32Error of this is checked, but PreSharp doesn't seem to be - // able to see that. -#pragma warning disable 56523 - IntPtr pProvData = - NativeMethods.WTHelperProvDataFromStateData(wvtStateData); -#pragma warning restore 56523 - - if (pProvData != IntPtr.Zero) + try { - pProvSigner = - NativeMethods.WTHelperGetProvSignerFromChain(pProvData, 0, 0, 0); + IntPtr pProvData = WinTrustMethods.WTHelperProvDataFromStateData(wvtStateData); - if (pProvSigner != IntPtr.Zero) - { - NativeMethods.CRYPT_PROVIDER_SGNR provSigner = - Marshal.PtrToStructure(pProvSigner); - if (provSigner.csCounterSigners == 1) - { - // - // time stamper cert available - // - timestamperCert = GetCertFromChain(provSigner.pasCounterSigners); - } + pProvSigner = WinTrustMethods.WTHelperGetProvSignerFromChain( + pProvData, + signerIdx: 0, + counterSigner: false, + counterSignerIdx: 0); - return true; + NativeMethods.CRYPT_PROVIDER_SGNR provSigner = + Marshal.PtrToStructure(pProvSigner); + if (provSigner.csCounterSigners == 1) + { + // + // time stamper cert available + // + timestamperCert = GetCertFromChain(provSigner.pasCounterSigners); } - } - return false; + return true; + } + catch (Win32Exception) + { + return false; + } } - [ArchitectureSensitive] - private static DWORD GetLastWin32Error() + private static uint GetLastWin32Error() { int error = Marshal.GetLastWin32Error(); diff --git a/src/System.Management.Automation/security/CatalogHelper.cs b/src/System.Management.Automation/security/CatalogHelper.cs index 0c1c76b63e4..4892f7434e4 100644 --- a/src/System.Management.Automation/security/CatalogHelper.cs +++ b/src/System.Management.Automation/security/CatalogHelper.cs @@ -6,12 +6,13 @@ using System.Security.Cryptography; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.ComponentModel; using System.IO; using System.Linq; using System.Management.Automation.Internal; using System.Management.Automation.Security; +using System.Management.Automation.Win32Native; using System.Runtime.InteropServices; -using DWORD = System.UInt32; namespace System.Management.Automation { @@ -68,14 +69,14 @@ public class CatalogInformation internal static class CatalogHelper { // Catalog Version is (0X100 = 256) for Catalog Version 1 - private static int catalogVersion1 = 256; + private const int catalogVersion1 = 256; // Catalog Version is (0X200 = 512) for Catalog Version 2 - private static int catalogVersion2 = 512; + private const int catalogVersion2 = 512; // Hash Algorithms supported by Windows Catalog - private static string HashAlgorithmSHA1 = "SHA1"; - private static string HashAlgorithmSHA256 = "SHA256"; + private const string HashAlgorithmSHA1 = "SHA1"; + private const string HashAlgorithmSHA256 = "SHA256"; private static PSCmdlet _cmdlet = null; /// @@ -83,12 +84,11 @@ internal static class CatalogHelper /// /// Handle to open catalog file. /// Version of the catalog. - private static int GetCatalogVersion(IntPtr catalogHandle) + private static int GetCatalogVersion(SafeCATHandle catalogHandle) { int catalogVersion = -1; - IntPtr catalogData = NativeMethods.CryptCATStoreFromHandle(catalogHandle); - NativeMethods.CRYPTCATSTORE catalogInfo = Marshal.PtrToStructure(catalogData); + WinTrustMethods.CRYPTCATSTORE catalogInfo = WinTrustMethods.CryptCATStoreFromHandle(catalogHandle); if (catalogInfo.dwPublicVersion == catalogVersion2) { @@ -220,9 +220,8 @@ internal static void ProcessFileToBeAddedInCatalogDefinitionFile(FileInfo fileTo relativePath = fileToHash.Name; } - if (!relativePaths.Contains(relativePath)) + if (relativePaths.Add(relativePath)) { - relativePaths.Add(relativePath); if (fileToHash.Length != 0) { cdfFilesContent += "" + fileToHash.FullName + "=" + fileToHash.FullName + Environment.NewLine; @@ -248,20 +247,32 @@ internal static void ProcessFileToBeAddedInCatalogDefinitionFile(FileInfo fileTo /// Path to the Input .cdf file. internal static void GenerateCatalogFile(string cdfFilePath) { - string pwszFilePath = cdfFilePath; - NativeMethods.CryptCATCDFOpenCallBack catOpenCallBack = new NativeMethods.CryptCATCDFOpenCallBack(ParseErrorCallback); - // Open CDF File - IntPtr resultCDF = NativeMethods.CryptCATCDFOpen(pwszFilePath, catOpenCallBack); + SafeCATCDFHandle resultCDF; + try + { + resultCDF = WinTrustMethods.CryptCATCDFOpen(cdfFilePath, ParseErrorCallback); + } + catch (Win32Exception e) + { + // If we are not able to open CDF file we can not continue generating catalog + ErrorRecord errorRecord = new ErrorRecord( + new InvalidOperationException(CatalogStrings.UnableToOpenCatalogDefinitionFile, e), + "UnableToOpenCatalogDefinitionFile", + ErrorCategory.InvalidOperation, + null); + _cmdlet.ThrowTerminatingError(errorRecord); + return; + } // navigate CDF header and files sections - if (resultCDF != IntPtr.Zero) + using (resultCDF) { // First navigate all catalog level attributes entries first, they represent zero size files IntPtr catalogAttr = IntPtr.Zero; do { - catalogAttr = NativeMethods.CryptCATCDFEnumCatAttributes(resultCDF, catalogAttr, catOpenCallBack); + catalogAttr = WinTrustMethods.CryptCATCDFEnumCatAttributes(resultCDF, catalogAttr, ParseErrorCallback); if (catalogAttr != IntPtr.Zero) { @@ -272,51 +283,38 @@ internal static void GenerateCatalogFile(string cdfFilePath) // navigate all the files hash entries in the .cdf file IntPtr memberInfo = IntPtr.Zero; - try + IntPtr memberFile = IntPtr.Zero; + string fileName = string.Empty; + do { - IntPtr memberFile = IntPtr.Zero; - NativeMethods.CryptCATCDFEnumMembersByCDFTagExErrorCallBack memberCallBack = new NativeMethods.CryptCATCDFEnumMembersByCDFTagExErrorCallBack(ParseErrorCallback); - string fileName = string.Empty; - do - { - memberFile = NativeMethods.CryptCATCDFEnumMembersByCDFTagEx(resultCDF, memberFile, memberCallBack, ref memberInfo, true, IntPtr.Zero); - fileName = Marshal.PtrToStringUni(memberFile); + memberFile = WinTrustMethods.CryptCATCDFEnumMembersByCDFTagEx(resultCDF, memberFile, ParseErrorCallback, ref memberInfo, + fContinueOnError: true, pvReserved: IntPtr.Zero); + fileName = Marshal.PtrToStringUni(memberFile); - if (!string.IsNullOrEmpty(fileName)) + if (!string.IsNullOrEmpty(fileName)) + { + IntPtr memberAttr = IntPtr.Zero; + string fileRelativePath = string.Empty; + do { - IntPtr memberAttr = IntPtr.Zero; - string fileRelativePath = string.Empty; - do - { - memberAttr = NativeMethods.CryptCATCDFEnumAttributesWithCDFTag(resultCDF, memberFile, memberInfo, memberAttr, memberCallBack); + memberAttr = WinTrustMethods.CryptCATCDFEnumAttributesWithCDFTag(resultCDF, memberFile, memberInfo, memberAttr, ParseErrorCallback); - if (memberAttr != IntPtr.Zero) + if (memberAttr != IntPtr.Zero) + { + fileRelativePath = ProcessFilePathAttributeInCatalog(memberAttr); + if (!string.IsNullOrEmpty(fileRelativePath)) { - fileRelativePath = ProcessFilePathAttributeInCatalog(memberAttr); - if (!string.IsNullOrEmpty(fileRelativePath)) - { - // Found the attribute we are looking for - // Filename we read from the above API has appended to its name as per CDF file tags convention - // Truncating that Information from the string. - string itemName = fileName.Substring(6); - _cmdlet.WriteVerbose(StringUtil.Format(CatalogStrings.AddFileToCatalog, itemName, fileRelativePath)); - break; - } + // Found the attribute we are looking for + // Filename we read from the above API has appended to its name as per CDF file tags convention + // Truncating that Information from the string. + string itemName = fileName.Substring(6); + _cmdlet.WriteVerbose(StringUtil.Format(CatalogStrings.AddFileToCatalog, itemName, fileRelativePath)); + break; } - } while (memberAttr != IntPtr.Zero); - } - } while (fileName != null); - } - finally - { - NativeMethods.CryptCATCDFClose(resultCDF); - } - } - else - { - // If we are not able to open CDF file we can not continue generating catalog - ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(CatalogStrings.UnableToOpenCatalogDefinitionFile), "UnableToOpenCatalogDefinitionFile", ErrorCategory.InvalidOperation, null); - _cmdlet.ThrowTerminatingError(errorRecord); + } + } while (memberAttr != IntPtr.Zero); + } + } while (fileName != null); } } @@ -374,7 +372,7 @@ internal static string ProcessFilePathAttributeInCatalog(IntPtr memberAttrInfo) { string relativePath = string.Empty; - NativeMethods.CRYPTCATATTRIBUTE currentMemberAttr = Marshal.PtrToStructure(memberAttrInfo); + WinTrustMethods.CRYPTCATATTRIBUTE currentMemberAttr = Marshal.PtrToStructure(memberAttrInfo); // check if this is the attribute we are looking for // catalog generated other way not using New-FileCatalog can have attributes we don't understand @@ -400,69 +398,65 @@ internal static string ProcessFilePathAttributeInCatalog(IntPtr memberAttrInfo) internal static string CalculateFileHash(string filePath, string hashAlgorithm) { string hashValue = string.Empty; - IntPtr catAdmin = IntPtr.Zero; // To get handle to the hash algorithm to be used to calculate hashes - if (!NativeMethods.CryptCATAdminAcquireContext2(ref catAdmin, IntPtr.Zero, hashAlgorithm, IntPtr.Zero, 0)) + SafeCATAdminHandle catAdmin; + try { - ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToAcquireHashAlgorithmContext, hashAlgorithm)), "UnableToAcquireHashAlgorithmContext", ErrorCategory.InvalidOperation, null); - _cmdlet.ThrowTerminatingError(errorRecord); + catAdmin = WinTrustMethods.CryptCATAdminAcquireContext2(hashAlgorithm); } + catch (Win32Exception e) + { + ErrorRecord errorRecord = new ErrorRecord( + new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToAcquireHashAlgorithmContext, hashAlgorithm), e), + "UnableToAcquireHashAlgorithmContext", + ErrorCategory.InvalidOperation, + null); + _cmdlet.ThrowTerminatingError(errorRecord); - const DWORD GENERIC_READ = 0x80000000; - const DWORD OPEN_EXISTING = 3; - IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); + // The method returns an empty string on a failure. + return hashValue; + } // Open the file that is to be hashed for reading and get its handle - IntPtr fileHandle = NativeMethods.CreateFile(filePath, GENERIC_READ, 0, 0, OPEN_EXISTING, 0, IntPtr.Zero); - if (fileHandle != INVALID_HANDLE_VALUE) + FileStream fileStream; + try { - try - { - DWORD hashBufferSize = 0; - IntPtr hashBuffer = IntPtr.Zero; - - // Call first time to get the size of expected buffer to hold new hash value - if (!NativeMethods.CryptCATAdminCalcHashFromFileHandle2(catAdmin, fileHandle, ref hashBufferSize, hashBuffer, 0)) - { - ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToCreateFileHash, filePath)), "UnableToCreateFileHash", ErrorCategory.InvalidOperation, null); - _cmdlet.ThrowTerminatingError(errorRecord); - } + fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); + } + catch (Exception e) + { + // If we are not able to open file that is to be hashed we can not continue with catalog validation + ErrorRecord errorRecord = new ErrorRecord( + new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToReadFileToHash, filePath), e), + "UnableToReadFileToHash", + ErrorCategory.InvalidOperation, + null); + _cmdlet.ThrowTerminatingError(errorRecord); - int size = (int)hashBufferSize; - hashBuffer = Marshal.AllocHGlobal(size); - try - { - // Call second time to actually get the hash value - if (!NativeMethods.CryptCATAdminCalcHashFromFileHandle2(catAdmin, fileHandle, ref hashBufferSize, hashBuffer, 0)) - { - ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToCreateFileHash, filePath)), "UnableToCreateFileHash", ErrorCategory.InvalidOperation, null); - _cmdlet.ThrowTerminatingError(errorRecord); - } + // The method returns an empty string on a failure. + return hashValue; + } - byte[] hashBytes = new byte[size]; - Marshal.Copy(hashBuffer, hashBytes, 0, size); - hashValue = BitConverter.ToString(hashBytes).Replace("-", string.Empty); - } - finally - { - if (hashBuffer != IntPtr.Zero) - { - Marshal.FreeHGlobal(hashBuffer); - } - } + using (catAdmin) + using (fileStream) + { + byte[] hashBytes = Array.Empty(); + try + { + hashBytes = WinTrustMethods.CryptCATAdminCalcHashFromFileHandle2(catAdmin, fileStream.SafeFileHandle); } - finally + catch (Win32Exception e) { - NativeMethods.CryptCATAdminReleaseContext(catAdmin, 0); - NativeMethods.CloseHandle(fileHandle); + ErrorRecord errorRecord = new ErrorRecord( + new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToCreateFileHash, filePath), e), + "UnableToCreateFileHash", + ErrorCategory.InvalidOperation, + null); + _cmdlet.ThrowTerminatingError(errorRecord); } - } - else - { - // If we are not able to open file that is to be hashed we can not continue with catalog validation - ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToReadFileToHash, filePath)), "UnableToReadFileToHash", ErrorCategory.InvalidOperation, null); - _cmdlet.ThrowTerminatingError(errorRecord); + + hashValue = Convert.ToHexString(hashBytes); } return hashValue; @@ -477,90 +471,92 @@ internal static string CalculateFileHash(string filePath, string hashAlgorithm) /// Dictionary mapping files relative paths to HashValues. internal static Dictionary GetHashesFromCatalog(string catalogFilePath, WildcardPattern[] excludedPatterns, out int catalogVersion) { - IntPtr resultCatalog = NativeMethods.CryptCATOpen(catalogFilePath, 0, IntPtr.Zero, 1, 0); - IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); Dictionary catalogHashes = new Dictionary(StringComparer.CurrentCultureIgnoreCase); catalogVersion = 0; - if (resultCatalog != INVALID_HANDLE_VALUE) + SafeCATHandle resultCatalog; + try { - try + resultCatalog = WinTrustMethods.CryptCATOpen(catalogFilePath, 0, IntPtr.Zero, 1, 0); + } + catch (Win32Exception e) + { + ErrorRecord errorRecord = new ErrorRecord( + new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToOpenCatalogFile, catalogFilePath), e), + "UnableToOpenCatalogFile", + ErrorCategory.InvalidOperation, + null); + _cmdlet.ThrowTerminatingError(errorRecord); + return catalogHashes; + } + + using (resultCatalog) + { + IntPtr catAttrInfo = IntPtr.Zero; + + // First traverse all catalog level attributes to get information about zero size file. + do { - IntPtr catAttrInfo = IntPtr.Zero; + catAttrInfo = WinTrustMethods.CryptCATEnumerateCatAttr(resultCatalog, catAttrInfo); - // First traverse all catalog level attributes to get information about zero size file. - do + // If we found attribute it is a file information retrieve its relative path + // and add it to catalog hash collection if its not in excluded files criteria + if (catAttrInfo != IntPtr.Zero) { - catAttrInfo = NativeMethods.CryptCATEnumerateCatAttr(resultCatalog, catAttrInfo); - - // If we found attribute it is a file information retrieve its relative path - // and add it to catalog hash collection if its not in excluded files criteria - if (catAttrInfo != IntPtr.Zero) + string relativePath = ProcessFilePathAttributeInCatalog(catAttrInfo); + if (!string.IsNullOrEmpty(relativePath)) { - string relativePath = ProcessFilePathAttributeInCatalog(catAttrInfo); - if (!string.IsNullOrEmpty(relativePath)) - { - ProcessCatalogFile(relativePath, string.Empty, excludedPatterns, ref catalogHashes); - } + ProcessCatalogFile(relativePath, string.Empty, excludedPatterns, ref catalogHashes); } - } while (catAttrInfo != IntPtr.Zero); + } + } while (catAttrInfo != IntPtr.Zero); - catalogVersion = GetCatalogVersion(resultCatalog); + catalogVersion = GetCatalogVersion(resultCatalog); - IntPtr memberInfo = IntPtr.Zero; - // Next Navigate all members in Catalog files and get their relative paths and hashes - do + IntPtr memberInfo = IntPtr.Zero; + // Next Navigate all members in Catalog files and get their relative paths and hashes + do + { + memberInfo = WinTrustMethods.CryptCATEnumerateMember(resultCatalog, memberInfo); + if (memberInfo != IntPtr.Zero) { - memberInfo = NativeMethods.CryptCATEnumerateMember(resultCatalog, memberInfo); - if (memberInfo != IntPtr.Zero) - { - NativeMethods.CRYPTCATMEMBER currentMember = Marshal.PtrToStructure(memberInfo); - NativeMethods.SIP_INDIRECT_DATA pIndirectData = Marshal.PtrToStructure(currentMember.pIndirectData); + WinTrustMethods.CRYPTCATMEMBER currentMember = Marshal.PtrToStructure(memberInfo); + WinTrustMethods.SIP_INDIRECT_DATA pIndirectData = Marshal.PtrToStructure(currentMember.pIndirectData); - // For Catalog version 2 CryptoAPI puts hashes of file attributes(relative path in our case) in Catalog as well - // We validate those along with file hashes so we are skipping duplicate entries - if (!((catalogVersion == 2) && (pIndirectData.DigestAlgorithm.pszObjId.Equals(new Oid("SHA1").Value, StringComparison.OrdinalIgnoreCase)))) + // For Catalog version 2 CryptoAPI puts hashes of file attributes(relative path in our case) in Catalog as well + // We validate those along with file hashes so we are skipping duplicate entries + if (!((catalogVersion == 2) && (pIndirectData.DigestAlgorithm.pszObjId.Equals(new Oid("SHA1").Value, StringComparison.OrdinalIgnoreCase)))) + { + string relativePath = string.Empty; + IntPtr memberAttrInfo = IntPtr.Zero; + do { - string relativePath = string.Empty; - IntPtr memberAttrInfo = IntPtr.Zero; - do - { - memberAttrInfo = NativeMethods.CryptCATEnumerateAttr(resultCatalog, memberInfo, memberAttrInfo); + memberAttrInfo = WinTrustMethods.CryptCATEnumerateAttr(resultCatalog, memberInfo, memberAttrInfo); - if (memberAttrInfo != IntPtr.Zero) + if (memberAttrInfo != IntPtr.Zero) + { + relativePath = ProcessFilePathAttributeInCatalog(memberAttrInfo); + if (!string.IsNullOrEmpty(relativePath)) { - relativePath = ProcessFilePathAttributeInCatalog(memberAttrInfo); - if (!string.IsNullOrEmpty(relativePath)) - { - break; - } + break; } } - while (memberAttrInfo != IntPtr.Zero); - - // If we did not find any Relative Path for the item in catalog we should quit - // This catalog must not be valid for our use as catalogs generated using New-FileCatalog - // always contains relative file Paths - if (string.IsNullOrEmpty(relativePath)) - { - ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToOpenCatalogFile, catalogFilePath)), "UnableToOpenCatalogFile", ErrorCategory.InvalidOperation, null); - _cmdlet.ThrowTerminatingError(errorRecord); - } + } + while (memberAttrInfo != IntPtr.Zero); - ProcessCatalogFile(relativePath, currentMember.pwszReferenceTag, excludedPatterns, ref catalogHashes); + // If we did not find any Relative Path for the item in catalog we should quit + // This catalog must not be valid for our use as catalogs generated using New-FileCatalog + // always contains relative file Paths + if (string.IsNullOrEmpty(relativePath)) + { + ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToOpenCatalogFile, catalogFilePath)), "UnableToOpenCatalogFile", ErrorCategory.InvalidOperation, null); + _cmdlet.ThrowTerminatingError(errorRecord); } + + ProcessCatalogFile(relativePath, currentMember.pwszReferenceTag, excludedPatterns, ref catalogHashes); } - } while (memberInfo != IntPtr.Zero); - } - finally - { - NativeMethods.CryptCATClose(resultCatalog); - } - } - else - { - ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.UnableToOpenCatalogFile, catalogFilePath)), "UnableToOpenCatalogFile", ErrorCategory.InvalidOperation, null); - _cmdlet.ThrowTerminatingError(errorRecord); + } + } while (memberInfo != IntPtr.Zero); } return catalogHashes; @@ -692,8 +688,8 @@ internal static bool CompareDictionaries(Dictionary catalogItems List relativePathsFromFolder = pathItems.Keys.ToList(); List relativePathsFromCatalog = catalogItems.Keys.ToList(); - // Find entires those are not in both list lists. These should be empty lists for success - // Hashes in Catalog should be exact similar to the ones from folder + // Find entries that are not in both lists. These should be empty lists for success + // Hashes in Catalog should be exactly similar to the ones from folder List relativePathsNotInFolder = relativePathsFromFolder.Except(relativePathsFromCatalog, StringComparer.CurrentCultureIgnoreCase).ToList(); List relativePathsNotInCatalog = relativePathsFromCatalog.Except(relativePathsFromFolder, StringComparer.CurrentCultureIgnoreCase).ToList(); @@ -785,14 +781,18 @@ internal static bool CheckExcludedCriteria(string filename, WildcardPattern[] ex /// /// Call back when error is thrown by catalog API's. /// - private static void ParseErrorCallback(DWORD dwErrorArea, DWORD dwLocalError, string pwszLine) + private static void ParseErrorCallback(uint dwErrorArea, uint dwLocalError, string pwszLine) { switch (dwErrorArea) { - case NativeConstants.CRYPTCAT_E_AREA_HEADER: break; - case NativeConstants.CRYPTCAT_E_AREA_MEMBER: break; - case NativeConstants.CRYPTCAT_E_AREA_ATTRIBUTE: break; - default: break; + case NativeConstants.CRYPTCAT_E_AREA_HEADER: + break; + case NativeConstants.CRYPTCAT_E_AREA_MEMBER: + break; + case NativeConstants.CRYPTCAT_E_AREA_ATTRIBUTE: + break; + default: + break; } switch (dwLocalError) @@ -815,18 +815,24 @@ private static void ParseErrorCallback(DWORD dwErrorArea, DWORD dwLocalError, st _cmdlet.ThrowTerminatingError(errorRecord); break; } - case NativeConstants.CRYPTCAT_E_CDF_BAD_GUID_CONV: break; - case NativeConstants.CRYPTCAT_E_CDF_ATTR_TYPECOMBO: break; - case NativeConstants.CRYPTCAT_E_CDF_ATTR_TOOFEWVALUES: break; - case NativeConstants.CRYPTCAT_E_CDF_UNSUPPORTED: break; + case NativeConstants.CRYPTCAT_E_CDF_BAD_GUID_CONV: + break; + case NativeConstants.CRYPTCAT_E_CDF_ATTR_TYPECOMBO: + break; + case NativeConstants.CRYPTCAT_E_CDF_ATTR_TOOFEWVALUES: + break; + case NativeConstants.CRYPTCAT_E_CDF_UNSUPPORTED: + break; case NativeConstants.CRYPTCAT_E_CDF_DUPLICATE: { ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(CatalogStrings.FoundDuplicateFileMemberInCatalog, pwszLine)), "FoundDuplicateFileMemberInCatalog", ErrorCategory.InvalidOperation, null); _cmdlet.ThrowTerminatingError(errorRecord); break; } - case NativeConstants.CRYPTCAT_E_CDF_TAGNOTFOUND: break; - default: break; + case NativeConstants.CRYPTCAT_E_CDF_TAGNOTFOUND: + break; + default: + break; } } } diff --git a/src/System.Management.Automation/security/MshSignature.cs b/src/System.Management.Automation/security/MshSignature.cs index 96ff8a9638a..fd8dd4f67ef 100644 --- a/src/System.Management.Automation/security/MshSignature.cs +++ b/src/System.Management.Automation/security/MshSignature.cs @@ -110,7 +110,7 @@ public sealed class Signature // Three states: // - True: we can rely on the catalog API to check catalog signature. - // - False: we cannot rely on the catalog API, either because it doesn't exist in the OS (win7), + // - False: we cannot rely on the catalog API, either because it doesn't exist in the OS (win7, nano), // or it's not working properly (OneCore SKUs or dev environment where powershell might // be updated/refreshed). // - Null: it's not determined yet whether catalog API can be relied on or not. diff --git a/src/System.Management.Automation/security/SecureStringHelper.cs b/src/System.Management.Automation/security/SecureStringHelper.cs index 4611463ae0e..2f7c991abc4 100644 --- a/src/System.Management.Automation/security/SecureStringHelper.cs +++ b/src/System.Management.Automation/security/SecureStringHelper.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Management.Automation; @@ -67,7 +68,6 @@ private static SecureString New(byte[] data) /// /// Input string. /// Contents of s (char[]) converted to byte[]. - [ArchitectureSensitive] internal static byte[] GetData(SecureString s) { // @@ -216,8 +216,6 @@ internal static SecureString Unprotect(string input) /// A string (see summary). internal static EncryptionResult Encrypt(SecureString input, SecureString key) { - EncryptionResult output = null; - // // get clear text key from the SecureString key // @@ -226,14 +224,14 @@ internal static EncryptionResult Encrypt(SecureString input, SecureString key) // // encrypt the data // - output = Encrypt(input, keyBlob); - - // - // clear the clear text key - // - Array.Clear(keyBlob, 0, keyBlob.Length); - - return output; + try + { + return Encrypt(input, keyBlob); + } + finally + { + Array.Clear(keyBlob); + } } /// @@ -253,48 +251,43 @@ internal static EncryptionResult Encrypt(SecureString input, byte[] key, byte[] Utils.CheckSecureStringArg(input, "input"); Utils.CheckKeyArg(key, "key"); - byte[] encryptedData = null; - MemoryStream ms = null; - ICryptoTransform encryptor = null; - CryptoStream cs = null; - // // prepare the crypto stuff. Initialization Vector is // randomized by default. // - Aes aes = Aes.Create(); - if (iv == null) - iv = aes.IV; - - encryptor = aes.CreateEncryptor(key, iv); - ms = new MemoryStream(); - - using (cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) + using (Aes aes = Aes.Create()) { + iv ??= aes.IV; + // // get clear text data from the input SecureString // byte[] data = GetData(input); + try + { + using (ICryptoTransform encryptor = aes.CreateEncryptor(key, iv)) + using (var sourceStream = new MemoryStream(data)) + using (var encryptedStream = new MemoryStream()) + { + // + // encrypt it + // + using (var cryptoStream = new CryptoStream(encryptedStream, encryptor, CryptoStreamMode.Write)) + { + sourceStream.CopyTo(cryptoStream); + } - // - // encrypt it - // - cs.Write(data, 0, data.Length); - cs.FlushFinalBlock(); - - // - // clear the clear text data array - // - Array.Clear(data, 0, data.Length); - - // - // convert the encrypted blob to a string - // - encryptedData = ms.ToArray(); - - EncryptionResult output = new EncryptionResult(ByteArrayToString(encryptedData), Convert.ToBase64String(iv)); - - return output; + // + // return encrypted data + // + byte[] encryptedData = encryptedStream.ToArray(); + return new EncryptionResult(ByteArrayToString(encryptedData), Convert.ToBase64String(iv)); + } + } + finally + { + Array.Clear(data, 0, data.Length); + } } } @@ -310,8 +303,6 @@ internal static EncryptionResult Encrypt(SecureString input, byte[] key, byte[] /// SecureString . internal static SecureString Decrypt(string input, SecureString key, byte[] IV) { - SecureString output = null; - // // get clear text key from the SecureString key // @@ -320,14 +311,14 @@ internal static SecureString Decrypt(string input, SecureString key, byte[] IV) // // decrypt the data // - output = Decrypt(input, keyBlob, IV); - - // - // clear the clear text key - // - Array.Clear(keyBlob, 0, keyBlob.Length); - - return output; + try + { + return Decrypt(input, keyBlob, IV); + } + finally + { + Array.Clear(keyBlob); + } } /// @@ -345,46 +336,55 @@ internal static SecureString Decrypt(string input, byte[] key, byte[] IV) Utils.CheckArgForNullOrEmpty(input, "input"); Utils.CheckKeyArg(key, "key"); - byte[] decryptedData = null; - byte[] encryptedData = null; - SecureString s = null; - // // prepare the crypto stuff // - Aes aes = Aes.Create(); - encryptedData = ByteArrayFromString(input); - - var decryptor = aes.CreateDecryptor(key, IV ?? aes.IV); - - MemoryStream ms = new MemoryStream(encryptedData); - - using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) + using (var aes = Aes.Create()) { - byte[] tempDecryptedData = new byte[encryptedData.Length]; - - int numBytesRead = 0; - - // - // decrypt the data - // - numBytesRead = cs.Read(tempDecryptedData, 0, - tempDecryptedData.Length); - - decryptedData = new byte[numBytesRead]; - - for (int i = 0; i < numBytesRead; i++) + using (ICryptoTransform decryptor = aes.CreateDecryptor(key, IV ?? aes.IV)) + using (var encryptedStream = new MemoryStream(ByteArrayFromString(input))) + using (var targetStream = new MemoryStream()) { - decryptedData[i] = tempDecryptedData[i]; + // + // decrypt the data and return as SecureString + // + using (var sourceStream = new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read)) + { + sourceStream.CopyTo(targetStream); + } + + byte[] decryptedData = targetStream.ToArray(); + try + { + return New(decryptedData); + } + finally + { + Array.Clear(decryptedData); + } } + } + } - s = New(decryptedData); - Array.Clear(decryptedData, 0, decryptedData.Length); - Array.Clear(tempDecryptedData, 0, tempDecryptedData.Length); +#nullable enable + /// Creates a new from a . + /// Plain text string. Must not be null. + /// A new SecureString. + internal static unsafe SecureString FromPlainTextString(string plainTextString) + { + Debug.Assert(plainTextString is not null); - return s; + if (plainTextString.Length == 0) + { + return new SecureString(); + } + + fixed (char* charsPtr = plainTextString) + { + return new SecureString(charsPtr, plainTextString.Length); } } +#nullable restore } /// @@ -430,10 +430,7 @@ internal static class ProtectedData /// public static byte[] Protect(byte[] userData, byte[] optionalEntropy, DataProtectionScope scope) { - if (userData == null) - { - throw new ArgumentNullException(nameof(userData)); - } + ArgumentNullException.ThrowIfNull(userData); GCHandle pbDataIn = new GCHandle(); GCHandle pOptionalEntropy = new GCHandle(); @@ -518,10 +515,7 @@ public static byte[] Protect(byte[] userData, byte[] optionalEntropy, DataProtec /// public static byte[] Unprotect(byte[] encryptedData, byte[] optionalEntropy, DataProtectionScope scope) { - if (encryptedData == null) - { - throw new ArgumentNullException(nameof(encryptedData)); - } + ArgumentNullException.ThrowIfNull(encryptedData); GCHandle pbDataIn = new GCHandle(); GCHandle pOptionalEntropy = new GCHandle(); diff --git a/src/System.Management.Automation/security/SecurityManager.cs b/src/System.Management.Automation/security/SecurityManager.cs index 5d7295354be..137daecc5b4 100644 --- a/src/System.Management.Automation/security/SecurityManager.cs +++ b/src/System.Management.Automation/security/SecurityManager.cs @@ -19,7 +19,7 @@ namespace Microsoft.PowerShell { /// /// Defines the authorization policy that controls the way scripts - /// (and other command types) are handled by Monad. This authorization + /// (and other command types) are handled by PowerShell. This authorization /// policy enforces one of four levels, as defined by the 'ExecutionPolicy' /// value in one of the following locations: /// @@ -40,14 +40,14 @@ namespace Microsoft.PowerShell /// signed, and by a trusted publisher. If you haven't made a trust decision /// on the publisher yet, prompting is done as in AllSigned mode. /// AllSigned - All .ps1 and .ps1xml files must be digitally signed. If - /// signed and executed, Monad prompts to determine if files from the + /// signed and executed, PowerShell prompts to determine if files from the /// signing publisher should be run or not. /// RemoteSigned - Only .ps1 and .ps1xml files originating from the internet - /// must be digitally signed. If remote, signed, and executed, Monad + /// must be digitally signed. If remote, signed, and executed, PowerShell /// prompts to determine if files from the signing publisher should be /// run or not. This is the default setting. /// Unrestricted - No files must be signed. If a file originates from the - /// internet, Monad provides a warning prompt to alert the user. To + /// internet, PowerShell provides a warning prompt to alert the user. To /// suppress this warning message, right-click on the file in File Explorer, /// select "Properties," and then "Unblock." Requires Shell. /// Bypass - No files must be signed, and internet origin is not verified. @@ -69,7 +69,7 @@ internal enum RunPromptDecision private ExecutionPolicy _executionPolicy; // shellId supplied by runspace configuration - private string _shellId; + private readonly string _shellId; /// /// Initializes a new instance of the PSAuthorizationManager @@ -160,7 +160,10 @@ private bool CheckPolicy(ExternalScriptInfo script, PSHost host, out Exception r } catch (System.ComponentModel.Win32Exception) { - if (saferAttempt > 4) { throw; } + if (saferAttempt > 4) + { + throw; + } saferAttempt++; System.Threading.Thread.Sleep(100); @@ -180,7 +183,7 @@ private bool CheckPolicy(ExternalScriptInfo script, PSHost host, out Exception r } } - // WLDP and Applocker takes priority over powershell exeuction policy. + // WLDP and Applocker takes priority over powershell execution policy. // See if they want to bypass the authorization manager if (_executionPolicy == ExecutionPolicy.Bypass) { @@ -328,9 +331,10 @@ private bool CheckPolicy(ExternalScriptInfo script, PSHost host, out Exception r if (string.Equals(fi.Extension, ".ps1xml", StringComparison.OrdinalIgnoreCase)) { string[] trustedDirectories = new string[] - { Platform.GetFolderPath(Environment.SpecialFolder.System), - Platform.GetFolderPath(Environment.SpecialFolder.ProgramFiles) - }; + { + Environment.GetFolderPath(Environment.SpecialFolder.System), + Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + }; foreach (string trustedDirectory in trustedDirectories) { @@ -442,7 +446,12 @@ private static bool IsTrustedPublisher(Signature signature, string file) foreach (X509Certificate2 trustedCertificate in trustedPublishers.Certificates) { if (string.Equals(trustedCertificate.Thumbprint, thumbprint, StringComparison.OrdinalIgnoreCase)) - if (!IsUntrustedPublisher(signature, file)) return true; + { + if (!IsUntrustedPublisher(signature, file)) + { + return true; + } + } } return false; @@ -533,16 +542,16 @@ private static Signature GetSignatureWithEncodingRetry(string path, ExternalScri // try harder to validate the signature by being explicit about encoding // and providing the script contents - string verificationContents = Encoding.Unicode.GetString(script.OriginalEncoding.GetPreamble()) + script.ScriptContents; - signature = SignatureHelper.GetSignature(path, verificationContents); + byte[] bytesWithBom = GetContentBytesWithBom(script.OriginalEncoding, script.ScriptContents); + signature = SignatureHelper.GetSignature(path, bytesWithBom); // A last ditch effort - // If the file was originally ASCII or UTF8, the SIP may have added the Unicode BOM if (signature.Status != SignatureStatus.Valid && script.OriginalEncoding != Encoding.Unicode) { - verificationContents = Encoding.Unicode.GetString(Encoding.Unicode.GetPreamble()) + script.ScriptContents; - Signature fallbackSignature = SignatureHelper.GetSignature(path, verificationContents); + bytesWithBom = GetContentBytesWithBom(Encoding.Unicode, script.ScriptContents); + Signature fallbackSignature = SignatureHelper.GetSignature(path, bytesWithBom); if (fallbackSignature.Status == SignatureStatus.Valid) signature = fallbackSignature; @@ -551,6 +560,17 @@ private static Signature GetSignatureWithEncodingRetry(string path, ExternalScri return signature; } + private static byte[] GetContentBytesWithBom(Encoding encoding, string scriptContent) + { + ReadOnlySpan bomBytes = encoding.Preamble; + byte[] contentBytes = encoding.GetBytes(scriptContent); + byte[] bytesWithBom = new byte[bomBytes.Length + contentBytes.Length]; + + bomBytes.CopyTo(bytesWithBom); + contentBytes.CopyTo(bytesWithBom, index: bomBytes.Length); + return bytesWithBom; + } + #endregion signing check /// @@ -631,17 +651,23 @@ protected internal override bool ShouldRun(CommandInfo commandInfo, break; case CommandTypes.ExternalScript: - ExternalScriptInfo si = commandInfo as ExternalScriptInfo; - if (si == null) + if (commandInfo is not ExternalScriptInfo si) { reason = PSTraceSource.NewArgumentException("scriptInfo"); } else { bool etwEnabled = ParserEventSource.Log.IsEnabled(); - if (etwEnabled) ParserEventSource.Log.CheckSecurityStart(si.Path); + if (etwEnabled) + { + ParserEventSource.Log.CheckSecurityStart(si.Path); + } + allowRun = CheckPolicy(si, host, out reason); - if (etwEnabled) ParserEventSource.Log.CheckSecurityStop(si.Path); + if (etwEnabled) + { + ParserEventSource.Log.CheckSecurityStop(si.Path); + } } break; diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs index 4afb15be55d..e6a6f10416b 100644 --- a/src/System.Management.Automation/security/SecuritySupport.cs +++ b/src/System.Management.Automation/security/SecuritySupport.cs @@ -32,23 +32,23 @@ namespace Microsoft.PowerShell public enum ExecutionPolicy { /// Unrestricted - No files must be signed. If a file originates from the - /// internet, Monad provides a warning prompt to alert the user. To + /// internet, PowerShell provides a warning prompt to alert the user. To /// suppress this warning message, right-click on the file in File Explorer, /// select "Properties," and then "Unblock." Unrestricted = 0, - /// RemoteSigned - Only .msh and .mshxml files originating from the internet - /// must be digitally signed. If remote, signed, and executed, Monad + /// RemoteSigned - Only .ps1 and .ps1xml files originating from the internet + /// must be digitally signed. If remote, signed, and executed, PowerShell /// prompts to determine if files from the signing publisher should be /// run or not. This is the default setting. RemoteSigned = 1, - /// AllSigned - All .msh and .mshxml files must be digitally signed. If - /// signed and executed, Monad prompts to determine if files from the + /// AllSigned - All .ps1 and .ps1xml files must be digitally signed. If + /// signed and executed, PowerShell prompts to determine if files from the /// signing publisher should be run or not. AllSigned = 2, - /// Restricted - All .msh files are blocked. Mshxml files must be digitally + /// Restricted - All .ps1 files are blocked. Ps1xml files must be digitally /// signed, and by a trusted publisher. If you haven't made a trust decision /// on the publisher yet, prompting is done as in AllSigned mode. Restricted = 3, @@ -248,7 +248,7 @@ private static bool IsCurrentProcessLaunchedByGpScript() while (currentProcess != null) { if (string.Equals(gpScriptPath, - PsUtils.GetMainModule(currentProcess).FileName, StringComparison.OrdinalIgnoreCase)) + currentProcess.MainModule.FileName, StringComparison.OrdinalIgnoreCase)) { foundGpScriptParent = true; break; @@ -412,7 +412,7 @@ public static bool IsProductBinary(string file) return true; } - // WTGetSignatureInfo is used to verify catalog signature. + // WTGetSignatureInfo, via Microsoft.Security.Extensions, is used to verify catalog signature. // On Win7, catalog API is not available. // On OneCore SKUs like NanoServer/IoT, the API has a bug that makes it not able to find the // corresponding catalog file for a given product file, so it doesn't work properly. @@ -495,7 +495,6 @@ private static string GetLocalPreferenceValue(string shellId, ExecutionPolicySco /// /// The path to the file in question. /// A file handle to the file in question, if available. - [ArchitectureSensitive] [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")] internal static SaferPolicy GetSaferPolicy(string path, SafeHandle handle) { @@ -664,8 +663,7 @@ private static bool CertHasKeyUsage(X509Certificate2 c, X509KeyUsageFlags keyUsa { foreach (X509Extension extension in c.Extensions) { - X509KeyUsageExtension keyUsageExtension = extension as X509KeyUsageExtension; - if (keyUsageExtension != null) + if (extension is X509KeyUsageExtension keyUsageExtension) { if ((keyUsageExtension.KeyUsages & keyUsage) == keyUsage) { @@ -682,7 +680,6 @@ private static bool CertHasKeyUsage(X509Certificate2 c, X509KeyUsageFlags keyUsa /// /// Certificate object. /// A collection of cert eku strings. - [ArchitectureSensitive] internal static Collection GetCertEKU(X509Certificate2 cert) { Collection ekus = new Collection(); @@ -856,6 +853,7 @@ internal enum CertificatePurpose namespace System.Management.Automation { + using System.Management.Automation.Tracing; using System.Security.Cryptography.Pkcs; /// @@ -995,7 +993,7 @@ public CmsMessageRecipient(string identifier) this.Certificates = new X509Certificate2Collection(); } - private string _identifier = null; + private readonly string _identifier; /// /// Creates an instance of the CmsMessageRecipient class. @@ -1007,7 +1005,7 @@ public CmsMessageRecipient(X509Certificate2 certificate) this.Certificates = new X509Certificate2Collection(); } - private X509Certificate2 _pendingCertificate = null; + private readonly X509Certificate2 _pendingCertificate; /// /// Gets the certificate associated with this recipient. @@ -1106,7 +1104,10 @@ private void ResolveFromBase64Encoding(ResolutionPurpose purpose, out ErrorRecor var certificatesToProcess = new X509Certificate2Collection(); try { + #pragma warning disable SYSLIB0057 X509Certificate2 newCertificate = new X509Certificate2(messageBytes); + #pragma warning restore SYSLIB0057 + certificatesToProcess.Add(newCertificate); } catch (Exception) @@ -1184,7 +1185,9 @@ private void ResolveFromPath(SessionState sessionState, ResolutionPurpose purpos try { + #pragma warning disable SYSLIB0057 certificate = new X509Certificate2(path); + #pragma warning restore SYSLIB0057 } catch (Exception) { @@ -1213,7 +1216,7 @@ private void ResolveFromStoreById(ResolutionPurpose purpose, out ErrorRecord err storeCU.Open(OpenFlags.ReadOnly); X509Certificate2Collection storeCerts = storeCU.Certificates; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (Platform.IsWindows) { using (var storeLM = new X509Store("my", StoreLocation.LocalMachine)) { @@ -1336,9 +1339,22 @@ public enum ResolutionPurpose internal static class AmsiUtils { - private static string GetProcessHostName(string processName) + static AmsiUtils() { - return string.Concat("PowerShell_", processName, ".exe_0.0.0.0"); +#if !UNIX + try + { + s_amsiInitFailed = !CheckAmsiInit(); + } + catch (DllNotFoundException) + { + PSEtwLog.LogAmsiUtilStateEvent("DllNotFoundException", $"{s_amsiContext}-{s_amsiSession}"); + s_amsiInitFailed = true; + return; + } + + PSEtwLog.LogAmsiUtilStateEvent($"init-{s_amsiInitFailed}", $"{s_amsiContext}-{s_amsiSession}"); +#endif } internal static int Init() @@ -1347,34 +1363,21 @@ internal static int Init() lock (s_amsiLockObject) { - Process currentProcess = Process.GetCurrentProcess(); - string hostname; + string appName; try { - var processModule = PsUtils.GetMainModule(currentProcess); - hostname = string.Concat("PowerShell_", processModule.FileName, "_", - processModule.FileVersionInfo.ProductVersion); - } - catch (ComponentModel.Win32Exception) - { - // This exception can be thrown during thread impersonation (Access Denied for process module access). - hostname = GetProcessHostName(currentProcess.ProcessName); + appName = string.Concat("PowerShell_", Environment.ProcessPath, "_", PSVersionInfo.ProductVersion); } - catch (FileNotFoundException) + catch (Exception) { - // This exception can occur if the file is renamed or moved to some other folder - // (This has occurred during Exchange set up). - hostname = GetProcessHostName(currentProcess.ProcessName); + // Fall back to 'Process.ProcessName' in case 'Environment.ProcessPath' throws exception. + Process currentProcess = Process.GetCurrentProcess(); + appName = string.Concat("PowerShell_", currentProcess.ProcessName, ".exe_", PSVersionInfo.ProductVersion); } AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; - var hr = AmsiNativeMethods.AmsiInitialize(hostname, ref s_amsiContext); - if (!Utils.Succeeded(hr)) - { - s_amsiInitFailed = true; - } - + var hr = AmsiNativeMethods.AmsiInitialize(appName, ref s_amsiContext); return hr; } } @@ -1396,7 +1399,10 @@ internal static AmsiNativeMethods.AMSI_RESULT ScanContent(string content, string #endif } - internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, string sourceMetadata, bool warmUp) + internal static AmsiNativeMethods.AMSI_RESULT WinScanContent( + string content, + string sourceMetadata, + bool warmUp) { if (string.IsNullOrEmpty(sourceMetadata)) { @@ -1415,6 +1421,7 @@ internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, str // If we had a previous initialization failure, just return the neutral result. if (s_amsiInitFailed) { + PSEtwLog.LogAmsiUtilStateEvent("ScanContent-InitFail", $"{s_amsiContext}-{s_amsiSession}"); return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; } @@ -1422,38 +1429,15 @@ internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, str { if (s_amsiInitFailed) { + PSEtwLog.LogAmsiUtilStateEvent("ScanContent-InitFail", $"{s_amsiContext}-{s_amsiSession}"); return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; } try { - int hr = 0; - - // Initialize AntiMalware Scan Interface, if not already initialized. - // If we failed to initialize previously, just return the neutral result ("AMSI_RESULT_NOT_DETECTED") - if (s_amsiContext == IntPtr.Zero) - { - hr = Init(); - - if (!Utils.Succeeded(hr)) - { - s_amsiInitFailed = true; - return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; - } - } - - // Initialize the session, if one isn't already started. - // If we failed to initialize previously, just return the neutral result ("AMSI_RESULT_NOT_DETECTED") - if (s_amsiSession == IntPtr.Zero) + if (!CheckAmsiInit()) { - hr = AmsiNativeMethods.AmsiOpenSession(s_amsiContext, ref s_amsiSession); - AmsiInitialized = true; - - if (!Utils.Succeeded(hr)) - { - s_amsiInitFailed = true; - return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; - } + return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; } if (warmUp) @@ -1466,6 +1450,7 @@ internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, str AmsiNativeMethods.AMSI_RESULT result = AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_CLEAN; // Run AMSI content scan + int hr; unsafe { fixed (char* buffer = content) @@ -1484,6 +1469,7 @@ internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, str if (!Utils.Succeeded(hr)) { // If we got a failure, just return the neutral result ("AMSI_RESULT_NOT_DETECTED") + PSEtwLog.LogAmsiUtilStateEvent($"AmsiScanBuffer-{hr}", $"{s_amsiContext}-{s_amsiSession}"); return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; } @@ -1491,12 +1477,127 @@ internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, str } catch (DllNotFoundException) { - s_amsiInitFailed = true; + PSEtwLog.LogAmsiUtilStateEvent("DllNotFoundException", $"{s_amsiContext}-{s_amsiSession}"); return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; } } } + /// + /// Reports provided content to AMSI (Antimalware Scan Interface). + /// + /// Name of content being reported. + /// Content being reported. + /// True if content was successfully reported. + internal static bool ReportContent( + string name, + string content) + { +#if UNIX + return false; +#else + return WinReportContent(name, content); +#endif + } + + private static bool WinReportContent( + string name, + string content) + { + if (string.IsNullOrEmpty(name) || + string.IsNullOrEmpty(content) || + s_amsiInitFailed || + s_amsiNotifyFailed) + { + return false; + } + + lock (s_amsiLockObject) + { + if (s_amsiNotifyFailed) + { + return false; + } + + try + { + if (!CheckAmsiInit()) + { + return false; + } + + int hr; + AmsiNativeMethods.AMSI_RESULT result = AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; + unsafe + { + fixed (char* buffer = content) + { + var buffPtr = new IntPtr(buffer); + hr = AmsiNativeMethods.AmsiNotifyOperation( + amsiContext: s_amsiContext, + buffer: buffPtr, + length: (uint)(content.Length * sizeof(char)), + contentName: name, + ref result); + } + } + + if (Utils.Succeeded(hr)) + { + if (result == AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_DETECTED) + { + // If malware is detected, throw to prevent method invoke expression from running. + throw new PSSecurityException(ParserStrings.ScriptContainedMaliciousContent); + } + + return true; + } + + return false; + } + catch (DllNotFoundException) + { + s_amsiNotifyFailed = true; + return false; + } + catch (System.EntryPointNotFoundException) + { + s_amsiNotifyFailed = true; + return false; + } + } + } + + private static bool CheckAmsiInit() + { + // Initialize AntiMalware Scan Interface, if not already initialized. + // If we failed to initialize previously, just return the neutral result ("AMSI_RESULT_NOT_DETECTED") + if (s_amsiContext == IntPtr.Zero) + { + int hr = Init(); + + if (!Utils.Succeeded(hr)) + { + return false; + } + } + + // Initialize the session, if one isn't already started. + // If we failed to initialize previously, just return the neutral result ("AMSI_RESULT_NOT_DETECTED") + if (s_amsiSession == IntPtr.Zero) + { + int hr = AmsiNativeMethods.AmsiOpenSession(s_amsiContext, ref s_amsiSession); + AmsiInitialized = true; + + if (!Utils.Succeeded(hr)) + { + return false; + } + } + + return true; + } + internal static void CurrentDomain_ProcessExit(object sender, EventArgs e) { if (AmsiInitialized && !AmsiUninitializeCalled) @@ -1511,8 +1612,9 @@ internal static void CurrentDomain_ProcessExit(object sender, EventArgs e) [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private static IntPtr s_amsiSession = IntPtr.Zero; - private static bool s_amsiInitFailed = false; - private static object s_amsiLockObject = new object(); + private static readonly bool s_amsiInitFailed = false; + private static bool s_amsiNotifyFailed = false; + private static readonly object s_amsiLockObject = new object(); /// /// Reset the AMSI session (used to track related script invocations) @@ -1601,29 +1703,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 @@ -1633,11 +1735,30 @@ 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, System.IntPtr amsiSession, ref AMSI_RESULT result); + System.IntPtr amsiContext, + System.IntPtr buffer, + uint length, + [In][MarshalAs(UnmanagedType.LPWStr)] string contentName, + System.IntPtr amsiSession, + ref AMSI_RESULT result); + + /// Return Type: HRESULT->LONG->int + /// amsiContext: HAMSICONTEXT->HAMSICONTEXT__* + /// buffer: PVOID->void* + /// length: ULONG->unsigned int + /// contentName: LPCWSTR->WCHAR* + /// result: AMSI_RESULT* + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [DllImport("amsi.dll", EntryPoint = "AmsiNotifyOperation", CallingConvention = CallingConvention.StdCall)] + internal static extern int AmsiNotifyOperation( + System.IntPtr amsiContext, + System.IntPtr buffer, + uint length, + [In][MarshalAs(UnmanagedType.LPWStr)] string contentName, + ref AMSI_RESULT result); /// Return Type: HRESULT->LONG->int ///amsiContext: HAMSICONTEXT->HAMSICONTEXT__* @@ -1645,11 +1766,11 @@ internal static extern int AmsiScanBuffer( ///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 new file mode 100644 index 00000000000..a0ae8bc0e99 --- /dev/null +++ b/src/System.Management.Automation/security/Win32Native/WinTrust.cs @@ -0,0 +1,440 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.ComponentModel; +using System.Runtime.InteropServices; + +namespace System.Management.Automation.Win32Native; + +internal class SafeCATAdminHandle : SafeHandle +{ + internal SafeCATAdminHandle() : base(IntPtr.Zero, true) { } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() => WinTrustMethods.CryptCATAdminReleaseContext(handle, 0); +} + +internal class SafeCATHandle : SafeHandle +{ + internal SafeCATHandle() : base(IntPtr.Zero, true) { } + + public override bool IsInvalid => handle == (IntPtr)(-1); + + protected override bool ReleaseHandle() => WinTrustMethods.CryptCATClose(handle); +} + +internal class SafeCATCDFHandle : SafeHandle +{ + internal SafeCATCDFHandle() : base(IntPtr.Zero, true) { } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() => WinTrustMethods.CryptCATCDFClose(handle); +} + +[Flags] +internal enum WinTrustUIChoice +{ + WTD_UI_ALL = 1, + WTD_UI_NONE = 2, + WTD_UI_NOBAD = 3, + WTD_UI_NOGOOD = 4 +} + +[Flags] +internal enum WinTrustUnionChoice +{ + WTD_CHOICE_FILE = 1, + WTD_CHOICE_CATALOG = 2, + WTD_CHOICE_BLOB = 3, + WTD_CHOICE_SIGNER = 4, + WTD_CHOICE_CERT = 5, +} + +[Flags] +internal enum WinTrustAction +{ + WTD_STATEACTION_IGNORE = 0x00000000, + WTD_STATEACTION_VERIFY = 0x00000001, + WTD_STATEACTION_CLOSE = 0x00000002, + WTD_STATEACTION_AUTO_CACHE = 0x00000003, + WTD_STATEACTION_AUTO_CACHE_FLUSH = 0x00000004 +} + +[Flags] +internal enum WinTrustProviderFlags +{ + WTD_PROV_FLAGS_MASK = 0x0000FFFF, + WTD_USE_IE4_TRUST_FLAG = 0x00000001, + WTD_NO_IE4_CHAIN_FLAG = 0x00000002, + WTD_NO_POLICY_USAGE_FLAG = 0x00000004, + WTD_REVOCATION_CHECK_NONE = 0x00000010, + WTD_REVOCATION_CHECK_END_CERT = 0x00000020, + WTD_REVOCATION_CHECK_CHAIN = 0x00000040, + WTD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT = 0x00000080, + WTD_SAFER_FLAG = 0x00000100, + WTD_HASH_ONLY_FLAG = 0x00000200, + WTD_USE_DEFAULT_OSVER_CHECK = 0x00000400, + WTD_LIFETIME_SIGNING_FLAG = 0x00000800, + WTD_CACHE_ONLY_URL_RETRIEVAL = 0x00001000 +} + +/// +/// Pinvoke methods from wintrust.dll +/// +internal static class WinTrustMethods +{ + private const string WinTrustDll = "wintrust.dll"; + + [StructLayout(LayoutKind.Sequential)] + internal struct CRYPT_ATTR_BLOB + { + public uint cbData; + public IntPtr pbData; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct CRYPT_ALGORITHM_IDENTIFIER + { + [MarshalAs(UnmanagedType.LPStr)] public string pszObjId; + public CRYPT_ATTR_BLOB Parameters; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct CRYPT_ATTRIBUTE_TYPE_VALUE + { + [MarshalAs(UnmanagedType.LPStr)] public string pszObjId; + public CRYPT_ATTR_BLOB Value; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct SIP_INDIRECT_DATA + { + public CRYPT_ATTRIBUTE_TYPE_VALUE Data; + public CRYPT_ALGORITHM_IDENTIFIER DigestAlgorithm; + public CRYPT_ATTR_BLOB Digest; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct CRYPTCATMEMBER + { + public uint cbStruct; + [MarshalAs(UnmanagedType.LPWStr)] public string pwszReferenceTag; + [MarshalAs(UnmanagedType.LPWStr)] public string pwszFileName; + public Guid gSubjectType; + public uint fdwMemberFlags; + public IntPtr pIndirectData; + public uint dwCertVersion; + public uint dwReserved; + public IntPtr hReserved; + public CRYPT_ATTR_BLOB sEncodedIndirectData; + public CRYPT_ATTR_BLOB sEncodedMemberInfo; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct CRYPTCATATTRIBUTE + { + public uint cbStruct; + [MarshalAs(UnmanagedType.LPWStr)] public string pwszReferenceTag; + public uint dwAttrTypeAndAction; + public uint cbValue; + public IntPtr pbValue; + public uint dwReserved; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct CRYPTCATSTORE + { + public uint cbStruct; + public uint dwPublicVersion; + [MarshalAs(UnmanagedType.LPWStr)] public string pwszP7File; + public IntPtr hProv; + public uint dwEncodingType; + public uint fdwStoreFlags; + public IntPtr hReserved; + public IntPtr hAttrs; + public IntPtr hCryptMsg; + public IntPtr hSorted; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct WINTRUST_DATA + { + public uint cbStruct; + public IntPtr pPolicyCallbackData; + public IntPtr pSIPClientData; + public WinTrustUIChoice dwUIChoice; + public uint fdwRevocationChecks; + public WinTrustUnionChoice dwUnionChoice; + public unsafe void* pChoice; + public WinTrustAction dwStateAction; + public IntPtr hWVTStateData; + public IntPtr pwszURLReference; + public WinTrustProviderFlags dwProvFlags; + public uint dwUIContext; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct WINTRUST_FILE_INFO + { + public uint cbStruct; + public unsafe char* pcwszFilePath; + public IntPtr hFile; + public IntPtr pgKnownSubject; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct WINTRUST_BLOB_INFO + { + public uint cbStruct; + public Guid gSubject; + public unsafe char* pcwszDisplayName; + public uint cbMemObject; + public unsafe byte* pbMemObject; + public uint cbMemSignedMsg; + public IntPtr pbMemSignedMsg; + } + + [DllImport( + WinTrustDll, + CharSet = CharSet.Unicode, + EntryPoint = "CryptCATAdminAcquireContext2", + SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool NativeCryptCATAdminAcquireContext2( + out SafeCATAdminHandle phCatAdmin, + IntPtr pgSubsystem, + [MarshalAs(UnmanagedType.LPWStr)] string pwszHashAlgorithm, + IntPtr pStrongHashPolicy, + uint dwFlags + ); + + internal static SafeCATAdminHandle CryptCATAdminAcquireContext2(string hashAlgorithm) + { + if (!NativeCryptCATAdminAcquireContext2(out var adminHandle, IntPtr.Zero, hashAlgorithm, IntPtr.Zero, 0)) + { + throw new Win32Exception(); + } + + return adminHandle; + } + + [DllImport( + WinTrustDll, + CharSet = CharSet.Unicode, + EntryPoint = "CryptCATAdminCalcHashFromFileHandle2", + SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern unsafe bool NativeCryptCATAdminCalcHashFromFileHandle2( + SafeCATAdminHandle hCatAdmin, + SafeHandle hFile, + [In, Out] ref int pcbHash, + byte* pbHash, + uint dwFlags + ); + + internal static byte[] CryptCATAdminCalcHashFromFileHandle2(SafeCATAdminHandle catAdmin, SafeHandle file) + { + unsafe + { + int hashLength = 0; + NativeCryptCATAdminCalcHashFromFileHandle2(catAdmin, file, ref hashLength, null, 0); + + byte[] hash = new byte[hashLength]; + fixed (byte* hashPtr = hash) + { + if (!NativeCryptCATAdminCalcHashFromFileHandle2(catAdmin, file, ref hashLength, hashPtr, 0)) + { + throw new Win32Exception(); + } + } + + return hash; + } + } + + [DllImport(WinTrustDll, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CryptCATAdminReleaseContext( + IntPtr phCatAdmin, + uint dwFlags + ); + + [DllImport(WinTrustDll, CharSet = CharSet.Unicode, EntryPoint = "CryptCATCDFOpen")] + private static extern SafeCATCDFHandle NativeCryptCATCDFOpen( + [MarshalAs(UnmanagedType.LPWStr)] string pwszFilePath, + CryptCATCDFParseErrorCallBack pfnParseError + ); + + internal static SafeCATCDFHandle CryptCATCDFOpen(string filePath, CryptCATCDFParseErrorCallBack parseError) + { + SafeCATCDFHandle handle = NativeCryptCATCDFOpen(filePath, parseError); + if (handle.IsInvalid) + { + throw new Win32Exception(); + } + + return handle; + } + + [DllImport(WinTrustDll, CharSet = CharSet.Unicode)] + internal static extern IntPtr CryptCATCDFEnumCatAttributes( + SafeCATCDFHandle pCDF, + IntPtr pPrevAttr, + CryptCATCDFParseErrorCallBack pfnParseError + ); + + [DllImport(WinTrustDll)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CryptCATCDFClose( + IntPtr pCDF + ); + + [DllImport(WinTrustDll, CharSet = CharSet.Unicode)] + internal static extern IntPtr CryptCATCDFEnumMembersByCDFTagEx( + SafeCATCDFHandle pCDF, + IntPtr pwszPrevCDFTag, + CryptCATCDFParseErrorCallBack fn, + ref IntPtr ppMember, + bool fContinueOnError, + IntPtr pvReserved + ); + + [DllImport(WinTrustDll, CharSet = CharSet.Unicode)] + internal static extern IntPtr CryptCATCDFEnumAttributesWithCDFTag( + SafeCATCDFHandle pCDF, + IntPtr pwszMemberTag, + IntPtr pMember, + IntPtr pPrevAttr, + CryptCATCDFParseErrorCallBack fn + ); + + [DllImport(WinTrustDll, CharSet = CharSet.Unicode)] + internal static extern IntPtr CryptCATEnumerateCatAttr( + SafeCATHandle hCatalog, + IntPtr pPrevAttr + ); + + [DllImport(WinTrustDll, CharSet = CharSet.Unicode, EntryPoint = "CryptCATOpen", SetLastError = true)] + internal static extern SafeCATHandle NativeCryptCATOpen( + [MarshalAs(UnmanagedType.LPWStr)] string pwszFilePath, + uint fdwOpenFlags, + IntPtr hProv, + uint dwPublicVersion, + uint dwEncodingType + ); + + internal static SafeCATHandle CryptCATOpen(string filePath, uint openFlags, IntPtr provider, uint publicVersion, + uint encodingType) + { + SafeCATHandle handle = NativeCryptCATOpen(filePath, openFlags, provider, publicVersion, encodingType); + if (handle.IsInvalid) + { + throw new Win32Exception(); + } + + return handle; + } + + [DllImport(WinTrustDll)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CryptCATClose( + IntPtr hCatalog + ); + + [DllImport(WinTrustDll, CharSet = CharSet.Unicode, EntryPoint = "CryptCATStoreFromHandle")] + private static extern IntPtr NativeCryptCATStoreFromHandle( + SafeCATHandle hCatalog + ); + + internal static CRYPTCATSTORE CryptCATStoreFromHandle(SafeCATHandle catalog) + { + IntPtr catStore = NativeCryptCATStoreFromHandle(catalog); + return Marshal.PtrToStructure(catStore); + } + + [DllImport(WinTrustDll, CharSet = CharSet.Unicode)] + internal static extern IntPtr CryptCATEnumerateMember( + SafeCATHandle hCatalog, + IntPtr pPrevMember + ); + + [DllImport(WinTrustDll, CharSet = CharSet.Unicode)] + internal static extern IntPtr CryptCATEnumerateAttr( + SafeCATHandle hCatalog, + IntPtr pCatMember, + IntPtr pPrevAttr + ); + + [DllImport(WinTrustDll, CharSet = CharSet.Unicode)] + internal static extern uint WinVerifyTrust( + IntPtr hWnd, + ref Guid pgActionID, + ref WINTRUST_DATA pWVTData + ); + + [DllImport(WinTrustDll, CharSet = CharSet.Unicode, EntryPoint = "WTHelperGetProvCertFromChain")] + private static extern IntPtr NativeWTHelperGetProvCertFromChain( + IntPtr pSgnr, + uint idxCert + ); + + internal static IntPtr WTHelperGetProvCertFromChain(IntPtr signer, uint certIdx) + { + IntPtr data = NativeWTHelperGetProvCertFromChain(signer, certIdx); + if (data == IntPtr.Zero) + { + throw new Win32Exception("WTHelperGetProvCertFromChain failed"); + } + + return data; + } + + [DllImport(WinTrustDll, CharSet = CharSet.Unicode, EntryPoint = "WTHelperGetProvSignerFromChain")] + private static extern IntPtr NativeWTHelperGetProvSignerFromChain( + IntPtr pProvData, + uint idxSigner, + bool fCounterSigner, + uint idxCounterSigner + ); + + internal static IntPtr WTHelperGetProvSignerFromChain(IntPtr providerData, uint signerIdx, bool counterSigner, + uint counterSignerIdx) + { + IntPtr data = NativeWTHelperGetProvSignerFromChain(providerData, signerIdx, counterSigner, counterSignerIdx); + if (data == IntPtr.Zero) + { + throw new Win32Exception("WTHelperGetProvSignerFromChain failed"); + } + + return data; + } + + [DllImport(WinTrustDll, CharSet = CharSet.Unicode, EntryPoint = "WTHelperProvDataFromStateData")] + private static extern IntPtr NativeWTHelperProvDataFromStateData( + IntPtr hStateData + ); + + internal static IntPtr WTHelperProvDataFromStateData(IntPtr stateData) + { + IntPtr data = NativeWTHelperProvDataFromStateData(stateData); + if (data == IntPtr.Zero) + { + throw new Win32Exception("WTHelperProvDataFromStateData failed"); + } + + return data; + } + + /// + /// Signature of call back function used by CryptCATCDFOpen, + /// CryptCATCDFEnumCatAttributes, CryptCATCDFEnumAttributesWithCDFTag, and + /// and CryptCATCDFEnumMembersByCDFTagEx. + /// + internal delegate void CryptCATCDFParseErrorCallBack( + uint dwErrorArea, + uint dwLocalArea, + [MarshalAs(UnmanagedType.LPWStr)] string pwszLine + ); +} diff --git a/src/System.Management.Automation/security/nativeMethods.cs b/src/System.Management.Automation/security/nativeMethods.cs index 69f018bb7bc..ae880e05518 100644 --- a/src/System.Management.Automation/security/nativeMethods.cs +++ b/src/System.Management.Automation/security/nativeMethods.cs @@ -620,7 +620,6 @@ internal struct CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO internal IntPtr psUnauthenticatedNotUsed; // PCRYPT_ATTRIBUTES } - [ArchitectureSensitive] internal static CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO InitSignInfoExtendedStruct(string description, string moreInfoUrl, @@ -654,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 @@ -724,7 +723,6 @@ internal static extern IntPtr CryptFindOIDInfo( System.IntPtr pvKey, uint dwGroupId); - [ArchitectureSensitive] internal static DWORD GetCertChoiceFromSigningOption( SigningOption option) { @@ -752,7 +750,6 @@ internal static DWORD GetCertChoiceFromSigningOption( return cc; } - [ArchitectureSensitive] internal static CRYPTUI_WIZ_DIGITAL_SIGN_INFO InitSignInfoStruct(string fileName, X509Certificate2 signingCert, @@ -779,424 +776,41 @@ internal static CRYPTUI_WIZ_DIGITAL_SIGN_INFO return si; } - // ----------------------------------------------------------------- - // wintrust.dll stuff - // - - // - // WinVerifyTrust() function and associated structures/enums - // - - [DllImport("wintrust.dll", SetLastError = true, CharSet = CharSet.Unicode)] - internal static extern - DWORD WinVerifyTrust( - IntPtr hWndNotUsed, // HWND - IntPtr pgActionID, // GUID* - IntPtr pWinTrustData // WINTRUST_DATA* - ); - - [StructLayout(LayoutKind.Sequential)] - internal struct WINTRUST_FILE_INFO - { - internal DWORD cbStruct; // = sizeof(WINTRUST_FILE_INFO) - - [MarshalAs(UnmanagedType.LPWStr)] - internal string pcwszFilePath; // LPCWSTR - - internal IntPtr hFileNotUsed; // optional, HANDLE to pcwszFilePath - internal IntPtr pgKnownSubjectNotUsed; // optional: GUID* : fill if the - // subject type is known - } - - [StructLayoutAttribute(LayoutKind.Sequential)] - internal struct WINTRUST_BLOB_INFO - { - /// DWORD->unsigned int - internal uint cbStruct; - - /// GUID->_GUID - internal Guid gSubject; - - /// LPCWSTR->WCHAR* - [MarshalAsAttribute(UnmanagedType.LPWStr)] - internal string pcwszDisplayName; - - /// DWORD->unsigned int - internal uint cbMemObject; - - /// BYTE* - internal System.IntPtr pbMemObject; - - /// DWORD->unsigned int - internal uint cbMemSignedMsg; - - /// BYTE* - internal System.IntPtr pbMemSignedMsg; - } - - [ArchitectureSensitive] - internal static WINTRUST_FILE_INFO InitWintrustFileInfoStruct(string fileName) - { - WINTRUST_FILE_INFO fi = new WINTRUST_FILE_INFO(); - - fi.cbStruct = (DWORD)Marshal.SizeOf(fi); - fi.pcwszFilePath = fileName; - fi.hFileNotUsed = IntPtr.Zero; - fi.pgKnownSubjectNotUsed = IntPtr.Zero; - - return fi; - } - - [ArchitectureSensitive] - internal static WINTRUST_BLOB_INFO InitWintrustBlobInfoStruct(string fileName, string content) - { - WINTRUST_BLOB_INFO bi = new WINTRUST_BLOB_INFO(); - byte[] contentBytes = System.Text.Encoding.Unicode.GetBytes(content); - - // The GUID of the PowerShell SIP - bi.gSubject = new Guid(0x603bcc1f, 0x4b59, 0x4e08, new byte[] { 0xb7, 0x24, 0xd2, 0xc6, 0x29, 0x7e, 0xf3, 0x51 }); - bi.cbStruct = (DWORD)Marshal.SizeOf(bi); - bi.pcwszDisplayName = fileName; - bi.cbMemObject = (uint)contentBytes.Length; - bi.pbMemObject = Marshal.AllocCoTaskMem(contentBytes.Length); - Marshal.Copy(contentBytes, 0, bi.pbMemObject, contentBytes.Length); - - return bi; - } - - [Flags] - internal enum WintrustUIChoice - { - WTD_UI_ALL = 1, - WTD_UI_NONE = 2, - WTD_UI_NOBAD = 3, - WTD_UI_NOGOOD = 4 - } - - [Flags] - internal enum WintrustUnionChoice - { - WTD_CHOICE_FILE = 1, - // WTD_CHOICE_CATALOG = 2, - WTD_CHOICE_BLOB = 3, - // WTD_CHOICE_SIGNER = 4, - // WTD_CHOICE_CERT = 5, - } - - [Flags] - internal enum WintrustProviderFlags - { - WTD_PROV_FLAGS_MASK = 0x0000FFFF, - WTD_USE_IE4_TRUST_FLAG = 0x00000001, - WTD_NO_IE4_CHAIN_FLAG = 0x00000002, - WTD_NO_POLICY_USAGE_FLAG = 0x00000004, - WTD_REVOCATION_CHECK_NONE = 0x00000010, - WTD_REVOCATION_CHECK_END_CERT = 0x00000020, - WTD_REVOCATION_CHECK_CHAIN = 0x00000040, - WTD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT = 0x00000080, - WTD_SAFER_FLAG = 0x00000100, - WTD_HASH_ONLY_FLAG = 0x00000200, - WTD_USE_DEFAULT_OSVER_CHECK = 0x00000400, - WTD_LIFETIME_SIGNING_FLAG = 0x00000800, - WTD_CACHE_ONLY_URL_RETRIEVAL = 0x00001000 - } - - [Flags] - internal enum WintrustAction - { - WTD_STATEACTION_IGNORE = 0x00000000, - WTD_STATEACTION_VERIFY = 0x00000001, - WTD_STATEACTION_CLOSE = 0x00000002, - WTD_STATEACTION_AUTO_CACHE = 0x00000003, - WTD_STATEACTION_AUTO_CACHE_FLUSH = 0x00000004 - } - - [StructLayoutAttribute(LayoutKind.Explicit)] - internal struct WinTrust_Choice - { - /// WINTRUST_FILE_INFO_* - [FieldOffsetAttribute(0)] - internal System.IntPtr pFile; - - /// WINTRUST_CATALOG_INFO_* - [FieldOffsetAttribute(0)] - internal System.IntPtr pCatalog; - - /// WINTRUST_BLOB_INFO_* - [FieldOffsetAttribute(0)] - internal System.IntPtr pBlob; - - /// WINTRUST_SGNR_INFO_* - [FieldOffsetAttribute(0)] - internal System.IntPtr pSgnr; - - /// WINTRUST_CERT_INFO_* - [FieldOffsetAttribute(0)] - internal System.IntPtr pCert; - } - - [StructLayoutAttribute(LayoutKind.Sequential)] - internal struct WINTRUST_DATA - { - /// DWORD->unsigned int - internal uint cbStruct; - - /// LPVOID->void* - internal System.IntPtr pPolicyCallbackData; - - /// LPVOID->void* - internal System.IntPtr pSIPClientData; - - /// DWORD->unsigned int - internal uint dwUIChoice; - - /// DWORD->unsigned int - internal uint fdwRevocationChecks; - - /// DWORD->unsigned int - internal uint dwUnionChoice; - - /// WinTrust_Choice struct - internal WinTrust_Choice Choice; - - /// DWORD->unsigned int - internal uint dwStateAction; - - /// HANDLE->void* - internal System.IntPtr hWVTStateData; - - /// WCHAR* - [MarshalAsAttribute(UnmanagedType.LPWStr)] - internal string pwszURLReference; - - /// DWORD->unsigned int - internal uint dwProvFlags; - - /// DWORD->unsigned int - internal uint dwUIContext; - } - - [ArchitectureSensitive] - internal static WINTRUST_DATA InitWintrustDataStructFromFile(WINTRUST_FILE_INFO wfi) - { - WINTRUST_DATA wtd = new WINTRUST_DATA(); - - wtd.cbStruct = (DWORD)Marshal.SizeOf(wtd); - wtd.pPolicyCallbackData = IntPtr.Zero; - wtd.pSIPClientData = IntPtr.Zero; - wtd.dwUIChoice = (DWORD)WintrustUIChoice.WTD_UI_NONE; - wtd.fdwRevocationChecks = 0; - wtd.dwUnionChoice = (DWORD)WintrustUnionChoice.WTD_CHOICE_FILE; - - IntPtr pFileBuffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(wfi)); - Marshal.StructureToPtr(wfi, pFileBuffer, false); - wtd.Choice.pFile = pFileBuffer; - - wtd.dwStateAction = (DWORD)WintrustAction.WTD_STATEACTION_VERIFY; - wtd.hWVTStateData = IntPtr.Zero; - wtd.pwszURLReference = null; - wtd.dwProvFlags = 0; - - return wtd; - } - - [ArchitectureSensitive] - internal static WINTRUST_DATA InitWintrustDataStructFromBlob(WINTRUST_BLOB_INFO wbi) - { - WINTRUST_DATA wtd = new WINTRUST_DATA(); - - wtd.cbStruct = (DWORD)Marshal.SizeOf(wbi); - wtd.pPolicyCallbackData = IntPtr.Zero; - wtd.pSIPClientData = IntPtr.Zero; - wtd.dwUIChoice = (DWORD)WintrustUIChoice.WTD_UI_NONE; - wtd.fdwRevocationChecks = 0; - wtd.dwUnionChoice = (DWORD)WintrustUnionChoice.WTD_CHOICE_BLOB; - - IntPtr pBlob = Marshal.AllocCoTaskMem(Marshal.SizeOf(wbi)); - Marshal.StructureToPtr(wbi, pBlob, false); - wtd.Choice.pBlob = pBlob; - - wtd.dwStateAction = (DWORD)WintrustAction.WTD_STATEACTION_VERIFY; - wtd.hWVTStateData = IntPtr.Zero; - wtd.pwszURLReference = null; - wtd.dwProvFlags = 0; - - return wtd; - } - - [ArchitectureSensitive] - internal static DWORD DestroyWintrustDataStruct(WINTRUST_DATA wtd) - { - DWORD dwResult = Win32Errors.E_FAIL; - IntPtr WINTRUST_ACTION_GENERIC_VERIFY_V2 = IntPtr.Zero; - IntPtr wtdBuffer = IntPtr.Zero; - - Guid actionVerify = - new Guid("00AAC56B-CD44-11d0-8CC2-00C04FC295EE"); - - try - { - WINTRUST_ACTION_GENERIC_VERIFY_V2 = - Marshal.AllocCoTaskMem(Marshal.SizeOf(actionVerify)); - Marshal.StructureToPtr(actionVerify, - WINTRUST_ACTION_GENERIC_VERIFY_V2, - false); - - wtd.dwStateAction = (DWORD)WintrustAction.WTD_STATEACTION_CLOSE; - wtdBuffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(wtd)); - Marshal.StructureToPtr(wtd, wtdBuffer, false); - - // The GetLastWin32Error of this is checked, but PreSharp doesn't seem to be - // able to see that. -#pragma warning disable 56523 - dwResult = WinVerifyTrust( - IntPtr.Zero, - WINTRUST_ACTION_GENERIC_VERIFY_V2, - wtdBuffer); -#pragma warning restore 56523 - - wtd = Marshal.PtrToStructure(wtdBuffer); - } - finally - { - Marshal.DestroyStructure(wtdBuffer); - Marshal.FreeCoTaskMem(wtdBuffer); - Marshal.DestroyStructure(WINTRUST_ACTION_GENERIC_VERIFY_V2); - Marshal.FreeCoTaskMem(WINTRUST_ACTION_GENERIC_VERIFY_V2); - } - - // Clear the blob or file info, depending on the type of - // verification that was done. - if (wtd.dwUnionChoice == (DWORD)WintrustUnionChoice.WTD_CHOICE_BLOB) - { - WINTRUST_BLOB_INFO originalBlob = - (WINTRUST_BLOB_INFO)Marshal.PtrToStructure(wtd.Choice.pBlob); - Marshal.FreeCoTaskMem(originalBlob.pbMemObject); - - Marshal.DestroyStructure(wtd.Choice.pBlob); - Marshal.FreeCoTaskMem(wtd.Choice.pBlob); - } - else - { - Marshal.DestroyStructure(wtd.Choice.pFile); - Marshal.FreeCoTaskMem(wtd.Choice.pFile); - } - - return dwResult; - } - [StructLayout(LayoutKind.Sequential)] internal struct CRYPT_PROVIDER_CERT { +#pragma warning disable IDE0044 private DWORD _cbStruct; +#pragma warning restore IDE0044 internal IntPtr pCert; // PCCERT_CONTEXT - private BOOL _fCommercial; - private BOOL _fTrustedRoot; - private BOOL _fSelfSigned; - private BOOL _fTestCert; - private DWORD _dwRevokedReason; - private DWORD _dwConfidence; - private DWORD _dwError; - private IntPtr _pTrustListContext; // CTL_CONTEXT* - private BOOL _fTrustListSignerCert; - private IntPtr _pCtlContext; // PCCTL_CONTEXT - private DWORD _dwCtlError; - private BOOL _fIsCyclic; - private IntPtr _pChainElement; // PCERT_CHAIN_ELEMENT + private readonly BOOL _fCommercial; + private readonly BOOL _fTrustedRoot; + private readonly BOOL _fSelfSigned; + private readonly BOOL _fTestCert; + private readonly DWORD _dwRevokedReason; + private readonly DWORD _dwConfidence; + private readonly DWORD _dwError; + private readonly IntPtr _pTrustListContext; // CTL_CONTEXT* + private readonly BOOL _fTrustListSignerCert; + private readonly IntPtr _pCtlContext; // PCCTL_CONTEXT + private readonly DWORD _dwCtlError; + private readonly BOOL _fIsCyclic; + private readonly IntPtr _pChainElement; // PCERT_CHAIN_ELEMENT } [StructLayout(LayoutKind.Sequential)] internal struct CRYPT_PROVIDER_SGNR { - private DWORD _cbStruct; + private readonly DWORD _cbStruct; private FILETIME _sftVerifyAsOf; - private DWORD _csCertChain; - private IntPtr _pasCertChain; // CRYPT_PROVIDER_CERT* - private DWORD _dwSignerType; - private IntPtr _psSigner; // CMSG_SIGNER_INFO* - private DWORD _dwError; + private readonly DWORD _csCertChain; + private readonly IntPtr _pasCertChain; // CRYPT_PROVIDER_CERT* + private readonly DWORD _dwSignerType; + private readonly IntPtr _psSigner; // CMSG_SIGNER_INFO* + private readonly DWORD _dwError; internal DWORD csCounterSigners; internal IntPtr pasCounterSigners; // CRYPT_PROVIDER_SGNR* - private IntPtr _pChainContext; // PCCERT_CHAIN_CONTEXT - } - - [DllImport("wintrust.dll", SetLastError = true, CharSet = CharSet.Unicode)] - internal static extern - IntPtr // CRYPT_PROVIDER_DATA* - WTHelperProvDataFromStateData(IntPtr hStateData); - - [DllImport("wintrust.dll", SetLastError = true, CharSet = CharSet.Unicode)] - internal static extern - IntPtr // CRYPT_PROVIDER_SGNR* - WTHelperGetProvSignerFromChain( - IntPtr pProvData, // CRYPT_PROVIDER_DATA* - DWORD idxSigner, - BOOL fCounterSigner, - DWORD idxCounterSigner - ); - - [DllImport("wintrust.dll", SetLastError = true, CharSet = CharSet.Unicode)] - internal static extern - IntPtr // CRYPT_PROVIDER_CERT* - WTHelperGetProvCertFromChain( - IntPtr pSgnr, // CRYPT_PROVIDER_SGNR* - DWORD idxCert - ); - - /// Return Type: HRESULT->LONG->int - ///pszFile: PCWSTR->WCHAR* - ///hFile: HANDLE->void* - ///sigInfoFlags: SIGNATURE_INFO_FLAGS->Anonymous_5157c654_2076_48e7_9241_84ac648615e9 - ///psiginfo: SIGNATURE_INFO* - ///ppCertContext: void** - ///phWVTStateData: HANDLE* - [DllImportAttribute("wintrust.dll", EntryPoint = "WTGetSignatureInfo", CallingConvention = CallingConvention.StdCall)] - internal static extern int WTGetSignatureInfo([InAttribute()][MarshalAsAttribute(UnmanagedType.LPWStr)] string pszFile, [InAttribute()] System.IntPtr hFile, SIGNATURE_INFO_FLAGS sigInfoFlags, ref SIGNATURE_INFO psiginfo, ref System.IntPtr ppCertContext, ref System.IntPtr phWVTStateData); - - internal static void FreeWVTStateData(System.IntPtr phWVTStateData) - { - WINTRUST_DATA wtd = new WINTRUST_DATA(); - DWORD dwResult = Win32Errors.E_FAIL; - IntPtr WINTRUST_ACTION_GENERIC_VERIFY_V2 = IntPtr.Zero; - IntPtr wtdBuffer = IntPtr.Zero; - - Guid actionVerify = - new Guid("00AAC56B-CD44-11d0-8CC2-00C04FC295EE"); - - try - { - WINTRUST_ACTION_GENERIC_VERIFY_V2 = - Marshal.AllocCoTaskMem(Marshal.SizeOf(actionVerify)); - Marshal.StructureToPtr(actionVerify, - WINTRUST_ACTION_GENERIC_VERIFY_V2, - false); - - wtd.cbStruct = (DWORD)Marshal.SizeOf(wtd); - wtd.dwUIChoice = (DWORD)WintrustUIChoice.WTD_UI_NONE; - wtd.fdwRevocationChecks = 0; - wtd.dwUnionChoice = (DWORD)WintrustUnionChoice.WTD_CHOICE_BLOB; - wtd.dwStateAction = (DWORD)WintrustAction.WTD_STATEACTION_CLOSE; - wtd.hWVTStateData = phWVTStateData; - - wtdBuffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(wtd)); - Marshal.StructureToPtr(wtd, wtdBuffer, false); - - // The GetLastWin32Error of this is checked, but PreSharp doesn't seem to be - // able to see that. -#pragma warning disable 56523 - dwResult = WinVerifyTrust( - IntPtr.Zero, - WINTRUST_ACTION_GENERIC_VERIFY_V2, - wtdBuffer); -#pragma warning restore 56523 - } - finally - { - Marshal.DestroyStructure(wtdBuffer); - Marshal.FreeCoTaskMem(wtdBuffer); - Marshal.DestroyStructure(WINTRUST_ACTION_GENERIC_VERIFY_V2); - Marshal.FreeCoTaskMem(WINTRUST_ACTION_GENERIC_VERIFY_V2); - } + private readonly IntPtr _pChainContext; // PCCERT_CHAIN_CONTEXT } // @@ -1284,7 +898,7 @@ internal enum SIGNATURE_INFO_TYPE SIT_CATALOG, } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct SIGNATURE_INFO { /// DWORD->unsigned int @@ -1303,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 @@ -1333,7 +947,7 @@ internal struct SIGNATURE_INFO internal int fOSBinary; } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct CERT_INFO { /// DWORD->unsigned int @@ -1373,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 @@ -1394,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 @@ -1404,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 @@ -1417,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 @@ -1472,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 @@ -1489,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, @@ -1502,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 @@ -1523,7 +1137,7 @@ internal struct SAFER_CODE_PROPERTIES public uint dwCheckFlags; /// LPCWSTR->WCHAR* - [MarshalAsAttribute(UnmanagedType.LPWStr)] + [MarshalAs(UnmanagedType.LPWStr)] public string ImagePath; /// HANDLE->void* @@ -1533,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)] @@ -1558,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 @@ -1591,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 @@ -1853,42 +1467,6 @@ internal static extern bool AdjustTokenPrivileges( internal const uint LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400; internal const uint LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800; internal const uint LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000; - - [DllImport(PinvokeDllNames.LoadLibraryEx, CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern IntPtr LoadLibraryExW( - string DllName, - IntPtr reserved, - uint Flags); - - [DllImport(PinvokeDllNames.FreeLibrary, CharSet = CharSet.Unicode, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool FreeLibrary( - IntPtr Module); - - internal static bool IsSystem32DllPresent(string DllName) - { - bool DllExists = false; - - try - { - IntPtr module = LoadLibraryExW( - DllName, - IntPtr.Zero, - NativeMethods.LOAD_LIBRARY_AS_DATAFILE | - NativeMethods.LOAD_LIBRARY_AS_IMAGE_RESOURCE | - NativeMethods.LOAD_LIBRARY_SEARCH_SYSTEM32); - if (module != IntPtr.Zero) - { - FreeLibrary(module); - DllExists = true; - } - } - catch (Exception) - { - } - - return DllExists; - } } // Constants needed for Catalog Error Handling @@ -1930,234 +1508,6 @@ internal partial class NativeConstants // CRYPTCAT_E_CDF_ATTR_TYPECOMBO = "0x00020004"; public const int CRYPTCAT_E_CDF_ATTR_TYPECOMBO = 131076; } - - /// - /// Pinvoke methods from wintrust.dll - /// These are added to Generate and Validate Window Catalog Files. - /// - internal static partial class NativeMethods - { - [StructLayout(LayoutKind.Sequential)] - internal struct CRYPT_ATTRIBUTE_TYPE_VALUE - { - [MarshalAs(UnmanagedType.LPStr)] - internal string pszObjId; - - internal CRYPT_ATTR_BLOB Value; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct SIP_INDIRECT_DATA - { - internal CRYPT_ATTRIBUTE_TYPE_VALUE Data; - internal CRYPT_ALGORITHM_IDENTIFIER DigestAlgorithm; - internal CRYPT_ATTR_BLOB Digest; - } - - [StructLayout(LayoutKind.Sequential)] - internal readonly struct CRYPTCATCDF - { - private readonly DWORD _cbStruct; - private readonly IntPtr _hFile; - private readonly DWORD _dwCurFilePos; - private readonly DWORD _dwLastMemberOffset; - private readonly BOOL _fEOF; - - [MarshalAs(UnmanagedType.LPWStr)] - private readonly string _pwszResultDir; - - private readonly IntPtr _hCATStore; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct CRYPTCATMEMBER - { - internal DWORD cbStruct; - - [MarshalAs(UnmanagedType.LPWStr)] - internal string pwszReferenceTag; - - [MarshalAs(UnmanagedType.LPWStr)] - internal string pwszFileName; - - internal Guid gSubjectType; - internal DWORD fdwMemberFlags; - internal IntPtr pIndirectData; - internal DWORD dwCertVersion; - internal DWORD dwReserved; - internal IntPtr hReserved; - internal CRYPT_ATTR_BLOB sEncodedIndirectData; - internal CRYPT_ATTR_BLOB sEncodedMemberInfo; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct CRYPTCATATTRIBUTE - { - private DWORD _cbStruct; - - [MarshalAs(UnmanagedType.LPWStr)] - internal string pwszReferenceTag; - - private DWORD _dwAttrTypeAndAction; - internal DWORD cbValue; - internal System.IntPtr pbValue; - private DWORD _dwReserved; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct CRYPTCATSTORE - { - private DWORD _cbStruct; - internal DWORD dwPublicVersion; - - [MarshalAs(UnmanagedType.LPWStr)] - internal string pwszP7File; - - private IntPtr _hProv; - private DWORD _dwEncodingType; - private DWORD _fdwStoreFlags; - private IntPtr _hReserved; - private IntPtr _hAttrs; - private IntPtr _hCryptMsg; - private IntPtr _hSorted; - } - - [DllImport("wintrust.dll", CharSet = CharSet.Unicode)] - internal static extern IntPtr CryptCATCDFOpen( - [MarshalAs(UnmanagedType.LPWStr)] - string pwszFilePath, - CryptCATCDFOpenCallBack pfnParseError - ); - - [DllImport("wintrust.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool CryptCATCDFClose( - IntPtr pCDF - ); - - [DllImport("wintrust.dll", CharSet = CharSet.Unicode)] - internal static extern IntPtr CryptCATCDFEnumCatAttributes( - IntPtr pCDF, - IntPtr pPrevAttr, - CryptCATCDFOpenCallBack pfnParseError - ); - - [DllImport("wintrust.dll", CharSet = CharSet.Unicode)] - internal static extern IntPtr CryptCATCDFEnumMembersByCDFTagEx( - IntPtr pCDF, - IntPtr pwszPrevCDFTag, - CryptCATCDFEnumMembersByCDFTagExErrorCallBack fn, - ref IntPtr ppMember, - bool fContinueOnError, - IntPtr pvReserved - ); - - [DllImport("wintrust.dll", CharSet = CharSet.Unicode)] - internal static extern IntPtr CryptCATCDFEnumAttributesWithCDFTag( - IntPtr pCDF, - IntPtr pwszMemberTag, - IntPtr pMember, - IntPtr pPrevAttr, - CryptCATCDFEnumMembersByCDFTagExErrorCallBack fn - ); - - [DllImport("wintrust.dll", CharSet = CharSet.Unicode)] - internal static extern IntPtr CryptCATOpen( - [MarshalAs(UnmanagedType.LPWStr)] - string pwszFilePath, - DWORD fdwOpenFlags, - IntPtr hProv, - DWORD dwPublicVersion, - DWORD dwEncodingType - ); - - [DllImport("wintrust.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool CryptCATClose( - IntPtr hCatalog - ); - - [DllImport("wintrust.dll", CharSet = CharSet.Unicode)] - internal static extern IntPtr CryptCATStoreFromHandle( - IntPtr hCatalog - ); - - [DllImport("wintrust.dll", CharSet = CharSet.Unicode)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool CryptCATAdminAcquireContext2( - ref IntPtr phCatAdmin, - IntPtr pgSubsystem, - [MarshalAs(UnmanagedType.LPWStr)] - string pwszHashAlgorithm, - IntPtr pStrongHashPolicy, - DWORD dwFlags - ); - - [DllImport("wintrust.dll", CharSet = CharSet.Unicode)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool CryptCATAdminReleaseContext( - IntPtr phCatAdmin, - DWORD dwFlags - ); - - [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)] - internal static extern unsafe IntPtr CreateFile( - string lpFileName, - DWORD dwDesiredAccess, - DWORD dwShareMode, - DWORD lpSecurityAttributes, - DWORD dwCreationDisposition, - DWORD dwFlagsAndAttributes, - IntPtr hTemplateFile - ); - - [DllImport("wintrust.dll", SetLastError = true, CharSet = CharSet.Unicode)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool CryptCATAdminCalcHashFromFileHandle2( - IntPtr hCatAdmin, - IntPtr hFile, - [In, Out] ref DWORD pcbHash, - IntPtr pbHash, - DWORD dwFlags - ); - - [DllImport("wintrust.dll", CharSet = CharSet.Unicode)] - internal static extern IntPtr CryptCATEnumerateCatAttr( - IntPtr hCatalog, - IntPtr pPrevAttr - ); - - [DllImport("wintrust.dll", CharSet = CharSet.Unicode)] - internal static extern IntPtr CryptCATEnumerateMember( - IntPtr hCatalog, - IntPtr pPrevMember - ); - - [DllImport("wintrust.dll", CharSet = CharSet.Unicode)] - internal static extern IntPtr CryptCATEnumerateAttr( - IntPtr hCatalog, - IntPtr pCatMember, - IntPtr pPrevAttr - ); - - /// - /// Signature of call back function used by CryptCATCDFOpen. - /// - internal delegate - void CryptCATCDFOpenCallBack(DWORD NotUsedDWORD1, - DWORD NotUsedDWORD2, - [MarshalAs(UnmanagedType.LPWStr)] - string NotUsedString); - - /// - /// Signature of call back function used by CryptCATCDFEnumMembersByCDFTagEx. - /// - internal delegate - void CryptCATCDFEnumMembersByCDFTagExErrorCallBack(DWORD NotUsedDWORD1, - DWORD NotUsedDWORD2, - [MarshalAs(UnmanagedType.LPWStr)] - string NotUsedString); - } } #pragma warning restore 56523 diff --git a/src/System.Management.Automation/security/wldpNativeMethods.cs b/src/System.Management.Automation/security/wldpNativeMethods.cs index 3e0edc58fd0..ab49f927614 100644 --- a/src/System.Management.Automation/security/wldpNativeMethods.cs +++ b/src/System.Management.Automation/security/wldpNativeMethods.cs @@ -6,12 +6,46 @@ // #if !UNIX +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Management.Automation.Internal; +using System.Management.Automation.Runspaces; +using System.Management.Automation.Tracing; using System.Runtime.InteropServices; -using System.Diagnostics.CodeAnalysis; namespace System.Management.Automation.Security { + /// + /// System wide policy enforcement for a specific script file. + /// + public enum SystemScriptFileEnforcement + { + /// + /// No policy enforcement. + /// + None = 0, + + /// + /// Script file is blocked from running. + /// + Block = 1, + + /// + /// Script file is allowed to run without restrictions (FullLanguage mode). + /// + Allow = 2, + + /// + /// Script file is allowed to run in ConstrainedLanguage mode only. + /// + AllowConstrained = 3, + + /// + /// Script file is allowed to run in FullLanguage mode but will emit ConstrainedLanguage restriction audit logs. + /// + AllowConstrainedAudit = 4 + } + /// /// How the policy is being enforced. /// @@ -41,6 +75,63 @@ private SystemPolicy() { } + /// + /// Writes to PowerShell WDAC Audit mode ETW log. + /// + /// Current execution context. + /// Audit message title. + /// Audit message message. + /// Fully Qualified ID. + /// Stops code execution and goes into debugger mode. + internal static void LogWDACAuditMessage( + ExecutionContext context, + string title, + string message, + string fqid, + bool dropIntoDebugger = false) + { + string messageToWrite = message; + + // Augment the log message with current script information from the script debugger, if available. + context ??= LocalPipeline.GetExecutionContextFromTLS(); + bool debuggerAvailable = context is not null && + context._debugger is ScriptDebugger; + + if (debuggerAvailable) + { + var scriptPosMessage = context._debugger.GetCurrentScriptPosition(); + if (!string.IsNullOrEmpty(scriptPosMessage)) + { + messageToWrite = message + scriptPosMessage; + } + } + + PSEtwLog.LogWDACAuditEvent(title, messageToWrite, fqid); + + // We drop into the debugger only if requested and we are running in the interactive host session runspace (Id == 1). + if (debuggerAvailable && dropIntoDebugger && + context._debugger.DebugMode.HasFlag(DebugModes.LocalScript) && + Runspace.DefaultRunspace?.Id == 1 && + context.DebugPreferenceVariable.HasFlag(ActionPreference.Break) && + context.InternalHost?.UI is not null) + { + try + { + context.InternalHost.UI.WriteLine(); + context.InternalHost.UI.WriteLine("WDAC Audit Log:"); + context.InternalHost.UI.WriteLine($"Title: {title}"); + context.InternalHost.UI.WriteLine($"Message: {message}"); + context.InternalHost.UI.WriteLine($"FullyQualifedId: {fqid}"); + context.InternalHost.UI.WriteLine("Stopping script execution in debugger..."); + context.InternalHost.UI.WriteLine(); + + context._debugger.Break(); + } + catch + { } + } + } + /// /// Gets the system lockdown policy. /// @@ -51,26 +142,121 @@ public static SystemEnforcementMode GetSystemLockdownPolicy() { lock (s_systemLockdownPolicyLock) { - if (s_systemLockdownPolicy == null) - { - s_systemLockdownPolicy = GetLockdownPolicy(path: null, handle: null); - } + s_systemLockdownPolicy ??= GetLockdownPolicy(path: null, handle: null); } } else if (s_allowDebugOverridePolicy) { lock (s_systemLockdownPolicyLock) { - s_systemLockdownPolicy = GetDebugLockdownPolicy(path: null); + s_systemLockdownPolicy = GetDebugLockdownPolicy(path: null, out _); } } return s_systemLockdownPolicy.Value; } - private static object s_systemLockdownPolicyLock = new object(); + private static readonly object s_systemLockdownPolicyLock = new object(); private static SystemEnforcementMode? s_systemLockdownPolicy = null; private static bool s_allowDebugOverridePolicy = false; + private static bool s_wldpCanExecuteAvailable = true; + + /// + /// Gets the system wide script file policy enforcement for an open file. + /// Based on system WDAC (Windows Defender Application Control) or AppLocker policies. + /// + /// Script file path for policy check. + /// FileStream object to script file path. + /// Policy check result for script file. + public static SystemScriptFileEnforcement GetFilePolicyEnforcement( + string filePath, + System.IO.FileStream fileStream) + { + SafeHandle fileHandle = fileStream.SafeFileHandle; + SystemEnforcementMode systemLockdownPolicy = GetSystemLockdownPolicy(); + + // First check latest WDAC APIs if available. + if (systemLockdownPolicy is SystemEnforcementMode.Enforce + && s_wldpCanExecuteAvailable + && TryGetWldpCanExecuteFileResult(filePath, fileHandle, out SystemScriptFileEnforcement wldpFilePolicy)) + { + return GetLockdownPolicy(filePath, fileHandle, wldpFilePolicy); + } + + // Failed to invoke WldpCanExecuteFile, revert to legacy APIs. + if (systemLockdownPolicy is SystemEnforcementMode.None) + { + return SystemScriptFileEnforcement.None; + } + + // 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); + } + + private static SystemScriptFileEnforcement ConvertToModernFileEnforcement(SystemEnforcementMode legacyMode) + { + return legacyMode switch + { + SystemEnforcementMode.None => SystemScriptFileEnforcement.Allow, + SystemEnforcementMode.Audit => SystemScriptFileEnforcement.AllowConstrainedAudit, + SystemEnforcementMode.Enforce => SystemScriptFileEnforcement.AllowConstrained, + _ => 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}"; + + 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); + + PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile", filePath, hr, (int)canExecuteResult); + + if (hr >= 0) + { + 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 HResult is unsuccessful (such as E_NOTIMPL (0x80004001)), fall through to legacy system checks. + } + catch (Exception ex) when (ex is DllNotFoundException or EntryPointNotFoundException) + { + // Fall back to legacy system policy checks. + s_wldpCanExecuteAvailable = false; + PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile_Failed", filePath, ex.HResult, 0); + } + + result = default; + return false; + } /// /// Gets lockdown policy as applied to a file. @@ -78,9 +264,32 @@ public static SystemEnforcementMode GetSystemLockdownPolicy() /// An EnforcementMode that describes policy. 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; } @@ -92,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", @@ -156,6 +364,7 @@ private static SystemEnforcementMode GetWldpPolicy(string path, SafeHandle handl uint pdwLockdownState = 0; int result = WldpNativeMethods.WldpGetLockdownPolicy(ref hostInformation, ref pdwLockdownState, 0); + PSEtwLog.LogWDACQueryEvent("WldpGetLockdownPolicy", path, result, (int)pdwLockdownState); if (result >= 0) { SystemEnforcementMode resultingLockdownPolicy = GetLockdownPolicyForResult(pdwLockdownState); @@ -174,9 +383,10 @@ private static SystemEnforcementMode GetWldpPolicy(string path, SafeHandle handl return SystemEnforcementMode.Enforce; } } - catch (DllNotFoundException) + catch (DllNotFoundException ex) { s_hadMissingWldpAssembly = true; + PSEtwLog.LogWDACQueryEvent("WldpGetLockdownPolicy_Failed", path, ex.HResult, 0); return s_cachedWldpSystemPolicy.GetValueOrDefault(SystemEnforcementMode.None); } } @@ -237,23 +447,38 @@ private static SystemEnforcementMode GetAppLockerPolicy(string path, SafeHandle IO.File.WriteAllText(testPathScript, dtAppLockerTestFileContents); IO.File.WriteAllText(testPathModule, dtAppLockerTestFileContents); } - catch (System.IO.IOException) + catch (IO.IOException) { - if (iteration == 2) throw; + if (iteration == 2) + { + throw; + } + error = true; } - catch (System.UnauthorizedAccessException) + catch (UnauthorizedAccessException) { - if (iteration == 2) throw; + if (iteration == 2) + { + throw; + } + error = true; } catch (System.Security.SecurityException) { - if (iteration == 2) throw; + if (iteration == 2) + { + throw; + } + error = true; } - if (!error) { break; } + if (!error) + { + break; + } // Try again with the AppData\LocalLow\Temp path using known folder id: // https://msdn.microsoft.com/library/dd378457.aspx @@ -352,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; @@ -363,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); } @@ -376,10 +610,13 @@ private static SystemEnforcementMode GetDebugLockdownPolicy(string path) if (result != null) { pdwLockdownState = LanguagePrimitives.ConvertTo(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; } @@ -391,6 +628,14 @@ private static SystemEnforcementMode GetDebugLockdownPolicy(string path) /// True if the COM object is allowed, False otherwise. internal static bool IsClassInApprovedList(Guid clsid) { + // This method is called only if there is an AppLocker and/or WLDP system wide lock down enforcement policy. + if (s_cachedWldpSystemPolicy.GetValueOrDefault(SystemEnforcementMode.None) != SystemEnforcementMode.Enforce) + { + // No WLDP policy implies only AppLocker policy enforcement. Disallow all COM object instantiation. + return false; + } + + // WLDP policy must be in system wide enforcement, look up COM Id in WLDP approval list. try { WLDP_HOST_INFORMATION hostInformation = new WLDP_HOST_INFORMATION(); @@ -538,18 +783,67 @@ internal struct WLDP_HOST_INFORMATION internal IntPtr hSource; } + /// + /// Options for WldpCanExecuteFile method. + /// + [Flags] + internal enum WLDP_EXECUTION_EVALUATION_OPTIONS + { + WLDP_EXECUTION_EVALUATION_OPTION_NONE = 0x0, + WLDP_EXECUTION_EVALUATION_OPTION_EXECUTE_IN_INTERACTIVE_SESSION = 0x1 + } + + /// + /// Results from WldpCanExecuteFile method. + /// + internal enum WLDP_EXECUTION_POLICY + { + WLDP_CAN_EXECUTE_BLOCKED = 0, + WLDP_CAN_EXECUTE_ALLOWED = 1, + WLDP_CAN_EXECUTE_REQUIRE_SANDBOX = 2 + } + + /// + /// Powershell Script Host. + /// + internal static readonly Guid PowerShellHost = new Guid("8E9AAA7C-198B-4879-AE41-A50D47AD6458"); + /// /// Native methods for dealing with the lockdown policy. /// internal static class WldpNativeMethods { + /// + /// Returns a WLDP_EXECUTION_POLICY enum value indicating if and how a script file + /// should be executed. + /// + /// Host guid. + /// Evaluation options. + /// Evaluated file handle. + /// Auditing information string. + /// Evaluation result. + /// HResult value. + [DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)] + [DllImportAttribute("wldp.dll", EntryPoint = "WldpCanExecuteFile")] + internal static extern int WldpCanExecuteFile( + [MarshalAs(UnmanagedType.LPStruct)] + Guid host, + WLDP_EXECUTION_EVALUATION_OPTIONS options, + IntPtr fileHandle, + [MarshalAs(UnmanagedType.LPWStr)] + string auditInfo, + out WLDP_EXECUTION_POLICY result); + /// Return Type: HRESULT->LONG->int /// pHostInformation: PWLDP_HOST_INFORMATION->_WLDP_HOST_INFORMATION* /// pdwLockdownState: PDWORD->DWORD* /// dwFlags: DWORD->unsigned int [DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)] [DllImportAttribute("wldp.dll", EntryPoint = "WldpGetLockdownPolicy")] - internal static extern int WldpGetLockdownPolicy(ref WLDP_HOST_INFORMATION pHostInformation, ref uint pdwLockdownState, uint dwFlags); + internal static extern int WldpGetLockdownPolicy( + ref WLDP_HOST_INFORMATION pHostInformation, + ref uint pdwLockdownState, + uint dwFlags); /// Return Type: HRESULT->LONG->int /// rclsid: IID* @@ -558,7 +852,11 @@ internal static class WldpNativeMethods /// dwFlags: DWORD->unsigned int [DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)] [DllImportAttribute("wldp.dll", EntryPoint = "WldpIsClassInApprovedList")] - internal static extern int WldpIsClassInApprovedList(ref Guid rclsid, ref WLDP_HOST_INFORMATION pHostInformation, ref int ptIsApproved, uint dwFlags); + internal static extern int WldpIsClassInApprovedList( + ref Guid rclsid, + ref WLDP_HOST_INFORMATION pHostInformation, + ref int ptIsApproved, + uint dwFlags); [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] internal static extern int SHGetKnownFolderPath( diff --git a/src/System.Management.Automation/singleshell/config/MshConsoleLoadException.cs b/src/System.Management.Automation/singleshell/config/MshConsoleLoadException.cs index 7e5a675a2b3..be4fa6c2edb 100644 --- a/src/System.Management.Automation/singleshell/config/MshConsoleLoadException.cs +++ b/src/System.Management.Automation/singleshell/config/MshConsoleLoadException.cs @@ -20,7 +20,6 @@ namespace System.Management.Automation.Runspaces /// 1. PSSnapin name /// 2. Inner exception. /// --> - [Serializable] public class PSConsoleLoadException : SystemException, IContainsErrorRecord { /// @@ -86,7 +85,7 @@ private void CreateErrorRecord() _errorRecord = new ErrorRecord(new ParentContainsErrorRecordException(this), "ConsoleLoadFailure", ErrorCategory.ResourceUnavailable, null); } - private Collection _PSSnapInExceptions = new Collection(); + private readonly Collection _PSSnapInExceptions = new Collection(); internal Collection PSSnapInExceptions { diff --git a/src/System.Management.Automation/singleshell/config/MshSnapinInfo.cs b/src/System.Management.Automation/singleshell/config/MshSnapinInfo.cs index db45a12b378..df01b053665 100644 --- a/src/System.Management.Automation/singleshell/config/MshSnapinInfo.cs +++ b/src/System.Management.Automation/singleshell/config/MshSnapinInfo.cs @@ -69,7 +69,7 @@ internal static class RegistryStrings } /// - /// Contains information about a mshsnapin. + /// Contains information about a PSSnapin. /// public class PSSnapInInfo { @@ -118,25 +118,13 @@ string vendorFallback version = new Version("0.0"); } - if (types == null) - { - types = new Collection(); - } + types ??= new Collection(); - if (formats == null) - { - formats = new Collection(); - } + formats ??= new Collection(); - if (descriptionFallback == null) - { - descriptionFallback = string.Empty; - } + descriptionFallback ??= string.Empty; - if (vendorFallback == null) - { - vendorFallback = string.Empty; - } + vendorFallback ??= string.Empty; Name = name; IsDefault = isDefault; @@ -201,22 +189,22 @@ string vendorIndirect } /// - /// Unique Name of the mshsnapin. + /// Unique Name of the PSSnapin. /// public string Name { get; } /// - /// Is this mshsnapin default mshsnapin. + /// Is this PSSnapin default PSSnapin. /// public bool IsDefault { get; } /// - /// Returns applicationbase for mshsnapin. + /// Returns applicationbase for PSSnapin. /// public string ApplicationBase { get; } /// - /// Strong name of mshSnapIn assembly. + /// Strong name of PSSnapin assembly. /// public string AssemblyName { get; } @@ -243,12 +231,12 @@ internal string AbsoluteModulePath } /// - /// Monad version used by mshsnapin. + /// PowerShell version used by PSSnapin. /// public Version PSVersion { get; } /// - /// Version of mshsnapin. + /// Version of PSSnapin. /// public Version Version { get; } @@ -262,11 +250,11 @@ internal string AbsoluteModulePath /// public Collection Formats { get; } - private string _descriptionIndirect; - private string _descriptionFallback = string.Empty; + private readonly string _descriptionIndirect; + private readonly string _descriptionFallback = string.Empty; private string _description; /// - /// Description of mshsnapin. + /// Description of PSSnapin. /// public string Description { @@ -281,11 +269,11 @@ public string Description } } - private string _vendorIndirect; - private string _vendorFallback = string.Empty; + private readonly string _vendorIndirect; + private readonly string _vendorFallback = string.Empty; private string _vendor; /// - /// Vendor of mshsnapin. + /// Vendor of PSSnapin. /// public string Vendor { @@ -419,8 +407,9 @@ internal PSSnapInInfo Clone() } /// - /// Returns true if the PSSnapIn Id is valid. A PSSnapIn is valid iff it contains only - /// "Alpha Numeric","-","_","." characters. + /// Returns true if the PSSnapIn Id is valid. A PSSnapIn is valid + /// if-and-only-if it contains only "Alpha Numeric","-","_","." + /// characters. /// /// PSSnapIn Id to validate. internal static bool IsPSSnapinIdValid(string psSnapinId) @@ -434,8 +423,8 @@ internal static bool IsPSSnapinIdValid(string psSnapinId) } /// - /// Validates the PSSnapIn Id. A PSSnapIn is valid iff it contains only - /// "Alpha Numeric","-","_","." characters. + /// Validates the PSSnapIn Id. A PSSnapIn is valid if-and-only-if it + /// contains only "Alpha Numeric","-","_","." characters. /// /// PSSnapIn Id to validate. /// @@ -773,8 +762,7 @@ private static Collection ReadMultiStringValue(RegistryKey mshsnapinKey, if (msv == null) { // Check if the value is in string format - string singleValue = value as string; - if (singleValue != null) + if (value is string singleValue) { msv = new string[1]; msv[0] = singleValue; @@ -877,18 +865,18 @@ internal static Version ReadVersionValue(RegistryKey mshsnapinKey, string name, return v; } - internal static void ReadRegistryInfo(out Version assemblyVersion, out string publicKeyToken, out string culture, out string architecture, out string applicationBase, out Version psVersion) + internal static void ReadRegistryInfo(out Version assemblyVersion, out string publicKeyToken, out string culture, out string applicationBase, out Version psVersion) { applicationBase = Utils.DefaultPowerShellAppBase; Dbg.Assert( !string.IsNullOrEmpty(applicationBase), - string.Format(CultureInfo.CurrentCulture, "{0} is empty or null", RegistryStrings.MonadEngine_ApplicationBase)); + string.Create(CultureInfo.CurrentCulture, $"{RegistryStrings.MonadEngine_ApplicationBase} is empty or null")); // Get the PSVersion from Utils..this is hardcoded psVersion = PSVersionInfo.PSVersion; Dbg.Assert( psVersion != null, - string.Format(CultureInfo.CurrentCulture, "{0} is null", RegistryStrings.MonadEngine_MonadVersion)); + string.Create(CultureInfo.CurrentCulture, $"{RegistryStrings.MonadEngine_MonadVersion} is null")); // Get version number in x.x.x.x format // This information is available from the executing assembly @@ -897,8 +885,7 @@ internal static void ReadRegistryInfo(out Version assemblyVersion, out string pu // culture, publickeytoken...This will break the scenarios where only one of // the assemblies is patched. ie., all monad assemblies should have the // same version number. - Assembly currentAssembly = typeof(PSSnapInReader).Assembly; - AssemblyName assemblyName = currentAssembly.GetName(); + AssemblyName assemblyName = typeof(PSSnapInReader).Assembly.GetName(); assemblyVersion = assemblyName.Version; byte[] publicTokens = assemblyName.GetPublicKeyToken(); if (publicTokens.Length == 0) @@ -911,11 +898,6 @@ internal static void ReadRegistryInfo(out Version assemblyVersion, out string pu // save some cpu cycles by hardcoding the culture to neutral // assembly should never be targeted to a particular culture culture = "neutral"; - - // Hardcoding the architecture MSIL as PowerShell assemblies are architecture neutral, this should - // be changed if the assumption is broken. Preferred hardcoded string to using (for perf reasons): - // string architecture = currentAssembly.GetName().ProcessorArchitecture.ToString() - architecture = "MSIL"; } /// @@ -943,23 +925,27 @@ internal static string ConvertByteArrayToString(byte[] tokens) /// internal static PSSnapInInfo ReadCoreEngineSnapIn() { - Version assemblyVersion, psVersion; - string publicKeyToken = null; - string culture = null; - string architecture = null; - string applicationBase = null; - - ReadRegistryInfo(out assemblyVersion, out publicKeyToken, out culture, out architecture, out applicationBase, out psVersion); + ReadRegistryInfo( + out Version assemblyVersion, + out string publicKeyToken, + out string culture, + out string applicationBase, + out Version psVersion); // System.Management.Automation formats & types files Collection types = new Collection(new string[] { "types.ps1xml", "typesv3.ps1xml" }); Collection formats = new Collection(new string[] - {"Certificate.format.ps1xml","DotNetTypes.format.ps1xml","FileSystem.format.ps1xml", - "Help.format.ps1xml","HelpV3.format.ps1xml","PowerShellCore.format.ps1xml","PowerShellTrace.format.ps1xml", + {"Certificate.format.ps1xml", "DotNetTypes.format.ps1xml", "FileSystem.format.ps1xml", + "Help.format.ps1xml", "HelpV3.format.ps1xml", "PowerShellCore.format.ps1xml", "PowerShellTrace.format.ps1xml", "Registry.format.ps1xml"}); - string strongName = string.Format(CultureInfo.InvariantCulture, "{0}, Version={1}, Culture={2}, PublicKeyToken={3}, ProcessorArchitecture={4}", - s_coreSnapin.AssemblyName, assemblyVersion, culture, publicKeyToken, architecture); + string strongName = string.Format( + CultureInfo.InvariantCulture, + "{0}, Version={1}, Culture={2}, PublicKeyToken={3}", + s_coreSnapin.AssemblyName, + assemblyVersion, + culture, + publicKeyToken); string moduleName = Path.Combine(applicationBase, s_coreSnapin.AssemblyName + ".dll"); @@ -997,18 +983,17 @@ internal static PSSnapInInfo ReadCoreEngineSnapIn() /// internal static Collection ReadEnginePSSnapIns() { - Version assemblyVersion, psVersion; - string publicKeyToken = null; - string culture = null; - string architecture = null; - string applicationBase = null; - - ReadRegistryInfo(out assemblyVersion, out publicKeyToken, out culture, out architecture, out applicationBase, out psVersion); + ReadRegistryInfo( + out Version assemblyVersion, + out string publicKeyToken, + out string culture, + out string applicationBase, + out Version psVersion); // System.Management.Automation formats & types files Collection smaFormats = new Collection(new string[] - {"Certificate.format.ps1xml","DotNetTypes.format.ps1xml","FileSystem.format.ps1xml", - "Help.format.ps1xml","HelpV3.format.ps1xml","PowerShellCore.format.ps1xml","PowerShellTrace.format.ps1xml", + {"Certificate.format.ps1xml", "DotNetTypes.format.ps1xml", "FileSystem.format.ps1xml", + "Help.format.ps1xml", "HelpV3.format.ps1xml", "PowerShellCore.format.ps1xml", "PowerShellTrace.format.ps1xml", "Registry.format.ps1xml"}); Collection smaTypes = new Collection(new string[] { "types.ps1xml", "typesv3.ps1xml" }); @@ -1022,12 +1007,11 @@ internal static Collection ReadEnginePSSnapIns() string strongName = string.Format( CultureInfo.InvariantCulture, - "{0}, Version={1}, Culture={2}, PublicKeyToken={3}, ProcessorArchitecture={4}", + "{0}, Version={1}, Culture={2}, PublicKeyToken={3}", defaultMshSnapinInfo.AssemblyName, assemblyVersionString, culture, - publicKeyToken, - architecture); + publicKeyToken); Collection formats = null; Collection types = null; @@ -1296,7 +1280,9 @@ private static IList DefaultMshSnapins { lock (s_syncObject) { +#pragma warning disable IDE0074 // Disabling the rule because it can't be applied on non Unix if (s_defaultMshSnapins == null) +#pragma warning restore IDE0074 { s_defaultMshSnapins = new List() { @@ -1305,18 +1291,18 @@ private static IList DefaultMshSnapins "GetEventResources,Description", "GetEventResources,Vendor"), #endif new DefaultPSSnapInInformation("Microsoft.PowerShell.Host", "Microsoft.PowerShell.ConsoleHost", null, - "HostMshSnapInResources,Description","HostMshSnapInResources,Vendor"), + "HostMshSnapInResources,Description", "HostMshSnapInResources,Vendor"), s_coreSnapin, new DefaultPSSnapInInformation("Microsoft.PowerShell.Utility", "Microsoft.PowerShell.Commands.Utility", null, - "UtilityMshSnapInResources,Description","UtilityMshSnapInResources,Vendor"), + "UtilityMshSnapInResources,Description", "UtilityMshSnapInResources,Vendor"), new DefaultPSSnapInInformation("Microsoft.PowerShell.Management", "Microsoft.PowerShell.Commands.Management", null, - "ManagementMshSnapInResources,Description","ManagementMshSnapInResources,Vendor"), + "ManagementMshSnapInResources,Description", "ManagementMshSnapInResources,Vendor"), new DefaultPSSnapInInformation("Microsoft.PowerShell.Security", "Microsoft.PowerShell.Security", null, - "SecurityMshSnapInResources,Description","SecurityMshSnapInResources,Vendor") + "SecurityMshSnapInResources,Description", "SecurityMshSnapInResources,Vendor") }; #if !UNIX @@ -1335,10 +1321,10 @@ private static IList DefaultMshSnapins } private static IList s_defaultMshSnapins = null; - private static object s_syncObject = new object(); + private static readonly object s_syncObject = new object(); #endregion - private static PSTraceSource s_mshsnapinTracer = PSTraceSource.GetTracer("MshSnapinLoadUnload", "Loading and unloading mshsnapins", false); + private static readonly PSTraceSource s_mshsnapinTracer = PSTraceSource.GetTracer("MshSnapinLoadUnload", "Loading and unloading mshsnapins", false); } } diff --git a/src/System.Management.Automation/singleshell/config/MshSnapinLoadException.cs b/src/System.Management.Automation/singleshell/config/MshSnapinLoadException.cs index b7b38254f29..f0e61f01a9e 100644 --- a/src/System.Management.Automation/singleshell/config/MshSnapinLoadException.cs +++ b/src/System.Management.Automation/singleshell/config/MshSnapinLoadException.cs @@ -19,7 +19,6 @@ namespace System.Management.Automation.Runspaces /// 1. PSSnapin name /// 2. Inner exception. /// --> - [Serializable] public class PSSnapInException : RuntimeException { /// @@ -116,7 +115,7 @@ private void CreateErrorRecord() } } - private bool _warning = false; + private readonly bool _warning = false; private ErrorRecord _errorRecord; private bool _isErrorRecordOriginallyNull; @@ -146,8 +145,8 @@ public override ErrorRecord ErrorRecord } } - private string _PSSnapin = string.Empty; - private string _reason = string.Empty; + private readonly string _PSSnapin = string.Empty; + private readonly string _reason = string.Empty; /// /// Gets message for this exception. @@ -172,32 +171,11 @@ public override string Message /// /// Serialization information. /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSSnapInException(SerializationInfo info, StreamingContext context) - : base(info, context) { - _PSSnapin = info.GetString("PSSnapIn"); - _reason = info.GetString("Reason"); - - CreateErrorRecord(); - } - - /// - /// Get object data from serialization information. - /// - /// Serialization information. - /// Streaming context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw PSTraceSource.NewArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - - info.AddValue("PSSnapIn", _PSSnapin); - info.AddValue("Reason", _reason); + throw new NotSupportedException(); } #endregion Serialization diff --git a/src/System.Management.Automation/utils/ArchitectureSensitiveAttribute.cs b/src/System.Management.Automation/utils/ArchitectureSensitiveAttribute.cs deleted file mode 100644 index eecde27926c..00000000000 --- a/src/System.Management.Automation/utils/ArchitectureSensitiveAttribute.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace System.Management.Automation.Internal -{ - /// - /// This attribute is used for Design For Testability. - /// It should be placed on any method containing code - /// which is likely to be sensitive to X86/X64/IA64 issues, - /// primarily code which calls DllImports or otherwise uses - /// NativeMethods. This allows us to generate code coverage - /// data specific to architecture sensitive code. - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - internal class ArchitectureSensitiveAttribute : Attribute - { - /// - /// Constructor for the ArchitectureSensitiveAttribute class. - /// - internal ArchitectureSensitiveAttribute() - { - } - } -} diff --git a/src/System.Management.Automation/utils/BackgroundDispatcher.cs b/src/System.Management.Automation/utils/BackgroundDispatcher.cs index 583eecfa836..ac74a32d352 100644 --- a/src/System.Management.Automation/utils/BackgroundDispatcher.cs +++ b/src/System.Management.Automation/utils/BackgroundDispatcher.cs @@ -70,11 +70,7 @@ public BackgroundDispatcher(EventProvider transferProvider, EventDescriptor tran // internal for unit testing only. Otherwise, would be private. internal BackgroundDispatcher(IMethodInvoker etwActivityMethodInvoker) { - if (etwActivityMethodInvoker == null) - { - throw new ArgumentNullException("etwActivityMethodInvoker"); - } - + ArgumentNullException.ThrowIfNull(etwActivityMethodInvoker); _etwActivityMethodInvoker = etwActivityMethodInvoker; _invokerWaitCallback = DoInvoker; } diff --git a/src/System.Management.Automation/utils/ClrFacade.cs b/src/System.Management.Automation/utils/ClrFacade.cs index 0372b249ca1..058c33a68bf 100644 --- a/src/System.Management.Automation/utils/ClrFacade.cs +++ b/src/System.Management.Automation/utils/ClrFacade.cs @@ -7,7 +7,6 @@ using System.Management.Automation.Internal; using System.Management.Automation.Language; using System.Reflection; -using System.Runtime.InteropServices; using System.Runtime.Loader; using System.Security; using System.Text; @@ -23,13 +22,23 @@ internal static class ClrFacade { /// /// Initialize powershell AssemblyLoadContext and register the 'Resolving' event, if it's not done already. - /// If powershell is hosted by a native host such as DSC, then PS ALC might be initialized via 'SetPowerShellAssemblyLoadContext' before loading S.M.A. + /// If powershell is hosted by a native host such as DSC, then PS ALC may be initialized via 'SetPowerShellAssemblyLoadContext' before loading S.M.A. /// + /// + /// We do this both here and during the initialization of the 'RunspaceBase' type. + /// This is because we want to make sure the assembly/library resolvers are: + /// 1. registered before any script/cmdlet can run. + /// 2. registered before 'ClrFacade' gets used for assembly related operations. + /// + /// The 'ClrFacade' type may be used without a Runspace created, for example, by calling type conversion methods in the 'LanguagePrimitive' type. + /// And at the mean time, script or cmdlet may run without the 'ClrFacade' type initialized. + /// That's why we attempt to create the singleton of 'PowerShellAssemblyLoadContext' at both places. + /// static ClrFacade() { - if (PowerShellAssemblyLoadContext.Instance == null) + if (PowerShellAssemblyLoadContext.Instance is null) { - PowerShellAssemblyLoadContext.InitializeSingleton(string.Empty); + PowerShellAssemblyLoadContext.InitializeSingleton(string.Empty, throwOnReentry: false); } } @@ -100,23 +109,6 @@ private static IEnumerable GetPSVisibleAssemblies() #region Encoding - /// - /// Facade for getting default encoding. - /// - internal static Encoding GetDefaultEncoding() - { - if (s_defaultEncoding == null) - { - // load all available encodings - EncodingRegisterProvider(); - s_defaultEncoding = new UTF8Encoding(false); - } - - return s_defaultEncoding; - } - - private static volatile Encoding s_defaultEncoding; - /// /// Facade for getting OEM encoding /// OEM encodings work on all platforms, or rather codepage 437 is available on both Windows and Non-Windows. @@ -125,12 +117,10 @@ internal static Encoding GetOEMEncoding() { if (s_oemEncoding == null) { - // load all available encodings - EncodingRegisterProvider(); #if UNIX - s_oemEncoding = new UTF8Encoding(false); + s_oemEncoding = Encoding.Default; #else - uint oemCp = NativeMethods.GetOEMCP(); + uint oemCp = Interop.Windows.GetOEMCP(); s_oemEncoding = Encoding.GetEncoding((int)oemCp); #endif } @@ -140,14 +130,6 @@ internal static Encoding GetOEMEncoding() private static volatile Encoding s_oemEncoding; - private static void EncodingRegisterProvider() - { - if (s_defaultEncoding == null && s_oemEncoding == null) - { - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - } - } - #endregion Encoding #if !UNIX @@ -263,7 +245,7 @@ private static SecurityZone ReadFromZoneIdentifierDataStream(string filePath) } // If we successfully get the zone data stream, try to read the ZoneId information - using (StreamReader zoneDataReader = new StreamReader(zoneDataStream, GetDefaultEncoding())) + using (StreamReader zoneDataReader = new StreamReader(zoneDataStream, Encoding.Default)) { string line = null; bool zoneTransferMatched = false; @@ -288,12 +270,18 @@ private static SecurityZone ReadFromZoneIdentifierDataStream(string filePath) else { Match match = Regex.Match(line, @"^ZoneId\s*=\s*(.*)", RegexOptions.IgnoreCase); - if (!match.Success) { continue; } + if (!match.Success) + { + continue; + } // Match found. Validate ZoneId value. string zoneIdRawValue = match.Groups[1].Value; match = Regex.Match(zoneIdRawValue, @"^[+-]?\d+", RegexOptions.IgnoreCase); - if (!match.Success) { return SecurityZone.NoZone; } + if (!match.Success) + { + return SecurityZone.NoZone; + } string zoneId = match.Groups[0].Value; SecurityZone result; @@ -356,7 +344,7 @@ internal static string ToDmtfDateTime(DateTime date) dmtfDateTime += date.Second.ToString(frmInt32).PadLeft(2, '0'); dmtfDateTime += "."; - // Construct a DateTime with with the precision to Second as same as the passed DateTime and so get + // Construct a DateTime with the precision to Second as same as the passed DateTime and so get // the ticks difference so that the microseconds can be calculated DateTime dtTemp = new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, 0); Int64 microsec = ((date.Ticks - dtTemp.Ticks) * 1000) / TimeSpan.TicksPerMillisecond; @@ -379,17 +367,5 @@ internal static string ToDmtfDateTime(DateTime date) } #endregion Misc - - /// - /// Native methods that are used by facade methods. - /// - private static class NativeMethods - { - /// - /// Pinvoke for GetOEMCP to get the OEM code page. - /// - [DllImport(PinvokeDllNames.GetOEMCPDllName, SetLastError = false, CharSet = CharSet.Unicode)] - internal static extern uint GetOEMCP(); - } } } diff --git a/src/System.Management.Automation/utils/CommandDiscoveryExceptions.cs b/src/System.Management.Automation/utils/CommandDiscoveryExceptions.cs index e08d8e1a65c..6c2380023ca 100644 --- a/src/System.Management.Automation/utils/CommandDiscoveryExceptions.cs +++ b/src/System.Management.Automation/utils/CommandDiscoveryExceptions.cs @@ -11,7 +11,6 @@ namespace System.Management.Automation /// /// This exception is thrown when a command cannot be found. /// - [Serializable] public class CommandNotFoundException : RuntimeException { /// @@ -70,7 +69,6 @@ public CommandNotFoundException(string message) : base(message) { } /// public CommandNotFoundException(string message, Exception innerException) : base(message, innerException) { } - #region Serialization /// /// Serialization constructor for class CommandNotFoundException. /// @@ -80,38 +78,12 @@ public CommandNotFoundException(string message, Exception innerException) : base /// /// streaming context /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected CommandNotFoundException(SerializationInfo info, StreamingContext context) - : base(info, context) { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - _commandName = info.GetString("CommandName"); - } - - /// - /// Serializes the CommandNotFoundException. - /// - /// - /// serialization information - /// - /// - /// streaming context - /// - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - info.AddValue("CommandName", _commandName); + throw new NotSupportedException(); } - #endregion Serialization #region Properties /// @@ -121,14 +93,11 @@ public override ErrorRecord ErrorRecord { get { - if (_errorRecord == null) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - _errorId, - _errorCategory, - _commandName); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + _errorId, + _errorCategory, + _commandName); return _errorRecord; } @@ -181,7 +150,6 @@ params object[] messageArgs /// Defines the exception thrown when a script's requirements to run specified by the #requires /// statements are not met. /// - [Serializable] public class ScriptRequiresException : RuntimeException { /// @@ -368,39 +336,13 @@ public ScriptRequiresException(string message, Exception innerException) : base( /// /// streaming context /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ScriptRequiresException(SerializationInfo info, StreamingContext context) - : base(info, context) { - _commandName = info.GetString("CommandName"); - _requiresPSVersion = (Version)info.GetValue("RequiresPSVersion", typeof(Version)); - _missingPSSnapIns = (ReadOnlyCollection)info.GetValue("MissingPSSnapIns", typeof(ReadOnlyCollection)); - _requiresShellId = info.GetString("RequiresShellId"); - _requiresShellPath = info.GetString("RequiresShellPath"); - } - /// - /// Gets the serialized data for the exception. - /// - /// - /// serialization information - /// - /// - /// streaming context - /// - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - info.AddValue("CommandName", _commandName); - info.AddValue("RequiresPSVersion", _requiresPSVersion, typeof(Version)); - info.AddValue("MissingPSSnapIns", _missingPSSnapIns, typeof(ReadOnlyCollection)); - info.AddValue("RequiresShellId", _requiresShellId); - info.AddValue("RequiresShellPath", _requiresShellPath); + 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 4aebe05af59..f63ff69bd2c 100644 --- a/src/System.Management.Automation/utils/CommandProcessorExceptions.cs +++ b/src/System.Management.Automation/utils/CommandProcessorExceptions.cs @@ -8,7 +8,6 @@ namespace System.Management.Automation /// /// Defines the exception that is thrown if a native command fails. /// - [Serializable] public class ApplicationFailedException : RuntimeException { #region private @@ -25,10 +24,11 @@ public class ApplicationFailedException : RuntimeException /// The serialization information to use when initializing this object. /// The streaming context to use when initializing this object. /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ApplicationFailedException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion Serialization diff --git a/src/System.Management.Automation/utils/CryptoUtils.cs b/src/System.Management.Automation/utils/CryptoUtils.cs index e121dd62b6d..2441d416a4d 100644 --- a/src/System.Management.Automation/utils/CryptoUtils.cs +++ b/src/System.Management.Automation/utils/CryptoUtils.cs @@ -43,7 +43,7 @@ internal static class PSCryptoNativeConverter /// public const uint PUBLICKEYBLOB = 0x00000006; - /// + /// /// PUBLICKEYBLOB header length. /// public const int PUBLICKEYBLOB_HEADER_LEN = 20; @@ -53,7 +53,7 @@ internal static class PSCryptoNativeConverter /// public const uint SIMPLEBLOB = 0x00000001; - /// + /// /// SIMPLEBLOB header length. /// public const int SIMPLEBLOB_HEADER_LEN = 12; @@ -97,10 +97,7 @@ internal static RSA FromCapiPublicKeyBlob(byte[] blob) private static RSA FromCapiPublicKeyBlob(byte[] blob, int offset) { - if (blob == null) - { - throw new ArgumentNullException(nameof(blob)); - } + ArgumentNullException.ThrowIfNull(blob); if (offset > blob.Length) { @@ -123,10 +120,7 @@ private static RSA FromCapiPublicKeyBlob(byte[] blob, int offset) private static RSAParameters GetParametersFromCapiPublicKeyBlob(byte[] blob, int offset) { - if (blob == null) - { - throw new ArgumentNullException(nameof(blob)); - } + ArgumentNullException.ThrowIfNull(blob); if (offset > blob.Length) { @@ -175,10 +169,7 @@ private static RSAParameters GetParametersFromCapiPublicKeyBlob(byte[] blob, int internal static byte[] ToCapiPublicKeyBlob(RSA rsa) { - if (rsa == null) - { - throw new ArgumentNullException(nameof(rsa)); - } + ArgumentNullException.ThrowIfNull(rsa); RSAParameters p = rsa.ExportParameters(false); int keyLength = p.Modulus.Length; // in bytes @@ -221,10 +212,7 @@ internal static byte[] ToCapiPublicKeyBlob(RSA rsa) internal static byte[] FromCapiSimpleKeyBlob(byte[] blob) { - if (blob == null) - { - throw new ArgumentNullException(nameof(blob)); - } + ArgumentNullException.ThrowIfNull(blob); if (blob.Length < SIMPLEBLOB_HEADER_LEN) { @@ -237,10 +225,7 @@ internal static byte[] FromCapiSimpleKeyBlob(byte[] blob) internal static byte[] ToCapiSimpleKeyBlob(byte[] encryptedKey) { - if (encryptedKey == null) - { - throw new ArgumentNullException(nameof(encryptedKey)); - } + ArgumentNullException.ThrowIfNull(encryptedKey); // formulate the PUBLICKEYSTRUCT byte[] blob = new byte[SIMPLEBLOB_HEADER_LEN + encryptedKey.Length]; @@ -250,9 +235,9 @@ internal static byte[] ToCapiSimpleKeyBlob(byte[] encryptedKey) // [2], [3] // RESERVED - Always 0 blob[4] = (byte)CALG_AES_256; // AES-256 algo id (0x10) blob[5] = 0x66; // ?? - // [6], [7], [8] // 0x00 + // [6], [7], [8] // 0x00 blob[9] = (byte)CALG_RSA_KEYX; // 0xa4 - // [10], [11] // 0x00 + // [10], [11] // 0x00 // create a reversed copy and add the encrypted key byte[] reversedKey = CreateReverseByteArray(encryptedKey); @@ -273,7 +258,6 @@ internal static byte[] ToCapiSimpleKeyBlob(byte[] encryptedKey) /// to the user when something fails on the remote end, then this /// can be turned public [SuppressMessage("Microsoft.Design", "CA1064:ExceptionsShouldBePublic")] - [Serializable] internal class PSCryptoException : Exception { #region Private Members @@ -344,33 +328,20 @@ public PSCryptoException(string message, Exception innerException) /// Context in which this constructor is called. /// Currently no custom type-specific serialization logic is /// implemented + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSCryptoException(SerializationInfo info, StreamingContext context) - : base(info, context) { - _errorCode = unchecked(0xFFFFFFF); - Dbg.Assert(false, "type-specific serialization logic not implemented and so this constructor should not be called"); + throw new NotSupportedException(); } #endregion Constructors - - #region ISerializable Overrides - /// - /// Returns base implementation. - /// - /// Serialization info. - /// Context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - base.GetObjectData(info, context); - } - #endregion ISerializable Overrides } /// /// A reverse compatible implementation of session key exchange. This supports the CAPI /// keyblob formats but uses dotnet std abstract AES and RSA classes for all crypto operations. /// - internal class PSRSACryptoServiceProvider : IDisposable + internal sealed class PSRSACryptoServiceProvider : IDisposable { #region Private Members @@ -380,7 +351,7 @@ internal class PSRSACryptoServiceProvider : IDisposable // handle to the AES provider object (houses session key and iv) private readonly Aes _aes; - // this flag indicates that this class has a key imported from the + // this flag indicates that this class has a key imported from the // remote end and so can be used for encryption private bool _canEncrypt; @@ -438,7 +409,7 @@ internal void GenerateSessionKey() { if (!_sessionKeyGenerated) { - // Aes object gens key automatically on construction, so this is somewhat redundant, + // Aes object gens key automatically on construction, so this is somewhat redundant, // but at least the actionable key will not be in-memory until it's requested fwiw. _aes.GenerateKey(); _sessionKeyGenerated = true; @@ -612,19 +583,12 @@ public void Dispose() System.GC.SuppressFinalize(this); } - protected void Dispose(bool disposing) + private void Dispose(bool disposing) { if (disposing) { - if (_rsa != null) - { - _rsa.Dispose(); - } - - if (_aes != null) - { - _aes.Dispose(); - } + _rsa?.Dispose(); + _aes?.Dispose(); } } @@ -635,7 +599,7 @@ protected void Dispose(bool disposing) /// Helper for exchanging keys and encrypting/decrypting /// secure strings for serialization in remoting. /// - internal abstract class PSRemotingCryptoHelper : IDisposable + public abstract class PSRemotingCryptoHelper : IDisposable { #region Protected Members @@ -645,7 +609,7 @@ internal abstract class PSRemotingCryptoHelper : IDisposable /// it and performing symmetric key operations using the /// session key. /// - protected PSRSACryptoServiceProvider _rsaCryptoProvider; + internal PSRSACryptoServiceProvider _rsaCryptoProvider; /// /// Key exchange has been completed and both keys @@ -851,11 +815,7 @@ public void Dispose(bool disposing) { if (disposing) { - if (_rsaCryptoProvider != null) - { - _rsaCryptoProvider.Dispose(); - } - + _rsaCryptoProvider?.Dispose(); _rsaCryptoProvider = null; _keyExchangeCompleted.Dispose(); @@ -906,12 +866,10 @@ internal PSRemotingCryptoHelperServer() internal override string EncryptSecureString(SecureString secureString) { - ServerRemoteSession session = Session as ServerRemoteSession; - // 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 != null) && (session.Context.ClientCapability.ProtocolVersion >= RemotingConstants.ProtocolVersionWin8RTM)) + if (Session is ServerRemoteSession session && session.Context.ClientCapability.ProtocolVersion >= RemotingConstants.ProtocolVersionWin8RTM) { _rsaCryptoProvider.GenerateSessionKey(); } diff --git a/src/System.Management.Automation/utils/EncodingUtils.cs b/src/System.Management.Automation/utils/EncodingUtils.cs index e9802488006..cdb467d213a 100644 --- a/src/System.Management.Automation/utils/EncodingUtils.cs +++ b/src/System.Management.Automation/utils/EncodingUtils.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; +using System.Globalization; using System.Text; using System.Management.Automation.Internal; @@ -10,41 +11,43 @@ namespace System.Management.Automation { internal static class EncodingConversion { - internal const string Unknown = "unknown"; - internal const string String = "string"; - internal const string Unicode = "unicode"; + internal const string ANSI = "ansi"; + internal const string Ascii = "ascii"; internal const string BigEndianUnicode = "bigendianunicode"; internal const string BigEndianUtf32 = "bigendianutf32"; - internal const string Ascii = "ascii"; + internal const string Default = "default"; + internal const string OEM = "oem"; + internal const string String = "string"; + internal const string Unicode = "unicode"; + internal const string Unknown = "unknown"; + internal const string Utf7 = "utf7"; internal const string Utf8 = "utf8"; - internal const string Utf8NoBom = "utf8NoBOM"; internal const string Utf8Bom = "utf8BOM"; - internal const string Utf7 = "utf7"; + internal const string Utf8NoBom = "utf8NoBOM"; internal const string Utf32 = "utf32"; - internal const string Default = "default"; - internal const string OEM = "oem"; internal static readonly string[] TabCompletionResults = { - Ascii, BigEndianUnicode, BigEndianUtf32, OEM, Unicode, Utf7, Utf8, Utf8Bom, Utf8NoBom, Utf32 + ANSI, Ascii, BigEndianUnicode, BigEndianUtf32, OEM, Unicode, Utf7, Utf8, Utf8Bom, Utf8NoBom, Utf32 }; - internal static readonly Dictionary encodingMap = new Dictionary(StringComparer.OrdinalIgnoreCase) + internal static readonly Dictionary encodingMap = new(StringComparer.OrdinalIgnoreCase) { - { Ascii, System.Text.Encoding.ASCII }, - { BigEndianUnicode, System.Text.Encoding.BigEndianUnicode }, + { ANSI, Encoding.GetEncoding(CultureInfo.CurrentCulture.TextInfo.ANSICodePage) }, + { Ascii, Encoding.ASCII }, + { BigEndianUnicode, Encoding.BigEndianUnicode }, { BigEndianUtf32, new UTF32Encoding(bigEndian: true, byteOrderMark: true) }, - { Default, ClrFacade.GetDefaultEncoding() }, + { Default, Encoding.Default }, { OEM, ClrFacade.GetOEMEncoding() }, - { Unicode, System.Text.Encoding.Unicode }, + { String, Encoding.Unicode }, + { Unicode, Encoding.Unicode }, + { Unknown, Encoding.Unicode }, #pragma warning disable SYSLIB0001 - { Utf7, System.Text.Encoding.UTF7 }, + { Utf7, Encoding.UTF7 }, #pragma warning restore SYSLIB0001 - { Utf8, ClrFacade.GetDefaultEncoding() }, - { Utf8Bom, System.Text.Encoding.UTF8 }, - { Utf8NoBom, ClrFacade.GetDefaultEncoding() }, - { Utf32, System.Text.Encoding.UTF32 }, - { String, System.Text.Encoding.Unicode }, - { Unknown, System.Text.Encoding.Unicode }, + { Utf8, Encoding.Default }, + { Utf8Bom, Encoding.UTF8 }, + { Utf8NoBom, Encoding.Default }, + { Utf32, Encoding.UTF32 }, }; /// @@ -57,11 +60,10 @@ internal static Encoding Convert(Cmdlet cmdlet, string encoding) if (string.IsNullOrEmpty(encoding)) { // no parameter passed, default to UTF8 - return ClrFacade.GetDefaultEncoding(); + return Encoding.Default; } - Encoding foundEncoding; - if (encodingMap.TryGetValue(encoding, out foundEncoding)) + if (encodingMap.TryGetValue(encoding, out Encoding foundEncoding)) { // Write a warning if using utf7 as it is obsolete in .NET5 if (string.Equals(encoding, Utf7, StringComparison.OrdinalIgnoreCase)) @@ -96,7 +98,7 @@ internal static Encoding Convert(Cmdlet cmdlet, string encoding) internal static void WarnIfObsolete(Cmdlet cmdlet, Encoding encoding) { // Check for UTF-7 by checking for code page 65000 - // See: https://docs.microsoft.com/en-us/dotnet/core/compatibility/corefx#utf-7-code-paths-are-obsolete + // See: https://learn.microsoft.com/dotnet/core/compatibility/corefx#utf-7-code-paths-are-obsolete if (encoding != null && encoding.CodePage == 65000) { cmdlet.WriteWarning(PathUtilsStrings.Utf7EncodingObsolete); @@ -113,6 +115,8 @@ internal sealed class ArgumentToEncodingTransformationAttribute : ArgumentTransf { public override object Transform(EngineIntrinsics engineIntrinsics, object inputData) { + inputData = PSObject.Base(inputData); + switch (inputData) { case string stringName: @@ -122,10 +126,10 @@ public override object Transform(EngineIntrinsics engineIntrinsics, object input } else { - return System.Text.Encoding.GetEncoding(stringName); + return Encoding.GetEncoding(stringName); } case int intName: - return System.Text.Encoding.GetEncoding(intName); + return Encoding.GetEncoding(intName); } return inputData; @@ -138,6 +142,7 @@ public override object Transform(EngineIntrinsics engineIntrinsics, object input internal sealed class ArgumentEncodingCompletionsAttribute : ArgumentCompletionsAttribute { public ArgumentEncodingCompletionsAttribute() : base( + EncodingConversion.ANSI, EncodingConversion.Ascii, EncodingConversion.BigEndianUnicode, EncodingConversion.BigEndianUtf32, diff --git a/src/System.Management.Automation/utils/ExecutionExceptions.cs b/src/System.Management.Automation/utils/ExecutionExceptions.cs index 226a9cb8cdd..69a58d326b2 100644 --- a/src/System.Management.Automation/utils/ExecutionExceptions.cs +++ b/src/System.Management.Automation/utils/ExecutionExceptions.cs @@ -19,7 +19,6 @@ namespace System.Management.Automation /// /// InnerException is the error which the cmdlet hit. /// - [Serializable] public class CmdletInvocationException : RuntimeException { #region ctor @@ -30,10 +29,7 @@ public class CmdletInvocationException : RuntimeException internal CmdletInvocationException(ErrorRecord errorRecord) : base(RetrieveMessage(errorRecord), RetrieveException(errorRecord)) { - if (errorRecord == null) - { - throw new ArgumentNullException(nameof(errorRecord)); - } + ArgumentNullException.ThrowIfNull(errorRecord); _errorRecord = errorRecord; if (errorRecord.Exception != null) @@ -55,14 +51,10 @@ internal CmdletInvocationException(Exception innerException, InvocationInfo invocationInfo) : base(RetrieveMessage(innerException), innerException) { - if (innerException == null) - { - throw new ArgumentNullException(nameof(innerException)); - } + ArgumentNullException.ThrowIfNull(innerException); // invocationInfo may be null - IContainsErrorRecord icer = innerException as IContainsErrorRecord; - if (icer != null && icer.ErrorRecord != null) + if (innerException is IContainsErrorRecord icer && icer.ErrorRecord != null) { _errorRecord = new ErrorRecord(icer.ErrorRecord, innerException); } @@ -122,33 +114,12 @@ public CmdletInvocationException(string message, /// Serialization information. /// Streaming context. /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected CmdletInvocationException(SerializationInfo info, StreamingContext context) - : base(info, context) { - bool hasErrorRecord = info.GetBoolean("HasErrorRecord"); - if (hasErrorRecord) - _errorRecord = (ErrorRecord)info.GetValue("ErrorRecord", typeof(ErrorRecord)); - } - - /// - /// Serializer for - /// - /// Serialization information. - /// Streaming context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - bool hasErrorRecord = (_errorRecord != null); - info.AddValue("HasErrorRecord", hasErrorRecord); - if (hasErrorRecord) - info.AddValue("ErrorRecord", _errorRecord); - } + throw new NotSupportedException(); + } #endregion Serialization #endregion ctor @@ -161,14 +132,11 @@ public override ErrorRecord ErrorRecord { get { - if (_errorRecord == null) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - "CmdletInvocationException", - ErrorCategory.NotSpecified, - null); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + "CmdletInvocationException", + ErrorCategory.NotSpecified, + null); return _errorRecord; } @@ -186,8 +154,7 @@ public override ErrorRecord ErrorRecord /// . /// This is generally reported from the standard provider navigation cmdlets /// such as get-childitem. - /// - [Serializable] + /// public class CmdletProviderInvocationException : CmdletInvocationException { #region ctor @@ -204,10 +171,7 @@ internal CmdletProviderInvocationException( InvocationInfo myInvocation) : base(GetInnerException(innerException), myInvocation) { - if (innerException == null) - { - throw new ArgumentNullException(nameof(innerException)); - } + ArgumentNullException.ThrowIfNull(innerException); _providerInvocationException = innerException; } @@ -229,11 +193,11 @@ public CmdletProviderInvocationException() /// Serialization information. /// Streaming context. /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected CmdletProviderInvocationException(SerializationInfo info, StreamingContext context) - : base(info, context) { - _providerInvocationException = InnerException as ProviderInvocationException; + throw new NotSupportedException(); } /// @@ -310,15 +274,14 @@ private static Exception GetInnerException(Exception e) /// user hitting CTRL-C, or by a call to /// . /// - /// When a cmdlet or provider sees this exception thrown from a Monad API such as + /// When a cmdlet or provider sees this exception thrown from a PowerShell API such as /// WriteObject(object) /// this means that the command was already stopped. The cmdlet or provider /// should clean up and return. /// Catching this exception is optional; if the cmdlet or providers chooses not to /// handle PipelineStoppedException and instead allow it to propagate to the - /// Monad Engine's call to ProcessRecord, the Monad Engine will handle it properly. - /// - [Serializable] + /// PowerShell Engine's call to ProcessRecord, the PowerShell Engine will handle it properly. + /// public class PipelineStoppedException : RuntimeException { #region ctor @@ -341,12 +304,11 @@ public PipelineStoppedException() /// Serialization information. /// Streaming context. /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PipelineStoppedException(SerializationInfo info, StreamingContext context) - : base(info, context) { - // no properties, nothing more to serialize - // no need for a GetObjectData implementation + throw new NotSupportedException(); } /// @@ -380,8 +342,7 @@ public PipelineStoppedException(string message, /// to an asynchronous pipeline source and the pipeline has already /// been stopped. /// - /// - [Serializable] + /// public class PipelineClosedException : RuntimeException { #region ctor @@ -426,10 +387,11 @@ public PipelineClosedException(string message, /// Serialization information. /// Streaming context. /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PipelineClosedException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion Serialization } @@ -443,8 +405,7 @@ protected PipelineClosedException(SerializationInfo info, /// /// For example, if $WarningPreference is "Stop", the command will fail with /// this error if a cmdlet calls WriteWarning. - /// - [Serializable] + /// public class ActionPreferenceStopException : RuntimeException { #region ctor @@ -467,10 +428,7 @@ public ActionPreferenceStopException() internal ActionPreferenceStopException(ErrorRecord error) : this(RetrieveMessage(error)) { - if (error == null) - { - throw new ArgumentNullException(nameof(error)); - } + ArgumentNullException.ThrowIfNull(error); _errorRecord = error; } @@ -495,10 +453,7 @@ internal ActionPreferenceStopException(InvocationInfo invocationInfo, string message) : this(invocationInfo, message) { - if (errorRecord == null) - { - throw new ArgumentNullException(nameof(errorRecord)); - } + ArgumentNullException.ThrowIfNull(errorRecord); _errorRecord = errorRecord; } @@ -512,47 +467,12 @@ internal ActionPreferenceStopException(InvocationInfo invocationInfo, /// Serialization information. /// Streaming context. /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ActionPreferenceStopException(SerializationInfo info, StreamingContext context) - : base(info, context) { - bool hasErrorRecord = info.GetBoolean("HasErrorRecord"); - if (hasErrorRecord) - _errorRecord = (ErrorRecord)info.GetValue("ErrorRecord", typeof(ErrorRecord)); - - // fix for BUG: Windows Out Of Band Releases: 906263 and 906264 - // The interpreter prompt CommandBaseStrings:InquireHalt - // should be suppressed when this flag is set. This will be set - // when this prompt has already occurred and Break was chosen, - // or for ActionPreferenceStopException in all cases. - this.SuppressPromptInInterpreter = true; - } - - /// - /// Serializer for - /// - /// Serialization information. - /// Streaming context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - base.GetObjectData(info, context); - if (info != null) - { - bool hasErrorRecord = (_errorRecord != null); - info.AddValue("HasErrorRecord", hasErrorRecord); - if (hasErrorRecord) - { - info.AddValue("ErrorRecord", _errorRecord); - } - } - - // fix for BUG: Windows Out Of Band Releases: 906263 and 906264 - // The interpreter prompt CommandBaseStrings:InquireHalt - // should be suppressed when this flag is set. This will be set - // when this prompt has already occurred and Break was chosen, - // or for ActionPreferenceStopException in all cases. - this.SuppressPromptInInterpreter = true; - } + throw new NotSupportedException(); + } #endregion Serialization /// @@ -619,15 +539,14 @@ public override ErrorRecord ErrorRecord #region ParentContainsErrorRecordException /// /// ParentContainsErrorRecordException is the exception contained by the ErrorRecord - /// which is associated with a Monad engine custom exception through + /// which is associated with a PowerShell engine custom exception through /// the IContainsErrorRecord interface. /// /// /// We use this exception class /// so that there is not a recursive "containment" relationship - /// between the Monad engine exception and its ErrorRecord. + /// between the PowerShell engine exception and its ErrorRecord. /// - [Serializable] public class ParentContainsErrorRecordException : SystemException { #region Constructors @@ -693,11 +612,11 @@ public ParentContainsErrorRecordException(string message, /// Streaming context. /// Doesn't return. /// Always. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ParentContainsErrorRecordException( SerializationInfo info, StreamingContext context) - : base(info, context) { - _message = info.GetString("ParentContainsErrorRecordException_Message"); + throw new NotSupportedException(); } #endregion Serialization /// @@ -711,22 +630,6 @@ public override string Message } } - /// - /// Serializer for - /// - /// Serialization information. - /// Context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - info.AddValue("ParentContainsErrorRecordException_Message", this.Message); - } - #region Private Data private readonly Exception _wrapperException; @@ -745,8 +648,7 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont /// The redirected object is available as /// /// in the ErrorRecord which contains this exception. - /// - [Serializable] + /// public class RedirectedException : RuntimeException { #region constructors @@ -795,10 +697,11 @@ public RedirectedException(string message, /// Serialization information. /// Streaming context. /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected RedirectedException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion constructors } @@ -811,13 +714,12 @@ protected RedirectedException(SerializationInfo info, /// exceeds the configured maximum. /// /// - /// When one Monad command or script calls another, this creates an additional - /// scope. Some script expressions also create a scope. Monad imposes a maximum + /// When one PowerShell command or script calls another, this creates an additional + /// scope. Some script expressions also create a scope. PowerShell imposes a maximum /// 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. - /// - [Serializable] + /// public class ScriptCallDepthException : SystemException, IContainsErrorRecord { #region ctor @@ -863,19 +765,11 @@ public ScriptCallDepthException(string message, /// Serialization information. /// Streaming context. /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ScriptCallDepthException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - /// - /// Serializer for - /// - /// Serialization information. - /// Context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) { - base.GetObjectData(info, context); + throw new NotSupportedException(); } #endregion Serialization @@ -891,14 +785,11 @@ public ErrorRecord ErrorRecord { get { - if (_errorRecord == null) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - "CallDepthOverflow", - ErrorCategory.InvalidOperation, - CallDepth); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + "CallDepthOverflow", + ErrorCategory.InvalidOperation, + CallDepth); return _errorRecord; } @@ -925,7 +816,6 @@ public int CallDepth /// /// /// - [Serializable] public class PipelineDepthException : SystemException, IContainsErrorRecord { #region ctor @@ -970,19 +860,11 @@ public PipelineDepthException(string message, /// Serialization information. /// Streaming context. /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PipelineDepthException(SerializationInfo info, - StreamingContext context) - : base(info, context) + StreamingContext context) { - } - /// - /// Serializer for - /// - /// Serialization information. - /// Context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - base.GetObjectData(info, context); + throw new NotSupportedException(); } #endregion Serialization @@ -999,14 +881,11 @@ public ErrorRecord ErrorRecord { get { - if (_errorRecord == null) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - "CallDepthOverflow", - ErrorCategory.InvalidOperation, - CallDepth); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + "CallDepthOverflow", + ErrorCategory.InvalidOperation, + CallDepth); return _errorRecord; } @@ -1039,8 +918,7 @@ public int CallDepth /// /// Note that HaltCommandException does not define IContainsErrorRecord. /// This is because it is not reported to the user. - /// - [Serializable] + /// public class HaltCommandException : SystemException { #region ctor @@ -1085,10 +963,11 @@ public HaltCommandException(string message, /// Serialization information. /// Streaming context. /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected HaltCommandException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion Serialization } diff --git a/src/System.Management.Automation/utils/FormatAndTypeDataHelper.cs b/src/System.Management.Automation/utils/FormatAndTypeDataHelper.cs index d4d877355c4..87da1d2a1d7 100644 --- a/src/System.Management.Automation/utils/FormatAndTypeDataHelper.cs +++ b/src/System.Management.Automation/utils/FormatAndTypeDataHelper.cs @@ -73,7 +73,7 @@ internal static class FormatAndTypeDataHelper private static string GetBaseFolder(Collection independentErrors) { - return Path.GetDirectoryName(PsUtils.GetMainModule(System.Diagnostics.Process.GetCurrentProcess()).FileName); + return Path.GetDirectoryName(Environment.ProcessPath); } private static string GetAndCheckFullFileName( diff --git a/src/System.Management.Automation/utils/FuzzyMatch.cs b/src/System.Management.Automation/utils/FuzzyMatch.cs index 5b21022a6ef..c4e542a3ca5 100644 --- a/src/System.Management.Automation/utils/FuzzyMatch.cs +++ b/src/System.Management.Automation/utils/FuzzyMatch.cs @@ -1,23 +1,38 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Collections.Generic; using System.Globalization; namespace System.Management.Automation { - internal static class FuzzyMatcher + internal class FuzzyMatcher { - public const int MinimumDistance = 5; + internal readonly uint MinimumDistance; + + internal FuzzyMatcher(uint minimumDistance) + { + MinimumDistance = minimumDistance; + } /// /// Determine if the two strings are considered similar. /// - /// The first string to compare. - /// The second string to compare. + internal bool IsFuzzyMatch(string candidate, string pattern) + { + return IsFuzzyMatch(candidate, pattern, out _); + } + + /// + /// Determine if the two strings are considered similar, and return the similarity score. + /// + /// The candidate string to be compared. + /// The pattern string to be compared with. /// True if the two strings have a distance <= MinimumDistance. - public static bool IsFuzzyMatch(string string1, string string2) + internal bool IsFuzzyMatch(string candidate, string pattern, out int score) { - return GetDamerauLevenshteinDistance(string1, string2) <= MinimumDistance; + score = GetDamerauLevenshteinDistance(candidate, pattern); + return score <= MinimumDistance; } /// @@ -27,7 +42,7 @@ public static bool IsFuzzyMatch(string string1, string string2) /// The first string to compare. /// The second string to compare. /// The distance value where the lower the value the shorter the distance between the two strings representing a closer match. - public static int GetDamerauLevenshteinDistance(string string1, string string2) + internal static int GetDamerauLevenshteinDistance(string string1, string string2) { string1 = string1.ToUpper(CultureInfo.CurrentCulture); string2 = string2.ToUpper(CultureInfo.CurrentCulture); @@ -36,8 +51,15 @@ public static int GetDamerauLevenshteinDistance(string string1, string string2) int[,] matrix = new int[bounds.Height, bounds.Width]; - for (int height = 0; height < bounds.Height; height++) { matrix[height, 0] = height; } - for (int width = 0; width < bounds.Width; width++) { matrix[0, width] = width; } + for (int height = 0; height < bounds.Height; height++) + { + matrix[height, 0] = height; + } + + for (int width = 0; width < bounds.Width; width++) + { + matrix[0, width] = width; + } for (int height = 1; height < bounds.Height; height++) { diff --git a/src/System.Management.Automation/utils/GraphicalHostReflectionWrapper.cs b/src/System.Management.Automation/utils/GraphicalHostReflectionWrapper.cs index f38bdb18f87..a0b12f986de 100644 --- a/src/System.Management.Automation/utils/GraphicalHostReflectionWrapper.cs +++ b/src/System.Management.Automation/utils/GraphicalHostReflectionWrapper.cs @@ -18,7 +18,7 @@ namespace System.Management.Automation.Internal /// 2) show-command window implementation (the actual cmdlet is in Microsoft.PowerShell.Commands.Utility.dll) /// 3) the help window used in the System.Management.Automation.dll's get-help cmdlet when -ShowWindow is specified. /// - internal class GraphicalHostReflectionWrapper + internal sealed class GraphicalHostReflectionWrapper { /// /// Initialized in GetGraphicalHostReflectionWrapper with the Microsoft.PowerShell.GraphicalHost.dll assembly. diff --git a/src/System.Management.Automation/utils/HostInterfacesExceptions.cs b/src/System.Management.Automation/utils/HostInterfacesExceptions.cs index 91e742fa3c5..362a1448207 100644 --- a/src/System.Management.Automation/utils/HostInterfacesExceptions.cs +++ b/src/System.Management.Automation/utils/HostInterfacesExceptions.cs @@ -10,7 +10,6 @@ namespace System.Management.Automation.Host /// Defines the exception thrown when the Host cannot complete an operation /// such as checking whether there is any input available. /// - [Serializable] public class HostException : RuntimeException { @@ -101,10 +100,11 @@ class HostException : RuntimeException /// /// The contextual information about the source or destination. /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected HostException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion @@ -120,8 +120,7 @@ private void SetDefaultErrorRecord() /// /// Defines the exception thrown when an error occurs from prompting for a command parameter. - /// - [Serializable] + /// public class PromptingException : HostException { @@ -210,10 +209,11 @@ class PromptingException : HostException /// /// The contextual information about the source or destination. /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PromptingException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion diff --git a/src/System.Management.Automation/utils/MetadataExceptions.cs b/src/System.Management.Automation/utils/MetadataExceptions.cs index 53f2328fc3d..f2c4ace8647 100644 --- a/src/System.Management.Automation/utils/MetadataExceptions.cs +++ b/src/System.Management.Automation/utils/MetadataExceptions.cs @@ -10,7 +10,6 @@ namespace System.Management.Automation /// /// Defines the exception thrown for all Metadata errors. /// - [Serializable] public class MetadataException : RuntimeException { internal const string MetadataMemberInitialization = "MetadataMemberInitialization"; @@ -21,9 +20,10 @@ public class MetadataException : RuntimeException /// /// Serialization information. /// Streaming context. - protected MetadataException(SerializationInfo info, StreamingContext context) : base(info, context) + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + protected MetadataException(SerializationInfo info, StreamingContext context) { - SetErrorCategory(ErrorCategory.MetadataError); + throw new NotSupportedException(); } /// @@ -48,7 +48,7 @@ public MetadataException(string message) : base(message) /// Initializes a new instance of MetadataException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. + /// The exception's inner exception. public MetadataException(string message, Exception innerException) : base(message, innerException) { SetErrorCategory(ErrorCategory.MetadataError); @@ -71,7 +71,6 @@ internal MetadataException( /// /// Defines the exception thrown for all Validate attributes. /// - [Serializable] [SuppressMessage("Microsoft.Usage", "CA2240:ImplementISerializableCorrectly")] public class ValidationMetadataException : MetadataException { @@ -109,7 +108,12 @@ public class ValidationMetadataException : MetadataException /// /// Serialization information. /// Streaming context. - protected ValidationMetadataException(SerializationInfo info, StreamingContext context) : base(info, context) { } + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + protected ValidationMetadataException(SerializationInfo info, StreamingContext context) + { + throw new NotSupportedException(); + } + /// /// Initializes a new instance of ValidationMetadataException with the message set /// to typeof(ValidationMetadataException).FullName. @@ -124,7 +128,7 @@ public ValidationMetadataException(string message) : this(message, false) { } /// Initializes a new instance of ValidationMetadataException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. + /// The exception's inner exception. public ValidationMetadataException(string message, Exception innerException) : base(message, innerException) { } internal ValidationMetadataException( @@ -167,7 +171,6 @@ internal bool SwallowException /// /// Defines the exception thrown for all ArgumentTransformation attributes. /// - [Serializable] public class ArgumentTransformationMetadataException : MetadataException { internal const string ArgumentTransformationArgumentsShouldBeStrings = "ArgumentTransformationArgumentsShouldBeStrings"; @@ -177,8 +180,11 @@ public class ArgumentTransformationMetadataException : MetadataException /// /// Serialization information. /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ArgumentTransformationMetadataException(SerializationInfo info, StreamingContext context) - : base(info, context) { } + { + throw new NotSupportedException(); + } /// /// Initializes a new instance of ArgumentTransformationMetadataException with the message set @@ -198,7 +204,7 @@ public ArgumentTransformationMetadataException(string message) /// Initializes a new instance of ArgumentTransformationMetadataException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. + /// The exception's inner exception. public ArgumentTransformationMetadataException(string message, Exception innerException) : base(message, innerException) { } @@ -215,7 +221,6 @@ internal ArgumentTransformationMetadataException( /// /// Defines the exception thrown for all parameter binding exceptions related to metadata attributes. /// - [Serializable] public class ParsingMetadataException : MetadataException { internal const string ParsingTooManyParameterSets = "ParsingTooManyParameterSets"; @@ -225,8 +230,11 @@ public class ParsingMetadataException : MetadataException /// /// Serialization information. /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ParsingMetadataException(SerializationInfo info, StreamingContext context) - : base(info, context) { } + { + throw new NotSupportedException(); + } /// /// Initializes a new instance of ParsingMetadataException with the message set @@ -246,7 +254,7 @@ public ParsingMetadataException(string message) /// Initializes a new instance of ParsingMetadataException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. + /// The exception's inner exception. public ParsingMetadataException(string message, Exception innerException) : base(message, innerException) { } diff --git a/src/System.Management.Automation/utils/MshArgumentException.cs b/src/System.Management.Automation/utils/MshArgumentException.cs index d9c9ce725eb..baa3f8c3163 100644 --- a/src/System.Management.Automation/utils/MshArgumentException.cs +++ b/src/System.Management.Automation/utils/MshArgumentException.cs @@ -13,10 +13,9 @@ namespace System.Management.Automation /// /// /// Instances of this exception class are usually generated by the - /// Monad Engine. It is unusual for code outside the Monad Engine + /// PowerShell Engine. It is unusual for code outside the PowerShell Engine /// to create an instance of this class. /// - [Serializable] public class PSArgumentException : ArgumentException, IContainsErrorRecord { @@ -69,30 +68,13 @@ public PSArgumentException(string message, string paramName) /// Serialization information. /// Streaming context. /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSArgumentException(SerializationInfo info, StreamingContext context) - : base(info, context) { - _errorId = info.GetString("ErrorId"); - _message = info.GetString("PSArgumentException_MessageOverride"); + throw new NotSupportedException(); } - /// - /// Serializer for - /// - /// Serialization information. - /// Streaming context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - info.AddValue("ErrorId", _errorId); - info.AddValue("PSArgumentException_MessageOverride", _message); - } #endregion Serialization /// @@ -121,14 +103,11 @@ public ErrorRecord ErrorRecord { get { - if (_errorRecord == null) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - _errorId, - ErrorCategory.InvalidArgument, - null); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + _errorId, + ErrorCategory.InvalidArgument, + null); return _errorRecord; } diff --git a/src/System.Management.Automation/utils/MshArgumentNullException.cs b/src/System.Management.Automation/utils/MshArgumentNullException.cs index be20163607b..a4f50256e44 100644 --- a/src/System.Management.Automation/utils/MshArgumentNullException.cs +++ b/src/System.Management.Automation/utils/MshArgumentNullException.cs @@ -13,10 +13,9 @@ namespace System.Management.Automation /// /// /// Instances of this exception class are usually generated by the - /// Monad Engine. It is unusual for code outside the Monad Engine + /// PowerShell Engine. It is unusual for code outside the PowerShell Engine /// to create an instance of this class. /// - [Serializable] public class PSArgumentNullException : ArgumentNullException, IContainsErrorRecord { @@ -80,30 +79,12 @@ public PSArgumentNullException(string paramName, string message) /// Serialization information. /// Streaming context. /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSArgumentNullException(SerializationInfo info, StreamingContext context) - : base(info, context) { - _errorId = info.GetString("ErrorId"); - _message = info.GetString("PSArgumentNullException_MessageOverride"); - } - - /// - /// Serializer for - /// - /// Serialization information. - /// Streaming context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - info.AddValue("ErrorId", _errorId); - info.AddValue("PSArgumentNullException_MessageOverride", _message); - } + throw new NotSupportedException(); + } #endregion Serialization #endregion ctor @@ -119,14 +100,11 @@ public ErrorRecord ErrorRecord { get { - if (_errorRecord == null) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - _errorId, - ErrorCategory.InvalidArgument, - null); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + _errorId, + ErrorCategory.InvalidArgument, + null); return _errorRecord; } diff --git a/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs b/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs index 2bf48e9047f..8d666778a4f 100644 --- a/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs +++ b/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs @@ -13,10 +13,9 @@ namespace System.Management.Automation /// /// /// Instances of this exception class are usually generated by the - /// Monad Engine. It is unusual for code outside the Monad Engine + /// PowerShell Engine. It is unusual for code outside the PowerShell Engine /// to create an instance of this class. /// - [Serializable] public class PSArgumentOutOfRangeException : ArgumentOutOfRangeException, IContainsErrorRecord { @@ -68,28 +67,13 @@ public PSArgumentOutOfRangeException(string paramName, object actualValue, strin /// Serialization information. /// Streaming context. /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSArgumentOutOfRangeException(SerializationInfo info, StreamingContext context) - : base(info, context) { - _errorId = info.GetString("ErrorId"); - } - - /// - /// Serializer for - /// - /// Serialization information. - /// Streaming context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - info.AddValue("ErrorId", _errorId); + throw new NotSupportedException(); } + #endregion Serialization /// @@ -117,14 +101,11 @@ public ErrorRecord ErrorRecord { get { - if (_errorRecord == null) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - _errorId, - ErrorCategory.InvalidArgument, - null); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + _errorId, + ErrorCategory.InvalidArgument, + null); return _errorRecord; } diff --git a/src/System.Management.Automation/utils/MshInvalidOperationException.cs b/src/System.Management.Automation/utils/MshInvalidOperationException.cs index f9a65fcf8aa..f398e4a2e99 100644 --- a/src/System.Management.Automation/utils/MshInvalidOperationException.cs +++ b/src/System.Management.Automation/utils/MshInvalidOperationException.cs @@ -13,10 +13,9 @@ namespace System.Management.Automation /// /// /// Instances of this exception class are usually generated by the - /// Monad Engine. It is unusual for code outside the Monad Engine + /// PowerShell Engine. It is unusual for code outside the PowerShell Engine /// to create an instance of this class. /// - [Serializable] public class PSInvalidOperationException : InvalidOperationException, IContainsErrorRecord { @@ -39,27 +38,11 @@ public PSInvalidOperationException() /// Serialization information. /// Streaming context. /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSInvalidOperationException(SerializationInfo info, StreamingContext context) - : base(info, context) { - _errorId = info.GetString("ErrorId"); - } - - /// - /// Serializer for - /// - /// Serialization information. - /// Streaming context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - info.AddValue("ErrorId", _errorId); + throw new NotSupportedException(); } #endregion Serialization @@ -115,14 +98,11 @@ public ErrorRecord ErrorRecord { get { - if (_errorRecord == null) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - _errorId, - _errorCategory, - _target); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + _errorId, + _errorCategory, + _target); return _errorRecord; } diff --git a/src/System.Management.Automation/utils/MshNotImplementedException.cs b/src/System.Management.Automation/utils/MshNotImplementedException.cs index 6c2dd504cf0..7d1082bf747 100644 --- a/src/System.Management.Automation/utils/MshNotImplementedException.cs +++ b/src/System.Management.Automation/utils/MshNotImplementedException.cs @@ -13,10 +13,9 @@ namespace System.Management.Automation /// /// /// Instances of this exception class are usually generated by the - /// Monad Engine. It is unusual for code outside the Monad Engine + /// PowerShell Engine. It is unusual for code outside the PowerShell Engine /// to create an instance of this class. /// - [Serializable] public class PSNotImplementedException : NotImplementedException, IContainsErrorRecord { @@ -39,28 +38,12 @@ public PSNotImplementedException() /// Serialization information. /// Streaming context. /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSNotImplementedException(SerializationInfo info, StreamingContext context) - : base(info, context) { - _errorId = info.GetString("ErrorId"); - } - - /// - /// Serializer for - /// - /// Serialization information. - /// Streaming context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - info.AddValue("ErrorId", _errorId); - } + throw new NotSupportedException(); + } #endregion Serialization /// @@ -98,14 +81,11 @@ public ErrorRecord ErrorRecord { get { - if (_errorRecord == null) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - _errorId, - ErrorCategory.NotImplemented, - null); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + _errorId, + ErrorCategory.NotImplemented, + null); return _errorRecord; } diff --git a/src/System.Management.Automation/utils/MshNotSupportedException.cs b/src/System.Management.Automation/utils/MshNotSupportedException.cs index 7614080dcff..1268636742b 100644 --- a/src/System.Management.Automation/utils/MshNotSupportedException.cs +++ b/src/System.Management.Automation/utils/MshNotSupportedException.cs @@ -13,10 +13,9 @@ namespace System.Management.Automation /// /// /// Instances of this exception class are usually generated by the - /// Monad Engine. It is unusual for code outside the Monad Engine + /// PowerShell Engine. It is unusual for code outside the PowerShell Engine /// to create an instance of this class. /// - [Serializable] public class PSNotSupportedException : NotSupportedException, IContainsErrorRecord { @@ -39,28 +38,13 @@ public PSNotSupportedException() /// Serialization information. /// Streaming context. /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSNotSupportedException(SerializationInfo info, StreamingContext context) - : base(info, context) { - _errorId = info.GetString("ErrorId"); - } - - /// - /// Serializer for - /// - /// Serialization information. - /// Streaming context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - info.AddValue("ErrorId", _errorId); + throw new NotSupportedException(); } + #endregion Serialization /// @@ -98,14 +82,11 @@ public ErrorRecord ErrorRecord { get { - if (_errorRecord == null) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - _errorId, - ErrorCategory.NotImplemented, - null); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + _errorId, + ErrorCategory.NotImplemented, + null); return _errorRecord; } diff --git a/src/System.Management.Automation/utils/MshObjectDisposedException.cs b/src/System.Management.Automation/utils/MshObjectDisposedException.cs index b8d4ab421cc..1bbf1f846d4 100644 --- a/src/System.Management.Automation/utils/MshObjectDisposedException.cs +++ b/src/System.Management.Automation/utils/MshObjectDisposedException.cs @@ -13,10 +13,9 @@ namespace System.Management.Automation /// /// /// Instances of this exception class are usually generated by the - /// Monad Engine. It is unusual for code outside the Monad Engine + /// PowerShell Engine. It is unusual for code outside the PowerShell Engine /// to create an instance of this class. /// - [Serializable] public class PSObjectDisposedException : ObjectDisposedException, IContainsErrorRecord { @@ -67,28 +66,12 @@ public PSObjectDisposedException(string message, Exception innerException) /// Serialization information. /// Streaming context. /// Constructed object. - protected PSObjectDisposedException(SerializationInfo info, - StreamingContext context) - : base(info, context) + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + protected PSObjectDisposedException(SerializationInfo info, StreamingContext context) : base(info, context) { - _errorId = info.GetString("ErrorId"); + throw new NotSupportedException(); } - /// - /// Serializer for - /// - /// Serialization information. - /// Streaming context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - info.AddValue("ErrorId", _errorId); - } #endregion Serialization #endregion ctor @@ -104,14 +87,11 @@ public ErrorRecord ErrorRecord { get { - if (_errorRecord == null) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - _errorId, - ErrorCategory.InvalidOperation, - null); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + _errorId, + ErrorCategory.InvalidOperation, + null); return _errorRecord; } diff --git a/src/System.Management.Automation/utils/MshTraceSource.cs b/src/System.Management.Automation/utils/MshTraceSource.cs index 8ed5949c31a..dd7d3214ff3 100644 --- a/src/System.Management.Automation/utils/MshTraceSource.cs +++ b/src/System.Management.Automation/utils/MshTraceSource.cs @@ -10,14 +10,14 @@ namespace System.Management.Automation { /// /// An PSTraceSource is a representation of a System.Diagnostics.TraceSource instance - /// that is used in the Monad components to produce trace output. + /// that is used in the PowerShell components to produce trace output. /// /// /// It is permitted to subclass /// but there is no established scenario for doing this, nor has it been tested. /// /// @@ -22,6 +21,12 @@ + + + + + + diff --git a/test/hosting/test_HostingBasic.cs b/test/hosting/test_HostingBasic.cs index efd3043d514..1b4a67707e2 100644 --- a/test/hosting/test_HostingBasic.cs +++ b/test/hosting/test_HostingBasic.cs @@ -51,7 +51,7 @@ public static void TestCommandFromCore() foreach (dynamic item in results) { - Assert.Equal(6,item); + Assert.Equal(6, item); } } } @@ -183,6 +183,19 @@ public static void TestConsoleShellScenario() Assert.Equal(42, ret); } + /* Test disabled because CommandLineParser is static and can only be initialized once (above in TestConsoleShellScenario) + /// + /// ConsoleShell cannot start with both InitialSessionState and -ConfigurationFile argument configurations specified. + /// + [Fact] + public static void TestConsoleShellConfigConflictError() + { + var iss = System.Management.Automation.Runspaces.InitialSessionState.CreateDefault2(); + int ret = ConsoleShell.Start(iss, "BannerText", string.Empty, new string[] { @"-ConfigurationFile ""noneSuch""" }); + Assert.Equal(70, ret); // ExitCodeInitFailure. + } + */ + [Fact] public static void TestBuiltInModules() { diff --git a/test/packaging/windows/msi.tests.ps1 b/test/packaging/windows/msi.tests.ps1 index 19df125691b..14dc40a6ff2 100644 --- a/test/packaging/windows/msi.tests.ps1 +++ b/test/packaging/windows/msi.tests.ps1 @@ -3,6 +3,7 @@ Describe -Name "Windows MSI" -Fixture { BeforeAll { + Set-StrictMode -Off function Test-Elevated { [CmdletBinding()] [OutputType([bool])] @@ -14,6 +15,62 @@ Describe -Name "Windows MSI" -Fixture { return (([Security.Principal.WindowsIdentity]::GetCurrent()).Groups -contains "S-1-5-32-544") } + function Test-IsMuEnabled { + $sm = (New-Object -ComObject Microsoft.Update.ServiceManager) + $mu = $sm.Services | Where-Object { $_.ServiceId -eq '7971f918-a847-4430-9279-4a52d1efe18d' } + if ($mu) { + return $true + } + return $false + } + + function Invoke-TestAndUploadLogOnFailure { + param ( + [scriptblock] $Test + ) + + try { + & $Test + } + catch { + Send-VstsLogFile -Path $msiLog + throw + } + } + + function Get-UseMU { + $useMu = $null + $key = 'HKLM:\SOFTWARE\Microsoft\PowerShellCore\' + if ($runtime -like '*x86*') { + $key = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\PowerShellCore\' + } + + try { + $useMu = Get-ItemPropertyValue -Path $key -Name UseMU -ErrorAction SilentlyContinue + } catch {} + + if (!$useMu) { + $useMu = 0 + } + + return $useMu + } + + function Set-UseMU { + param( + [int] + $Value + ) + $key = 'HKLM:\SOFTWARE\Microsoft\PowerShellCore\' + if ($runtime -like '*x86*') { + $key = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\PowerShellCore\' + } + + Set-ItemProperty -Path $key -Name UseMU -Value $Value -Type DWord + + return $useMu + } + function Invoke-Msiexec { param( [Parameter(ParameterSetName = 'Install', Mandatory)] @@ -45,6 +102,7 @@ Describe -Name "Windows MSI" -Fixture { } $argumentList = "$switch $MsiPath /quiet /l*vx $msiLog $additionalOptions" + Write-Verbose -Message "running msiexec $argumentList" $msiExecProcess = Start-Process msiexec.exe -Wait -ArgumentList $argumentList -NoNewWindow -PassThru if ($msiExecProcess.ExitCode -ne 0) { $exitCode = $msiExecProcess.ExitCode @@ -55,6 +113,28 @@ Describe -Name "Windows MSI" -Fixture { $msiX64Path = $env:PsMsiX64Path $channel = $env:PSMsiChannel $runtime = $env:PSMsiRuntime + $muEnabled = Test-IsMuEnabled + + if ($runtime -like '*x86*') { + $propertiesRegKeyParent = "HKLM:\SOFTWARE\Wow6432Node\Microsoft\PowerShellCore" + } else { + $propertiesRegKeyParent = "HKLM:\SOFTWARE\Microsoft\PowerShellCore" + } + + if ($channel -eq "preview") { + $propertiesRegKeyName = "PreviewInstallerProperties" + } else { + $propertiesRegKeyName = "InstallerProperties" + } + + # Rename the registry key that contains the saved installer + # properties so that the tests don't overwrite them. + $propertiesRegKeyPath = Join-Path -Path $propertiesRegKeyParent -ChildPath $propertiesRegKeyName + $propertiesBackupRegKeyName = "BackupInstallerProperties" + $propertiesBackupRegKeyPath = Join-Path -Path $propertiesRegKeyParent -ChildPath $propertiesBackupRegKeyName + if (Test-Path -Path $propertiesRegKeyPath) { + Rename-Item -Path $propertiesRegKeyPath -NewName $propertiesBackupRegKeyName + } # Get any existing powershell in the path $beforePath = @(([System.Environment]::GetEnvironmentVariable('PATH', 'MACHINE')) -split ';' | @@ -71,32 +151,53 @@ Describe -Name "Windows MSI" -Fixture { } $uploadedLog = $false } + + AfterAll { + Set-StrictMode -Version 3.0 + + # Restore the original saved installer properties registry key. + Remove-Item -Path $propertiesRegKeyPath -ErrorAction SilentlyContinue + if (Test-Path -Path $propertiesBackupRegKeyPath) { + Rename-Item -Path $propertiesBackupRegKeyPath -NewName $propertiesRegKeyName + } + } + BeforeEach { $error.Clear() - } - AfterEach { - if ($error.Count -ne 0 -and !$uploadedLog) { - Copy-Item -Path $msiLog -Destination $env:temp -Force - Write-Verbose "MSI log is at $env:temp\msilog.txt" -Verbose - $uploadedLog = $true - } + Remove-Item -Path $propertiesRegKeyPath -ErrorAction SilentlyContinue } Context "Upgrade code" { BeforeAll { Write-Verbose "cr-$channel-$runtime" -Verbose + $pwshPath = Join-Path $env:ProgramFiles -ChildPath "PowerShell" + $pwshx86Path = Join-Path ${env:ProgramFiles(x86)} -ChildPath "PowerShell" + $regKeyPath = "HKLM:\SOFTWARE\Microsoft\PowerShellCore\InstalledVersions" + switch ("$channel-$runtime") { "preview-win7-x64" { + $versionPath = Join-Path -Path $pwshPath -ChildPath '7-preview' + $revisionRange = 0, 99 $msiUpgradeCode = '39243d76-adaf-42b1-94fb-16ecf83237c8' + $regKeyPath = Join-Path $regKeyPath -ChildPath $msiUpgradeCode } "stable-win7-x64" { + $versionPath = Join-Path -Path $pwshPath -ChildPath '7' + $revisionRange = 500, 500 $msiUpgradeCode = '31ab5147-9a97-4452-8443-d9709f0516e1' + $regKeyPath = Join-Path $regKeyPath -ChildPath $msiUpgradeCode } "preview-win7-x86" { + $versionPath = Join-Path -Path $pwshx86Path -ChildPath '7-preview' + $revisionRange = 0, 99 $msiUpgradeCode = '86abcfbd-1ccc-4a88-b8b2-0facfde29094' + $regKeyPath = Join-Path $regKeyPath -ChildPath $msiUpgradeCode } "stable-win7-x86" { + $versionPath = Join-Path -Path $pwshx86Path -ChildPath '7' + $revisionRange = 500, 500 $msiUpgradeCode = '1d00683b-0f84-4db8-a64f-2f98ad42fe06' + $regKeyPath = Join-Path $regKeyPath -ChildPath $msiUpgradeCode } default { throw "'$_' not a valid channel runtime combination" @@ -120,6 +221,35 @@ Describe -Name "Windows MSI" -Fixture { $result.Count | Should -Be 1 -Because "Query should return 1 result if Upgrade code is for $runtime $channel" } + It "Revision should be in correct range" -Skip:(!(Test-Elevated)) { + $pwshDllPath = Join-Path -Path $versionPath -ChildPath "pwsh.dll" + [version] $version = (Get-ChildItem $pwshDllPath).VersionInfo.FileVersion + Write-Verbose "pwsh.dll version: $version" -Verbose + $version.Revision | Should -BeGreaterOrEqual $revisionRange[0] -Because "$channel revision should between $($revisionRange[0]) and $($revisionRange[1])" + $version.Revision | Should -BeLessOrEqual $revisionRange[1] -Because "$channel revision should between $($revisionRange[0]) and $($revisionRange[1])" + } + + It 'MSI should add ProductCode in registry' -Skip:(!(Test-Elevated)) { + + $productCode = if ($msiUpgradeCode -eq '39243d76-adaf-42b1-94fb-16ecf83237c8' -or + $msiUpgradeCode -eq '31ab5147-9a97-4452-8443-d9709f0516e1') { + # x64 + $regKeyPath | Should -Exist + Get-ItemPropertyValue -Path $regKeyPath -Name 'ProductCode' + } elseif ($msiUpgradeCode -eq '86abcfbd-1ccc-4a88-b8b2-0facfde29094' -or + $msiUpgradeCode -eq '1d00683b-0f84-4db8-a64f-2f98ad42fe06') { + # x86 - need to open the 32bit reghive + $wow32RegKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryView]::Registry32) + $subKey = $wow32RegKey.OpenSubKey("Software\Microsoft\PowerShellCore\InstalledVersions\$msiUpgradeCode") + $subKey.GetValue("ProductCode") + } + + $productCode | Should -Not -BeNullOrEmpty + $productCodeGuid = [Guid]$productCode + $productCodeGuid | Should -BeOfType "Guid" + $productCodeGuid.Guid | Should -Not -Be $msiUpgradeCode + } + It "MSI should uninstall without error" -Skip:(!(Test-Elevated)) { { Invoke-MsiExec -Uninstall -MsiPath $msiX64Path @@ -128,9 +258,18 @@ Describe -Name "Windows MSI" -Fixture { } Context "Add Path disabled" { + BeforeAll { + Set-UseMU -Value 0 + } + + It "UseMU should be 0 before install" -Skip:(!(Test-Elevated)) { + $useMu = Get-UseMU + $useMu | Should -Be 0 + } + It "MSI should install without error" -Skip:(!(Test-Elevated)) { { - Invoke-MsiExec -Install -MsiPath $msiX64Path -Properties @{ADD_PATH = 0} + Invoke-MsiExec -Install -MsiPath $msiX64Path -Properties @{ADD_PATH = 0; USE_MU = 1; ENABLE_MU = 1} } | Should -Not -Throw } @@ -141,6 +280,43 @@ Describe -Name "Windows MSI" -Fixture { $psPath | Should -BeNullOrEmpty } + It "UseMU should be 1" -Skip:(!(Test-Elevated)) { + Invoke-TestAndUploadLogOnFailure -Test { + $useMu = Get-UseMU + $useMu | Should -Be 1 + } + } + + It "MSI should uninstall without error" -Skip:(!(Test-Elevated)) { + { + Invoke-MsiExec -Uninstall -MsiPath $msiX64Path + } | Should -Not -Throw + } + } + + Context "USE_MU disabled" { + BeforeAll { + Set-UseMU -Value 0 + } + + It "UseMU should be 0 before install" -Skip:(!(Test-Elevated)) { + $useMu = Get-UseMU + $useMu | Should -Be 0 + } + + It "MSI should install without error" -Skip:(!(Test-Elevated)) { + { + Invoke-MsiExec -Install -MsiPath $msiX64Path -Properties @{USE_MU = 0} + } | Should -Not -Throw + } + + It "UseMU should be 0" -Skip:(!(Test-Elevated)) { + Invoke-TestAndUploadLogOnFailure -Test { + $useMu = Get-UseMU + $useMu | Should -Be 0 + } + } + It "MSI should uninstall without error" -Skip:(!(Test-Elevated)) { { Invoke-MsiExec -Uninstall -MsiPath $msiX64Path @@ -179,5 +355,43 @@ Describe -Name "Windows MSI" -Fixture { Invoke-MsiExec -Uninstall -MsiPath $msiX64Path } | Should -Not -Throw } + + Context "Disable Telemetry" { + It "MSI should set POWERSHELL_TELEMETRY_OPTOUT env variable when MSI property DISABLE_TELEMETRY is set to 1" -Skip:(!(Test-Elevated)) { + try { + $originalValue = [System.Environment]::GetEnvironmentVariable('POWERSHELL_TELEMETRY_OPTOUT', [System.EnvironmentVariableTarget]::Machine) + [System.Environment]::SetEnvironmentVariable('POWERSHELL_TELEMETRY_OPTOUT', '0', [System.EnvironmentVariableTarget]::Machine) + { + Invoke-MsiExec -Install -MsiPath $msiX64Path -Properties @{DISABLE_TELEMETRY = 1 } + } | Should -Not -Throw + [System.Environment]::GetEnvironmentVariable('POWERSHELL_TELEMETRY_OPTOUT', [System.EnvironmentVariableTarget]::Machine) | + Should -Be 1 + } + finally { + [System.Environment]::SetEnvironmentVariable('POWERSHELL_TELEMETRY_OPTOUT', $originalValue, [System.EnvironmentVariableTarget]::Machine) + { + Invoke-MsiExec -Uninstall -MsiPath $msiX64Path + } | Should -Not -Throw + } + } + + It "MSI should not change POWERSHELL_TELEMETRY_OPTOUT env variable when MSI property DISABLE_TELEMETRY not set" -Skip:(!(Test-Elevated)) { + try { + $originalValue = [System.Environment]::GetEnvironmentVariable('POWERSHELL_TELEMETRY_OPTOUT', [System.EnvironmentVariableTarget]::Machine) + [System.Environment]::SetEnvironmentVariable('POWERSHELL_TELEMETRY_OPTOUT', 'untouched', [System.EnvironmentVariableTarget]::Machine) + { + Invoke-MsiExec -Install -MsiPath $msiX64Path + } | Should -Not -Throw + [System.Environment]::GetEnvironmentVariable('POWERSHELL_TELEMETRY_OPTOUT', [System.EnvironmentVariableTarget]::Machine) | + Should -Be 'untouched' + } + finally { + [System.Environment]::SetEnvironmentVariable('POWERSHELL_TELEMETRY_OPTOUT', $originalValue, [System.EnvironmentVariableTarget]::Machine) + { + Invoke-MsiExec -Uninstall -MsiPath $msiX64Path + } | Should -Not -Throw + } + } + } } } diff --git a/test/perf/benchmarks/Categories.cs b/test/perf/benchmarks/Categories.cs new file mode 100644 index 00000000000..09f71064930 --- /dev/null +++ b/test/perf/benchmarks/Categories.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace MicroBenchmarks +{ + public static class Categories + { + /// + /// Benchmarks belonging to this category are executed for CI jobs. + /// + public const string Components = "Components"; + + /// + /// Benchmarks belonging to this category are executed for CI jobs. + /// + public const string Engine = "Engine"; + + /// + /// Benchmarks belonging to this category are targeting internal APIs. + /// + public const string Internal = "Internal"; + + /// + /// Benchmarks belonging to this category are targeting public APIs. + /// + public const string Public = "Public"; + } +} diff --git a/test/perf/benchmarks/Engine.Compiler.cs b/test/perf/benchmarks/Engine.Compiler.cs new file mode 100644 index 00000000000..4f9dbab88a9 --- /dev/null +++ b/test/perf/benchmarks/Engine.Compiler.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#if NET6_0 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Management.Automation; +using System.Management.Automation.Language; + +using BenchmarkDotNet.Attributes; +using MicroBenchmarks; + +namespace Engine +{ + [BenchmarkCategory(Categories.Engine, Categories.Internal)] + public class Compiler + { + private static readonly Dictionary s_scriptBlocksDict; + private static readonly List s_functionNames; + private ScriptBlockAst _currentAst; + + static Compiler() + { + string pattern = string.Format("{0}test{0}perf{0}benchmarks", Path.DirectorySeparatorChar); + string location = typeof(Compiler).Assembly.Location; + string testFilePath = null; + + int start = location.IndexOf(pattern, StringComparison.Ordinal); + if (start > 0) + { + testFilePath = Path.Join(location.AsSpan(0, start + pattern.Length), "assets", "compiler.test.ps1"); + } + + var topScriptBlockAst = Parser.ParseFile(testFilePath, tokens: out _, errors: out _); + var allFunctions = topScriptBlockAst.FindAll(ast => ast is FunctionDefinitionAst, searchNestedScriptBlocks: false); + + s_scriptBlocksDict = new Dictionary(capacity: 16); + s_functionNames = new List(capacity: 16); + + foreach (FunctionDefinitionAst function in allFunctions) + { + s_functionNames.Add(function.Name); + s_scriptBlocksDict.Add(function.Name, function.Body); + } + } + + [ParamsSource(nameof(FunctionName))] + public string FunctionsToCompile { get; set; } + + public IEnumerable FunctionName() => s_functionNames; + + [GlobalSetup(Target = nameof(CompileFunction))] + public void GlobalSetup() + { + _currentAst = s_scriptBlocksDict[FunctionsToCompile]; + + // Run it once to get the C# code jitted. + // The first call to this takes relatively too long, which makes the BDN's heuristic incorrectly + // 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. + // See https://github.com/dotnet/BenchmarkDotNet/issues/837#issuecomment-828600157 + CompileFunction(); + } + + [Benchmark] + public bool CompileFunction() + { + var compiledData = new CompiledScriptBlockData(_currentAst, isFilter: false); + return compiledData.Compile(true); + } + } +} + +#endif diff --git a/test/perf/benchmarks/Engine.Parser.cs b/test/perf/benchmarks/Engine.Parser.cs new file mode 100644 index 00000000000..10538e3201a --- /dev/null +++ b/test/perf/benchmarks/Engine.Parser.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Management.Automation.Language; +using BenchmarkDotNet.Attributes; +using MicroBenchmarks; + +namespace Engine +{ + [BenchmarkCategory(Categories.Engine, Categories.Public)] + public class Parsing + { + [Benchmark] + public Ast UsingStatement() + { + const string Script = @" + using module moduleA + using Assembly assemblyA + using namespace System.IO"; + return Parser.ParseInput(Script, out _, out _); + } + } +} diff --git a/test/perf/benchmarks/Engine.ScriptBlock.cs b/test/perf/benchmarks/Engine.ScriptBlock.cs new file mode 100644 index 00000000000..dfd7beb865f --- /dev/null +++ b/test/perf/benchmarks/Engine.ScriptBlock.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Runtime.InteropServices; +using BenchmarkDotNet.Attributes; +using MicroBenchmarks; + +namespace Engine +{ + [BenchmarkCategory(Categories.Engine, Categories.Public)] + public class Scripting + { + private Runspace runspace; + private ScriptBlock scriptBlock; + + private void SetupRunspace() + { + // Unless you want to run commands from any built-in modules, using 'CreateDefault2' is enough. + runspace = RunspaceFactory.CreateRunspace(InitialSessionState.CreateDefault2()); + runspace.Open(); + Runspace.DefaultRunspace = runspace; + } + + #region Invoke-Method + + [ParamsSource(nameof(ValuesForScript))] + public string InvokeMethodScript { get; set; } + + public IEnumerable ValuesForScript() + { + yield return @"'String'.GetType()"; + yield return @"[System.IO.Path]::HasExtension('')"; + + // Test on COM method invocation. + if (Platform.IsWindows) + { + yield return @"$sh=New-Object -ComObject Shell.Application; $sh.Namespace('c:\')"; + yield return @"$fs=New-Object -ComObject scripting.filesystemobject; $fs.Drives"; + } + } + + [GlobalSetup(Target = nameof(InvokeMethod))] + public void GlobalSetup() + { + SetupRunspace(); + scriptBlock = ScriptBlock.Create(InvokeMethodScript); + + // Run it once to get the C# code jitted and the script compiled. + // The first call to this takes relatively too long, which makes the BDN's heuristic incorrectly + // 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. + // See https://github.com/dotnet/BenchmarkDotNet/issues/837#issuecomment-828600157 + scriptBlock.Invoke(); + } + + [Benchmark] + public Collection InvokeMethod() + { + return scriptBlock.Invoke(); + } + + #endregion + + [GlobalCleanup] + public void GlobalCleanup() + { + runspace.Dispose(); + Runspace.DefaultRunspace = null; + } + } +} diff --git a/test/perf/benchmarks/Program.cs b/test/perf/benchmarks/Program.cs new file mode 100644 index 00000000000..53f9a3ce95b --- /dev/null +++ b/test/perf/benchmarks/Program.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using BenchmarkDotNet.Running; +using BenchmarkDotNet.Extensions; + +namespace MicroBenchmarks +{ + public sealed class Program + { + public static int Main(string[] args) + { + var argsList = new List(args); + int? partitionCount; + int? partitionIndex; + List exclusionFilterValue; + List categoryExclusionFilterValue; + bool getDiffableDisasm; + + // Parse and remove any additional parameters that we need that aren't part of BDN (BenchmarkDotnet) + try + { + CommandLineOptions.ParseAndRemoveIntParameter(argsList, "--partition-count", out partitionCount); + CommandLineOptions.ParseAndRemoveIntParameter(argsList, "--partition-index", out partitionIndex); + CommandLineOptions.ParseAndRemoveStringsParameter(argsList, "--exclusion-filter", out exclusionFilterValue); + CommandLineOptions.ParseAndRemoveStringsParameter(argsList, "--category-exclusion-filter", out categoryExclusionFilterValue); + CommandLineOptions.ParseAndRemoveBooleanParameter(argsList, "--disasm-diff", out getDiffableDisasm); + + CommandLineOptions.ValidatePartitionParameters(partitionCount, partitionIndex); + } + catch (ArgumentException e) + { + Console.WriteLine("ArgumentException: {0}", e.Message); + return 1; + } + + return BenchmarkSwitcher + .FromAssembly(typeof(Program).Assembly) + .Run( + argsList.ToArray(), + RecommendedConfig.Create( + artifactsPath: new DirectoryInfo(Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location), "BenchmarkDotNet.Artifacts")), + mandatoryCategories: ImmutableHashSet.Create(Categories.Components, Categories.Engine), + partitionCount: partitionCount, + partitionIndex: partitionIndex, + exclusionFilterValue: exclusionFilterValue, + categoryExclusionFilterValue: categoryExclusionFilterValue, + getDiffableDisasm: getDiffableDisasm)) + .ToExitCode(); + } + } +} diff --git a/test/perf/benchmarks/README.md b/test/perf/benchmarks/README.md new file mode 100644 index 00000000000..cf2b96c184c --- /dev/null +++ b/test/perf/benchmarks/README.md @@ -0,0 +1,92 @@ +## Micro Benchmarks + +This folder contains micro benchmarks that test the performance of PowerShell Engine. + +### Requirement + +1. A good suite of benchmarks + Something that measures only the thing that we are interested in and _produces accurate, stable and repeatable results_. +2. A set of machine with the same configurations. +3. Automation for regression detection. + +### Design Decision + +1. This project is internal visible to `System.Management.Automation`. + We want to be able to target some internal APIs to get measurements on specific scoped scenarios, + such as measuring the time to compile AST to a delegate by the compiler. +2. This project makes `ProjectReference` to other PowerShell assemblies. + This makes it easy to run benchmarks with the changes made in the codebase. + To run benchmarks with a specific version of PowerShell, + just replace the `ProjectReference` with a `PackageReference` to the `Microsoft.PowerShell.SDK` NuGet package of the corresponding version. + +### Quick Start + +You can run the benchmarks directly using `dotnet run` in this directory: +1. To run the benchmarks in Interactive Mode, where you will be asked which benchmark(s) to run: + ``` + dotnet run -c Release -f net6.0 + ``` + +2. To list all available benchmarks ([read more](https://github.com/dotnet/performance/blob/main/docs/benchmarkdotnet.md#Listing-the-Benchmarks)): + ``` + dotnet run -c Release -f net6.0 --list [flat/tree] + ``` + +3. To filter the benchmarks using a glob pattern applied to `namespace.typeName.methodName` ([read more](https://github.com/dotnet/performance/blob/main/docs/benchmarkdotnet.md#Filtering-the-Benchmarks)]): + ``` + dotnet run -c Release -f net6.0 --filter *script* --list flat + ``` + +4. To profile the benchmarked code and produce an ETW Trace file ([read more](https://github.com/dotnet/performance/blob/main/docs/benchmarkdotnet.md#Profiling)) + ``` + dotnet run -c Release -f net6.0 --filter *script* --profiler ETW + ``` + +You can also use the function `Start-Benchmarking` from the module [`perf.psm1`](../perf.psm1) to run the benchmarks: +```powershell +Start-Benchmarking [-TargetFramework ] [-List ] [-Filter ] [-Artifacts ] [-KeepFiles] [] + +Start-Benchmarking [-TargetPSVersion ] [-Filter ] [-Artifacts ] [-KeepFiles] [] + +Start-Benchmarking -Runtime [-Filter ] [-Artifacts ] [-KeepFiles] [] +``` +Run `Get-Help Start-Benchmarking -Full` to see the description of each parameter. + +### Regression Detection + +We use the tool [`ResultsComparer`](../dotnet-tools/ResultsComparer) to compare the provided benchmark results. +See the [README.md](../dotnet-tools/ResultsComparer/README.md) for `ResultsComparer` for more details. + +The module `perf.psm1` also provides `Compare-BenchmarkResult` that wraps `ResultsComparer`. +Here is an example of using it: + +``` +## Run benchmarks targeting the current code base +PS:1> Start-Benchmarking -Filter *script* -Artifacts C:\arena\tmp\BenchmarkDotNet.Artifacts\current\ + +## Run benchmarks targeting the 7.1.3 version of PS package +PS:2> Start-Benchmarking -Filter *script* -Artifacts C:\arena\tmp\BenchmarkDotNet.Artifacts\7.1.3 -TargetPSVersion 7.1.3 + +## Compare the results using 5% threshold +PS:3> Compare-BenchmarkResult -BaseResultPath C:\arena\tmp\BenchmarkDotNet.Artifacts\7.1.3\ -DiffResultPath C:\arena\tmp\BenchmarkDotNet.Artifacts\current\ -Threshold 1% +summary: +better: 4, geomean: 1.057 +total diff: 4 + +No Slower results for the provided threshold = 1% and noise filter = 0.3ns. + +| Faster | base/diff | Base Median (ns) | Diff Median (ns) | Modality| +| -------------------------------------------------------------------------------- | ---------:| ----------------:| ----------------:| --------:| +| Engine.Scripting.InvokeMethod(Script: "$fs=New-Object -ComObject scripting.files | 1.07 | 50635.77 | 47116.42 | | +| Engine.Scripting.InvokeMethod(Script: "$sh=New-Object -ComObject Shell.Applicati | 1.07 | 1063085.23 | 991602.08 | | +| Engine.Scripting.InvokeMethod(Script: "'String'.GetType()") | 1.06 | 1329.93 | 1252.51 | | +| Engine.Scripting.InvokeMethod(Script: "[System.IO.Path]::HasExtension('')") | 1.02 | 1322.04 | 1297.72 | | + +No file given +``` + +## References + +- [Getting started with BenchmarkDotNet](https://benchmarkdotnet.org/articles/guides/getting-started.html) +- [Micro-benchmark Design Guidelines](https://github.com/dotnet/performance/blob/main/docs/microbenchmark-design-guidelines.md) +- [Adam SITNIK: Powerful benchmarking in .NET](https://www.youtube.com/watch?v=pdcrSG4tOLI&t=351s) diff --git a/test/perf/benchmarks/assets/compiler.test.ps1 b/test/perf/benchmarks/assets/compiler.test.ps1 new file mode 100644 index 00000000000..5105ae9b408 --- /dev/null +++ b/test/perf/benchmarks/assets/compiler.test.ps1 @@ -0,0 +1,2685 @@ +## Copyright (c) Microsoft Corporation. +## Licensed under the MIT License. + +function Get-EnvInformation +{ + $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]) { + $environment += @{'IsCoreCLR' = [System.Management.Automation.Platform]::IsCoreCLR} + $environment += @{'IsLinux' = [System.Management.Automation.Platform]::IsLinux} + $environment += @{'IsMacOS' = [System.Management.Automation.Platform]::IsMacOS} + } else { + $environment += @{'IsCoreCLR' = $false} + $environment += @{'IsLinux' = $false} + $environment += @{'IsMacOS' = $false} + } + + if ($environment.IsWindows) + { + $environment += @{'IsAdmin' = (New-Object Security.Principal.WindowsPrincipal ([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)} + $environment += @{'nugetPackagesRoot' = "${env:USERPROFILE}\.nuget\packages", "${env:NUGET_PACKAGES}"} + } + else + { + $environment += @{'nugetPackagesRoot' = "${env:HOME}/.nuget/packages"} + } + + if ($environment.IsMacOS) { + $environment += @{'UsingHomebrew' = [bool](Get-Command brew -ErrorAction ignore)} + $environment += @{'UsingMacports' = [bool](Get-Command port -ErrorAction ignore)} + + $environment += @{ + 'OSArchitecture' = if ((uname -v) -match 'ARM64') { 'arm64' } else { 'x64' } + } + + if (-not($environment.UsingHomebrew -or $environment.UsingMacports)) { + throw "Neither Homebrew nor MacPorts is installed on this system, visit https://brew.sh/ or https://www.macports.org/ to continue" + } + } + + if ($environment.IsLinux) { + $LinuxInfo = Get-Content /etc/os-release -Raw | ConvertFrom-StringData + $lsb_release = Get-Command lsb_release -Type Application -ErrorAction Ignore | Select-Object -First 1 + if ($lsb_release) { + $LinuxID = & $lsb_release -is + } + else { + $LinuxID = "" + } + + $environment += @{'LinuxInfo' = $LinuxInfo} + $environment += @{'IsDebian' = $LinuxInfo.ID -match 'debian' -or $LinuxInfo.ID -match 'kali'} + $environment += @{'IsDebian9' = $environment.IsDebian -and $LinuxInfo.VERSION_ID -match '9'} + $environment += @{'IsDebian10' = $environment.IsDebian -and $LinuxInfo.VERSION_ID -match '10'} + $environment += @{'IsDebian11' = $environment.IsDebian -and $LinuxInfo.PRETTY_NAME -match 'bullseye'} + $environment += @{'IsUbuntu' = $LinuxInfo.ID -match 'ubuntu' -or $LinuxID -match 'Ubuntu'} + $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 += @{'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'} + $environment += @{'IsSLES' = $LinuxInfo.ID -match 'sles'} + $environment += @{'IsRedHat' = $LinuxInfo.ID -match 'rhel'} + $environment += @{'IsRedHat7' = $environment.IsRedHat -and $LinuxInfo.VERSION_ID -match '7' } + $environment += @{'IsOpenSUSE13' = $environment.IsOpenSUSE -and $LinuxInfo.VERSION_ID -match '13'} + $environment += @{'IsOpenSUSE42.1' = $environment.IsOpenSUSE -and $LinuxInfo.VERSION_ID -match '42.1'} + $environment += @{'IsDebianFamily' = $environment.IsDebian -or $environment.IsUbuntu} + $environment += @{'IsRedHatFamily' = $environment.IsCentOS -or $environment.IsFedora -or $environment.IsRedHat} + $environment += @{'IsSUSEFamily' = $environment.IsSLES -or $environment.IsOpenSUSE} + $environment += @{'IsAlpine' = $LinuxInfo.ID -match 'alpine'} + + # Workaround for temporary LD_LIBRARY_PATH hack for Fedora 24 + # https://github.com/PowerShell/PowerShell/issues/2511 + if ($environment.IsFedora -and (Test-Path ENV:\LD_LIBRARY_PATH)) { + Remove-Item -Force ENV:\LD_LIBRARY_PATH + Get-ChildItem ENV: + } + + if( -not( + $environment.IsDebian -or + $environment.IsUbuntu -or + $environment.IsRedHatFamily -or + $environment.IsSUSEFamily -or + $environment.IsAlpine) + ) { + if ($SkipLinuxDistroCheck) { + Write-Warning "The current OS : $($LinuxInfo.ID) is not supported for building PowerShell." + } else { + throw "The current OS : $($LinuxInfo.ID) is not supported for building PowerShell. Import this module with '-ArgumentList `$true' to bypass this check." + } + } + } + + return [PSCustomObject] $environment +} + +function Start-PSBuild { + [CmdletBinding(DefaultParameterSetName="Default")] + param( + # When specified this switch will stops running dev powershell + # to help avoid compilation error, because file are in use. + [switch]$StopDevPowerShell, + + [switch]$Restore, + # Accept a path to the output directory + # When specified, --output will be passed to dotnet + [string]$Output, + [switch]$ResGen, + [switch]$TypeGen, + [switch]$Clean, + [Parameter(ParameterSetName="Legacy")] + [switch]$PSModuleRestore, + [Parameter(ParameterSetName="Default")] + [switch]$NoPSModuleRestore, + [switch]$CI, + [switch]$ForMinimalSize, + + # Skips the step where the pwsh that's been built is used to create a configuration + # Useful when changing parsing/compilation, since bugs there can mean we can't get past this step + [switch]$SkipExperimentalFeatureGeneration, + + # this switch will re-build only System.Management.Automation.dll + # it's useful for development, to do a quick changes in the engine + [switch]$SMAOnly, + + # These runtimes must match those in project.json + # We do not use ValidateScript since we want tab completion + # If this parameter is not provided it will get determined automatically. + [ValidateSet("alpine-x64", + "fxdependent", + "fxdependent-win-desktop", + "linux-arm", + "linux-arm64", + "linux-x64", + "osx-arm64", + "osx-x64", + "win-arm", + "win-arm64", + "win7-x64", + "win7-x86")] + [string]$Runtime, + + [ValidateSet('Debug', 'Release', 'CodeCoverage', '')] # We might need "Checked" as well + [string]$Configuration, + + [switch]$CrossGen, + + [ValidatePattern("^v\d+\.\d+\.\d+(-\w+(\.\d{1,2})?)?$")] + [ValidateNotNullOrEmpty()] + [string]$ReleaseTag, + [switch]$Detailed, + [switch]$InteractiveAuth, + [switch]$SkipRoslynAnalyzers + ) + + if ($ReleaseTag -and $ReleaseTag -notmatch "^v\d+\.\d+\.\d+(-(preview|rc)(\.\d{1,2})?)?$") { + Write-Warning "Only preview or rc are supported for releasing pre-release version of PowerShell" + } + + if ($PSCmdlet.ParameterSetName -eq "Default" -and !$NoPSModuleRestore) + { + $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 ("win-arm","win-arm64" -contains $Runtime -and -not $environment.IsWindows) { + throw "Cross compiling for win-arm or win-arm64 is only supported on Windows environment" + } + + if ($ForMinimalSize) { + if ($CrossGen) { + throw "Build for the minimal size requires the minimal disk footprint, so `CrossGen` is not allowed" + } + + if ($Runtime -and "linux-x64", "win7-x64", "osx-x64" -notcontains $Runtime) { + throw "Build for the minimal size is enabled only for following runtimes: 'linux-x64', 'win7-x64', 'osx-x64'" + } + } + + function Stop-DevPowerShell { + Get-Process pwsh* | + Where-Object { + $_.Modules | + Where-Object { + $_.FileName -eq (Resolve-Path $script:Options.Output).Path + } + } | + Stop-Process -Verbose + } + + if ($Clean) { + Write-Log -message "Cleaning your working directory. You can also do it with 'git clean -fdX --exclude .vs/PowerShell/v16/Server/sqlite3'" + Push-Location $PSScriptRoot + try { + # Excluded sqlite3 folder is due to this Roslyn issue: https://github.com/dotnet/roslyn/issues/23060 + # Excluded src/Modules/nuget.config as this is required for release build. + # 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 { + Pop-Location + } + } + + # Add .NET CLI tools to PATH + Find-Dotnet + + # Verify we have git in place to do the build, and abort if the precheck failed + $precheck = precheck 'git' "Build dependency 'git' not found in PATH. See " + if (-not $precheck) { + return + } + + # Verify we have .NET SDK in place to do the build, and abort if the precheck failed + $precheck = precheck 'dotnet' "Build dependency 'dotnet' not found in PATH. Run Start-PSBootstrap. Also see " + if (-not $precheck) { + return + } + + # Verify if the dotnet in-use is the required version + $dotnetCLIInstalledVersion = Start-NativeExecution -sb { dotnet --version } -IgnoreExitcode + If ($dotnetCLIInstalledVersion -ne $dotnetCLIRequiredVersion) { + Write-Warning @" +The currently installed .NET Command Line Tools is not the required version. + +Installed version: $dotnetCLIInstalledVersion +Required version: $dotnetCLIRequiredVersion + +Fix steps: + +1. Remove the installed version from: + - on windows '`$env:LOCALAPPDATA\Microsoft\dotnet' + - on macOS and linux '`$env:HOME/.dotnet' +2. Run Start-PSBootstrap or Install-Dotnet +3. Start-PSBuild -Clean +`n +"@ + return + } + + # set output options + $OptionsArguments = @{ + CrossGen=$CrossGen + Output=$Output + Runtime=$Runtime + Configuration=$Configuration + Verbose=$true + SMAOnly=[bool]$SMAOnly + PSModuleRestore=$PSModuleRestore + ForMinimalSize=$ForMinimalSize + } + $script:Options = New-PSOptions @OptionsArguments + + if ($StopDevPowerShell) { + Stop-DevPowerShell + } + + # setup arguments + # adding ErrorOnDuplicatePublishOutputFiles=false due to .NET SDk issue: https://github.com/dotnet/sdk/issues/15748 + # removing --no-restore due to .NET SDK issue: https://github.com/dotnet/sdk/issues/18999 + # $Arguments = @("publish","--no-restore","/property:GenerateFullPaths=true", "/property:ErrorOnDuplicatePublishOutputFiles=false") + $Arguments = @("publish","/property:GenerateFullPaths=true", "/property:ErrorOnDuplicatePublishOutputFiles=false") + if ($Output -or $SMAOnly) { + $Arguments += "--output", (Split-Path $Options.Output) + } + + # Add --self-contained due to "warning NETSDK1179: One of '--self-contained' or '--no-self-contained' options are required when '--runtime' is used." + if ($Options.Runtime -like 'fxdependent*') { + $Arguments += "--no-self-contained" + } + else { + $Arguments += "--self-contained" + } + + if ($Options.Runtime -like 'win*' -or ($Options.Runtime -like 'fxdependent*' -and $environment.IsWindows)) { + $Arguments += "/property:IsWindows=true" + } + else { + $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) { + $Arguments += "/property:PublishReadyToRun=false" + } + + $Arguments += "--configuration", $Options.Configuration + $Arguments += "--framework", $Options.Framework + + if ($Detailed.IsPresent) + { + $Arguments += '--verbosity', 'd' + } + + if (-not $SMAOnly -and $Options.Runtime -notlike 'fxdependent*') { + # libraries should not have runtime + $Arguments += "--runtime", $Options.Runtime + } + + if ($ReleaseTag) { + $ReleaseTagToUse = $ReleaseTag -Replace '^v' + $Arguments += "/property:ReleaseTag=$ReleaseTagToUse" + } + + if ($SkipRoslynAnalyzers) { + $Arguments += "/property:RunAnalyzersDuringBuild=false" + } + + # handle Restore + Restore-PSPackage -Options $Options -Force:$Restore -InteractiveAuth:$InteractiveAuth + + # handle ResGen + # Heuristic to run ResGen on the fresh machine + if ($ResGen -or -not (Test-Path "$PSScriptRoot/src/Microsoft.PowerShell.ConsoleHost/gen")) { + Write-Log -message "Run ResGen (generating C# bindings for resx files)" + Start-ResGen + } + + # Handle TypeGen + # .inc file name must be different for Windows and Linux to allow build on Windows and WSL. + $incFileName = "powershell_$($Options.Runtime).inc" + if ($TypeGen -or -not (Test-Path "$PSScriptRoot/src/TypeCatalogGen/$incFileName")) { + Write-Log -message "Run TypeGen (generating CorePsTypeCatalog.cs)" + Start-TypeGen -IncFileName $incFileName + } + + # Get the folder path where pwsh.exe is located. + if ((Split-Path $Options.Output -Leaf) -like "pwsh*") { + $publishPath = Split-Path $Options.Output -Parent + } + else { + $publishPath = $Options.Output + } + + try { + # Relative paths do not work well if cwd is not changed to project + Push-Location $Options.Top + + if ($Options.Runtime -notlike 'fxdependent*') { + $sdkToUse = 'Microsoft.NET.Sdk' + if ($Options.Runtime -like 'win7-*' -and !$ForMinimalSize) { + ## WPF/WinForm and the PowerShell GraphicalHost assemblies are included + ## when 'Microsoft.NET.Sdk.WindowsDesktop' is used. + $sdkToUse = 'Microsoft.NET.Sdk.WindowsDesktop' + } + + $Arguments += "/property:SDKToUse=$sdkToUse" + + Write-Log -message "Run dotnet $Arguments from $PWD" + Start-NativeExecution { dotnet $Arguments } + Write-Log -message "PowerShell output: $($Options.Output)" + + if ($CrossGen) { + # fxdependent package cannot be CrossGen'ed + Start-CrossGen -PublishPath $publishPath -Runtime $script:Options.Runtime + Write-Log -message "pwsh.exe with ngen binaries is available at: $($Options.Output)" + } + } else { + $globalToolSrcFolder = Resolve-Path (Join-Path $Options.Top "../Microsoft.PowerShell.GlobalTool.Shim") | Select-Object -ExpandProperty Path + + if ($Options.Runtime -eq 'fxdependent') { + $Arguments += "/property:SDKToUse=Microsoft.NET.Sdk" + } elseif ($Options.Runtime -eq 'fxdependent-win-desktop') { + $Arguments += "/property:SDKToUse=Microsoft.NET.Sdk.WindowsDesktop" + } + + Write-Log -message "Run dotnet $Arguments from $PWD" + Start-NativeExecution { dotnet $Arguments } + Write-Log -message "PowerShell output: $($Options.Output)" + + try { + Push-Location $globalToolSrcFolder + $Arguments += "--output", $publishPath + Write-Log -message "Run dotnet $Arguments from $PWD to build global tool entry point" + Start-NativeExecution { dotnet $Arguments } + } + finally { + Pop-Location + } + } + } finally { + Pop-Location + } + + # 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) { + return + } + + # publish reference assemblies + try { + Push-Location "$PSScriptRoot/src/TypeCatalogGen" + $refAssemblies = Get-Content -Path $incFileName | Where-Object { $_ -like "*microsoft.netcore.app*" } | ForEach-Object { $_.TrimEnd(';') } + $refDestFolder = Join-Path -Path $publishPath -ChildPath "ref" + + if (Test-Path $refDestFolder -PathType Container) { + Remove-Item $refDestFolder -Force -Recurse -ErrorAction Stop + } + New-Item -Path $refDestFolder -ItemType Directory -Force -ErrorAction Stop > $null + Copy-Item -Path $refAssemblies -Destination $refDestFolder -Force -ErrorAction Stop + } finally { + Pop-Location + } + + if ($ReleaseTag) { + $psVersion = $ReleaseTag + } + else { + $psVersion = git --git-dir="$PSScriptRoot/.git" describe + } + + if ($environment.IsLinux) { + if ($environment.IsRedHatFamily -or $environment.IsDebian) { + # Symbolic links added here do NOT affect packaging as we do not build on Debian. + # add two symbolic links to system shared libraries that libmi.so is dependent on to handle + # platform specific changes. This is the only set of platforms needed for this currently + # as Ubuntu has these specific library files in the platform and macOS builds for itself + # against the correct versions. + + if ($environment.IsDebian10 -or $environment.IsDebian11){ + $sslTarget = "/usr/lib/x86_64-linux-gnu/libssl.so.1.1" + $cryptoTarget = "/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1" + } + elseif ($environment.IsDebian9){ + # NOTE: Debian 8 doesn't need these symlinks + $sslTarget = "/usr/lib/x86_64-linux-gnu/libssl.so.1.0.2" + $cryptoTarget = "/usr/lib/x86_64-linux-gnu/libcrypto.so.1.0.2" + } + else { #IsRedHatFamily + $sslTarget = "/lib64/libssl.so.10" + $cryptoTarget = "/lib64/libcrypto.so.10" + } + + if ( ! (Test-Path "$publishPath/libssl.so.1.0.0")) { + $null = New-Item -Force -ItemType SymbolicLink -Target $sslTarget -Path "$publishPath/libssl.so.1.0.0" -ErrorAction Stop + } + if ( ! (Test-Path "$publishPath/libcrypto.so.1.0.0")) { + $null = New-Item -Force -ItemType SymbolicLink -Target $cryptoTarget -Path "$publishPath/libcrypto.so.1.0.0" -ErrorAction Stop + } + } + } + + # download modules from powershell gallery. + # - PowerShellGet, PackageManagement, Microsoft.PowerShell.Archive + if ($PSModuleRestore) { + Restore-PSModuleToBuild -PublishPath $publishPath + } + + # publish powershell.config.json + $config = @{} + if ($environment.IsWindows) { + $config = @{ "Microsoft.PowerShell:ExecutionPolicy" = "RemoteSigned"; + "WindowsPowerShellCompatibilityModuleDenyList" = @("PSScheduledJob","BestPractices","UpdateServices") } + } + + # When building preview, we want the configuration to enable all experiemental features by default + # ARM is cross compiled, so we can't run pwsh to enumerate Experimental Features + if (-not $SkipExperimentalFeatureGeneration -and + (Test-IsPreview $psVersion) -and + -not (Test-IsReleaseCandidate $psVersion) -and + -not $Runtime.Contains("arm") -and + -not ($Runtime -like 'fxdependent*')) { + + $json = & $publishPath\pwsh -noprofile -command { + # Special case for DSC code in PS; + # this experimental feature requires new DSC module that is not inbox, + # so we don't want default DSC use case be broken + [System.Collections.ArrayList] $expFeatures = Get-ExperimentalFeature | Where-Object Name -NE PS7DscSupport | ForEach-Object -MemberName Name + + $expFeatures | Out-String | Write-Verbose -Verbose + + # Make sure ExperimentalFeatures from modules in PSHome are added + # https://github.com/PowerShell/PowerShell/issues/10550 + $ExperimentalFeaturesFromGalleryModulesInPSHome = @() + $ExperimentalFeaturesFromGalleryModulesInPSHome | ForEach-Object { + if (!$expFeatures.Contains($_)) { + $null = $expFeatures.Add($_) + } + } + + ConvertTo-Json $expFeatures + } + + $config += @{ ExperimentalFeatures = ([string[]] ($json | ConvertFrom-Json)) } + } + + if ($config.Count -gt 0) { + $configPublishPath = Join-Path -Path $publishPath -ChildPath "powershell.config.json" + Set-Content -Path $configPublishPath -Value ($config | ConvertTo-Json) -Force -ErrorAction Stop + } + + # Restore the Pester module + if ($CI) { + Restore-PSPester -Destination (Join-Path $publishPath "Modules") + } +} + +function New-PSOptions { + [CmdletBinding()] + param( + [ValidateSet("Debug", "Release", "CodeCoverage", '')] + [string]$Configuration, + + [ValidateSet("net6.0")] + [string]$Framework = "net6.0", + + # These are duplicated from Start-PSBuild + # We do not use ValidateScript since we want tab completion + [ValidateSet("", + "alpine-x64", + "fxdependent", + "fxdependent-win-desktop", + "linux-arm", + "linux-arm64", + "linux-x64", + "osx-arm64", + "osx-x64", + "win-arm", + "win-arm64", + "win7-x64", + "win7-x86")] + [string]$Runtime, + + [switch]$CrossGen, + + # Accept a path to the output directory + # If not null or empty, name of the executable will be appended to + # this path, otherwise, to the default path, and then the full path + # of the output executable will be assigned to the Output property + [string]$Output, + + [switch]$SMAOnly, + + [switch]$PSModuleRestore, + + [switch]$ForMinimalSize + ) + + # Add .NET CLI tools to PATH + Find-Dotnet + + if (-not $Configuration) { + $Configuration = 'Debug' + } + + Write-Verbose "Using configuration '$Configuration'" + Write-Verbose "Using framework '$Framework'" + + if (-not $Runtime) { + if ($environment.IsLinux) { + $Runtime = "linux-x64" + } elseif ($environment.IsMacOS) { + if ($PSVersionTable.OS.Contains('ARM64')) { + $Runtime = "osx-arm64" + } + else { + $Runtime = "osx-x64" + } + } else { + $RID = dotnet --info | ForEach-Object { + if ($_ -match "RID") { + $_ -split "\s+" | Select-Object -Last 1 + } + } + + # We plan to release packages targeting win7-x64 and win7-x86 RIDs, + # which supports all supported windows platforms. + # So we, will change the RID to win7- + $Runtime = $RID -replace "win\d+", "win7" + } + + if (-not $Runtime) { + Throw "Could not determine Runtime Identifier, please update dotnet" + } else { + Write-Verbose "Using runtime '$Runtime'" + } + } + + $PowerShellDir = if ($Runtime -like 'win*' -or ($Runtime -like 'fxdependent*' -and $environment.IsWindows)) { + "powershell-win-core" + } else { + "powershell-unix" + } + + $Top = [IO.Path]::Combine($PSScriptRoot, "src", $PowerShellDir) + Write-Verbose "Top project directory is $Top" + + $Executable = if ($Runtime -like 'fxdependent*') { + "pwsh.dll" + } elseif ($environment.IsLinux -or $environment.IsMacOS) { + "pwsh" + } elseif ($environment.IsWindows) { + "pwsh.exe" + } + + # Build the Output path + if (!$Output) { + if ($Runtime -like 'fxdependent*') { + $Output = [IO.Path]::Combine($Top, "bin", $Configuration, $Framework, "publish", $Executable) + } else { + $Output = [IO.Path]::Combine($Top, "bin", $Configuration, $Framework, $Runtime, "publish", $Executable) + } + } else { + $Output = [IO.Path]::Combine($Output, $Executable) + } + + if ($SMAOnly) + { + $Top = [IO.Path]::Combine($PSScriptRoot, "src", "System.Management.Automation") + } + + $RootInfo = @{RepoPath = $PSScriptRoot} + + # the valid root is the root of the filesystem and the folder PowerShell + $RootInfo['ValidPath'] = Join-Path -Path ([system.io.path]::GetPathRoot($RootInfo.RepoPath)) -ChildPath 'PowerShell' + + if($RootInfo.RepoPath -ne $RootInfo.ValidPath) + { + $RootInfo['Warning'] = "Please ensure your repo is at the root of the file system and named 'PowerShell' (example: '$($RootInfo.ValidPath)'), when building and packaging for release!" + $RootInfo['IsValid'] = $false + } + else + { + $RootInfo['IsValid'] = $true + } + + return New-PSOptionsObject ` + -RootInfo ([PSCustomObject]$RootInfo) ` + -Top $Top ` + -Runtime $Runtime ` + -Crossgen $Crossgen.IsPresent ` + -Configuration $Configuration ` + -PSModuleRestore $PSModuleRestore.IsPresent ` + -Framework $Framework ` + -Output $Output ` + -ForMinimalSize $ForMinimalSize +} + +function Start-PSPester { + [CmdletBinding(DefaultParameterSetName='default')] + param( + [Parameter(Position=0)] + [string[]]$Path = @("$PSScriptRoot/test/powershell"), + [string]$OutputFormat = "NUnitXml", + [string]$OutputFile = "pester-tests.xml", + [string[]]$ExcludeTag = 'Slow', + [string[]]$Tag = @("CI","Feature"), + [switch]$ThrowOnFailure, + [string]$BinDir = (Split-Path (Get-PSOptions -DefaultToNew).Output), + [string]$powershell = (Join-Path $BinDir 'pwsh'), + [string]$Pester = ([IO.Path]::Combine($BinDir, "Modules", "Pester")), + [Parameter(ParameterSetName='Unelevate',Mandatory=$true)] + [switch]$Unelevate, + [switch]$Quiet, + [switch]$Terse, + [Parameter(ParameterSetName='PassThru',Mandatory=$true)] + [switch]$PassThru, + [Parameter(ParameterSetName='PassThru',HelpMessage='Run commands on Linux with sudo.')] + [switch]$Sudo, + [switch]$IncludeFailingTest, + [switch]$IncludeCommonTests, + [string]$ExperimentalFeatureName, + [Parameter(HelpMessage='Title to publish the results as.')] + [string]$Title = 'PowerShell 7 Tests', + [Parameter(ParameterSetName='Wait', Mandatory=$true, + HelpMessage='Wait for the debugger to attach to PowerShell before Pester starts. Debug builds only!')] + [switch]$Wait, + [switch]$SkipTestToolBuild + ) + + if (-not (Get-Module -ListAvailable -Name $Pester -ErrorAction SilentlyContinue | Where-Object { $_.Version -ge "4.2" } )) + { + Restore-PSPester + } + + if ($IncludeFailingTest.IsPresent) + { + $Path += "$PSScriptRoot/tools/failingTests" + } + + if($IncludeCommonTests.IsPresent) + { + $path = += "$PSScriptRoot/test/common" + } + + # we need to do few checks and if user didn't provide $ExcludeTag explicitly, we should alternate the default + if ($Unelevate) + { + if (-not $environment.IsWindows) + { + throw '-Unelevate is currently not supported on non-Windows platforms' + } + + if (-not $environment.IsAdmin) + { + throw '-Unelevate cannot be applied because the current user is not Administrator' + } + + if (-not $PSBoundParameters.ContainsKey('ExcludeTag')) + { + $ExcludeTag += 'RequireAdminOnWindows' + } + } + elseif ($environment.IsWindows -and (-not $environment.IsAdmin)) + { + if (-not $PSBoundParameters.ContainsKey('ExcludeTag')) + { + $ExcludeTag += 'RequireAdminOnWindows' + } + } + elseif (-not $environment.IsWindows -and (-not $Sudo.IsPresent)) + { + if (-not $PSBoundParameters.ContainsKey('ExcludeTag')) + { + $ExcludeTag += 'RequireSudoOnUnix' + } + } + elseif (-not $environment.IsWindows -and $Sudo.IsPresent) + { + if (-not $PSBoundParameters.ContainsKey('Tag')) + { + $Tag = 'RequireSudoOnUnix' + } + } + + Write-Verbose "Running pester tests at '$path' with tag '$($Tag -join ''', ''')' and ExcludeTag '$($ExcludeTag -join ''', ''')'" -Verbose + if(!$SkipTestToolBuild.IsPresent) + { + $publishArgs = @{ } + # if we are building for Alpine, we must include the runtime as linux-x64 + # will not build runnable test tools + if ( $environment.IsLinux -and $environment.IsAlpine ) { + $publishArgs['runtime'] = 'alpine-x64' + } + Publish-PSTestTools @publishArgs | ForEach-Object {Write-Host $_} + } + + # All concatenated commands/arguments are suffixed with the delimiter (space) + + # Disable telemetry for all startups of pwsh in tests + $command = "`$env:POWERSHELL_TELEMETRY_OPTOUT = 'yes';" + if ($Terse) + { + $command += "`$ProgressPreference = 'silentlyContinue'; " + } + + # Autoload (in subprocess) temporary modules used in our tests + $newPathFragment = $TestModulePath + $TestModulePathSeparator + $command += '$env:PSModulePath = '+"'$newPathFragment'" + '+$env:PSModulePath;' + + # Windows needs the execution policy adjusted + if ($environment.IsWindows) { + $command += "Set-ExecutionPolicy -Scope Process Unrestricted; " + } + + $command += "Import-Module '$Pester'; " + + if ($Unelevate) + { + if ($environment.IsWindows) { + $outputBufferFilePath = [System.IO.Path]::GetTempFileName() + } + else { + # Azure DevOps agents do not have Temp folder setup on Ubuntu 20.04, hence using HOME directory + $outputBufferFilePath = (Join-Path $env:HOME $([System.IO.Path]::GetRandomFileName())) + } + } + + $command += "Invoke-Pester " + + $command += "-OutputFormat ${OutputFormat} -OutputFile ${OutputFile} " + if ($ExcludeTag -and ($ExcludeTag -ne "")) { + $command += "-ExcludeTag @('" + (${ExcludeTag} -join "','") + "') " + } + if ($Tag) { + $command += "-Tag @('" + (${Tag} -join "','") + "') " + } + # sometimes we need to eliminate Pester output, especially when we're + # doing a daily build as the log file is too large + if ( $Quiet ) { + $command += "-Quiet " + } + if ( $PassThru ) { + $command += "-PassThru " + } + + $command += "'" + ($Path -join "','") + "'" + if ($Unelevate) + { + $command += " *> $outputBufferFilePath; '__UNELEVATED_TESTS_THE_END__' >> $outputBufferFilePath" + } + + Write-Verbose $command + + $script:nonewline = $true + $script:inerror = $false + function Write-Terse([string] $line) + { + $trimmedline = $line.Trim() + if ($trimmedline.StartsWith("[+]")) { + Write-Host "+" -NoNewline -ForegroundColor Green + $script:nonewline = $true + $script:inerror = $false + } + elseif ($trimmedline.StartsWith("[?]")) { + Write-Host "?" -NoNewline -ForegroundColor Cyan + $script:nonewline = $true + $script:inerror = $false + } + elseif ($trimmedline.StartsWith("[!]")) { + Write-Host "!" -NoNewline -ForegroundColor Gray + $script:nonewline = $true + $script:inerror = $false + } + elseif ($trimmedline.StartsWith("Executing script ")) { + # Skip lines where Pester reports that is executing a test script + return + } + elseif ($trimmedline -match "^\d+(\.\d+)?m?s$") { + # Skip the time elapse like '12ms', '1ms', '1.2s' and '12.53s' + return + } + else { + if ($script:nonewline) { + Write-Host "`n" -NoNewline + } + if ($trimmedline.StartsWith("[-]") -or $script:inerror) { + Write-Host $line -ForegroundColor Red + $script:inerror = $true + } + elseif ($trimmedline.StartsWith("VERBOSE:")) { + Write-Host $line -ForegroundColor Yellow + $script:inerror = $false + } + elseif ($trimmedline.StartsWith("Describing") -or $trimmedline.StartsWith("Context")) { + Write-Host $line -ForegroundColor Magenta + $script:inerror = $false + } + else { + Write-Host $line -ForegroundColor Gray + } + $script:nonewline = $false + } + } + + $PSFlags = @("-noprofile") + if (-not [string]::IsNullOrEmpty($ExperimentalFeatureName)) { + + if ($environment.IsWindows) { + $configFile = [System.IO.Path]::GetTempFileName() + } + else { + $configFile = (Join-Path $env:HOME $([System.IO.Path]::GetRandomFileName())) + } + + $configFile = [System.IO.Path]::ChangeExtension($configFile, ".json") + + ## Create the config.json file to enable the given experimental feature. + ## On Windows, we need to have 'RemoteSigned' declared for ExecutionPolicy because the ExecutionPolicy is 'Restricted' by default. + ## On Unix, ExecutionPolicy is not supported, so we don't need to declare it. + if ($environment.IsWindows) { + $content = @" +{ + "Microsoft.PowerShell:ExecutionPolicy":"RemoteSigned", + "ExperimentalFeatures": [ + "$ExperimentalFeatureName" + ] +} +"@ + } else { + $content = @" +{ + "ExperimentalFeatures": [ + "$ExperimentalFeatureName" + ] +} +"@ + } + + Set-Content -Path $configFile -Value $content -Encoding Ascii -Force + $PSFlags = @("-settings", $configFile, "-noprofile") + } + + # -Wait is only available on Debug builds + # It is used to allow the debugger to attach before PowerShell + # runs pester in this case + if($Wait.IsPresent){ + $PSFlags += '-wait' + } + + # To ensure proper testing, the module path must not be inherited by the spawned process + try { + $originalModulePath = $env:PSModulePath + $originalTelemetry = $env:POWERSHELL_TELEMETRY_OPTOUT + $env:POWERSHELL_TELEMETRY_OPTOUT = 'yes' + if ($Unelevate) + { + Start-UnelevatedProcess -process $powershell -arguments ($PSFlags + "-c $Command") + $currentLines = 0 + while ($true) + { + $lines = Get-Content $outputBufferFilePath | Select-Object -Skip $currentLines + if ($Terse) + { + foreach ($line in $lines) + { + Write-Terse -line $line + } + } + else + { + $lines | Write-Host + } + if ($lines | Where-Object { $_ -eq '__UNELEVATED_TESTS_THE_END__'}) + { + break + } + + $count = ($lines | Measure-Object).Count + if ($count -eq 0) + { + Start-Sleep -Seconds 1 + } + else + { + $currentLines += $count + } + } + } + else + { + if ($PassThru.IsPresent) + { + if ($environment.IsWindows) { + $passThruFile = [System.IO.Path]::GetTempFileName() + } + else { + $passThruFile = Join-Path $env:HOME $([System.IO.Path]::GetRandomFileName()) + } + + try + { + $command += "| Export-Clixml -Path '$passThruFile' -Force" + + $passThruCommand = { & $powershell $PSFlags -c $command } + if ($Sudo.IsPresent) { + # -E says to preserve the environment + $passThruCommand = { & sudo -E $powershell $PSFlags -c $command } + } + + $writeCommand = { Write-Host $_ } + if ($Terse) + { + $writeCommand = { Write-Terse $_ } + } + + Start-NativeExecution -sb $passThruCommand | ForEach-Object $writeCommand + Import-Clixml -Path $passThruFile | Where-Object {$_.TotalCount -is [Int32]} + } + finally + { + Remove-Item $passThruFile -ErrorAction SilentlyContinue -Force + } + } + else + { + if ($Terse) + { + Start-NativeExecution -sb {& $powershell $PSFlags -c $command} | ForEach-Object { Write-Terse -line $_ } + } + else + { + Start-NativeExecution -sb {& $powershell $PSFlags -c $command} + } + } + } + } finally { + $env:PSModulePath = $originalModulePath + $env:POWERSHELL_TELEMETRY_OPTOUT = $originalTelemetry + if ($Unelevate) + { + Remove-Item $outputBufferFilePath + } + } + + Publish-TestResults -Path $OutputFile -Title $Title + + if($ThrowOnFailure) + { + Test-PSPesterResults -TestResultsFile $OutputFile + } +} + +function Install-Dotnet { + [CmdletBinding()] + param( + [string]$Channel = $dotnetCLIChannel, + [string]$Version = $dotnetCLIRequiredVersion, + [string]$Quality = $dotnetCLIQuality, + [switch]$NoSudo, + [string]$InstallDir, + [string]$AzureFeed, + [string]$FeedCredential + ) + + # 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://dotnet.microsoft.com/download/dotnet-core/scripts/v1" + $uninstallObtainUrl = "https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain" + + # Install for Linux and OS X + if ($environment.IsLinux -or $environment.IsMacOS) { + $wget = Get-Command -Name wget -CommandType Application -TotalCount 1 -ErrorAction Stop + + # Uninstall all previous dotnet packages + $uninstallScript = if ($environment.IsLinux -and $environment.IsUbuntu) { + "dotnet-uninstall-debian-packages.sh" + } elseif ($environment.IsMacOS) { + "dotnet-uninstall-pkgs.sh" + } + + if ($uninstallScript) { + Start-NativeExecution { + & $wget $uninstallObtainUrl/uninstall/$uninstallScript + Invoke-Expression "$sudo bash ./$uninstallScript" + } + } else { + Write-Warning "This script only removes prior versions of dotnet for Ubuntu and OS X" + } + + # Install new dotnet 1.1.0 preview packages + $installScript = "dotnet-install.sh" + Start-NativeExecution { + Write-Verbose -Message "downloading install script from $installObtainUrl/$installScript ..." -Verbose + & $wget $installObtainUrl/$installScript + + if ((Get-ChildItem "./$installScript").Length -eq 0) { + throw "./$installScript was 0 length" + } + + if ($Version) { + $bashArgs = @("./$installScript", '-v', $Version, '-q', $Quality) + } + elseif ($Channel) { + $bashArgs = @("./$installScript", '-c', $Channel, '-q', $Quality) + } + + if ($InstallDir) { + $bashArgs += @('-i', $InstallDir) + } + + if ($AzureFeed) { + $bashArgs += @('-AzureFeed', $AzureFeed, '-FeedCredential', $FeedCredential) + } + + bash @bashArgs + } + } elseif ($environment.IsWindows) { + Remove-Item -ErrorAction SilentlyContinue -Recurse -Force ~\AppData\Local\Microsoft\dotnet + $installScript = "dotnet-install.ps1" + Invoke-WebRequest -Uri $installObtainUrl/$installScript -OutFile $installScript + if (-not $environment.IsCoreCLR) { + $installArgs = @{ + Quality = $Quality + } + + if ($Version) { + $installArgs += @{ Version = $Version } + } elseif ($Channel) { + $installArgs += @{ Channel = $Channel } + } + + if ($InstallDir) { + $installArgs += @{ InstallDir = $InstallDir } + } + + if ($AzureFeed) { + $installArgs += @{ + AzureFeed = $AzureFeed + $FeedCredential = $FeedCredential + } + } + + & ./$installScript @installArgs + } + else { + # dotnet-install.ps1 uses APIs that are not supported in .NET Core, so we run it with Windows PowerShell + $fullPSPath = Join-Path -Path $env:windir -ChildPath "System32\WindowsPowerShell\v1.0\powershell.exe" + $fullDotnetInstallPath = Join-Path -Path $PWD.Path -ChildPath $installScript + Start-NativeExecution { + + if ($Version) { + $psArgs = @('-NoLogo', '-NoProfile', '-File', $fullDotnetInstallPath, '-Version', $Version, '-Quality', $Quality) + } + elseif ($Channel) { + $psArgs = @('-NoLogo', '-NoProfile', '-File', $fullDotnetInstallPath, '-Channel', $Channel, '-Quality', $Quality) + } + + if ($InstallDir) { + $psArgs += @('-InstallDir', $InstallDir) + } + + if ($AzureFeed) { + $psArgs += @('-AzureFeed', $AzureFeed, '-FeedCredential', $FeedCredential) + } + + & $fullPSPath @psArgs + } + } + } +} + +function Start-PSBootstrap { + [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 + ) + + Write-Log -message "Installing PowerShell build dependencies" + + Push-Location $PSScriptRoot/tools + + try { + if ($environment.IsLinux -or $environment.IsMacOS) { + # 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" + return + } + + # Install ours and .NET's dependencies + $Deps = @() + if ($environment.IsLinux -and $environment.IsUbuntu) { + # Build tools + $Deps += "curl", "g++", "make" + + if ($BuildLinuxArm) { + $Deps += "gcc-arm-linux-gnueabihf", "g++-arm-linux-gnueabihf" + } + + # .NET Core required runtime libraries + $Deps += "libunwind8" + if ($environment.IsUbuntu16) { $Deps += "libicu55" } + elseif ($environment.IsUbuntu18) { $Deps += "libicu60"} + + # Packaging tools + if ($Package) { $Deps += "ruby-dev", "groff", "libffi-dev" } + + # Install dependencies + # change the fontend from apt-get to noninteractive + $originalDebianFrontEnd=$env:DEBIAN_FRONTEND + $env:DEBIAN_FRONTEND='noninteractive' + try { + Start-NativeExecution { + Invoke-Expression "$sudo apt-get update -qq" + Invoke-Expression "$sudo apt-get install -y -qq $Deps" + } + } + finally { + # change the apt frontend back to the original + $env:DEBIAN_FRONTEND=$originalDebianFrontEnd + } + } elseif ($environment.IsLinux -and $environment.IsRedHatFamily) { + # Build tools + $Deps += "which", "curl", "gcc-c++", "make" + + # .NET Core required runtime libraries + $Deps += "libicu", "libunwind" + + # Packaging tools + if ($Package) { $Deps += "ruby-devel", "rpm-build", "groff", 'libffi-devel' } + + $PackageManager = Get-RedHatPackageManager + + $baseCommand = "$sudo $PackageManager" + + # On OpenSUSE 13.2 container, sudo does not exist, so don't use it if not needed + if($NoSudo) + { + $baseCommand = $PackageManager + } + + # Install dependencies + Start-NativeExecution { + Invoke-Expression "$baseCommand $Deps" + } + } elseif ($environment.IsLinux -and $environment.IsSUSEFamily) { + # Build tools + $Deps += "gcc", "make" + + # Packaging tools + if ($Package) { $Deps += "ruby-devel", "rpmbuild", "groff", 'libffi-devel' } + + $PackageManager = "zypper --non-interactive install" + $baseCommand = "$sudo $PackageManager" + + # On OpenSUSE 13.2 container, sudo does not exist, so don't use it if not needed + if($NoSudo) + { + $baseCommand = $PackageManager + } + + # Install dependencies + Start-NativeExecution { + Invoke-Expression "$baseCommand $Deps" + } + } elseif ($environment.IsMacOS) { + if ($environment.UsingHomebrew) { + $PackageManager = "brew" + } elseif ($environment.UsingMacports) { + $PackageManager = "$sudo port" + } + + # .NET Core required runtime libraries + $Deps += "openssl" + + # Install dependencies + # ignore exitcode, because they may be already installed + Start-NativeExecution ([ScriptBlock]::Create("$PackageManager install $Deps")) -IgnoreExitcode + } elseif ($environment.IsLinux -and $environment.IsAlpine) { + $Deps += 'libunwind', 'libcurl', 'bash', 'clang', 'build-base', 'git', 'curl' + + Start-NativeExecution { + Invoke-Expression "apk add $Deps" + } + } + + # Install [fpm](https://github.com/jordansissel/fpm) and [ronn](https://github.com/rtomayko/ronn) + if ($Package) { + 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 ffi -v 1.12.0 --no-document")) + Start-NativeExecution ([ScriptBlock]::Create("$gemsudo gem install fpm -v 1.11.0 --no-document")) + Start-NativeExecution ([ScriptBlock]::Create("$gemsudo gem install ronn -v 0.7.3 --no-document")) + } catch { + Write-Warning "Installation of fpm and ronn gems failed! Must resolve manually." + } + } + } + + # Try to locate dotnet-SDK before installing it + Find-Dotnet + + # Install dotnet-SDK + $dotNetExists = precheck 'dotnet' $null + $dotNetVersion = [string]::Empty + if($dotNetExists) { + $dotNetVersion = Start-NativeExecution -sb { dotnet --version } -IgnoreExitcode + } + + 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." + } + + $DotnetArguments = @{ Channel=$Channel; Version=$Version; NoSudo=$NoSudo } + Install-Dotnet @DotnetArguments + } + else { + Write-Log -message "dotnet is already installed. Skipping installation." + } + + # Install Windows dependencies if `-Package` or `-BuildWindowsNative` is specified + if ($environment.IsWindows) { + ## The VSCode build task requires 'pwsh.exe' to be found in Path + if (-not (Get-Command -Name pwsh.exe -CommandType Application -ErrorAction Ignore)) + { + Write-Log -message "pwsh.exe not found. Install latest PowerShell release and add it to Path" + $psInstallFile = [System.IO.Path]::Combine($PSScriptRoot, "tools", "install-powershell.ps1") + & $psInstallFile -AddToPath + } + } + } finally { + Pop-Location + } +} + +function Start-CrossGen { + [CmdletBinding()] + param( + [Parameter(Mandatory= $true)] + [ValidateNotNullOrEmpty()] + [String] + $PublishPath, + + [Parameter(Mandatory=$true)] + [ValidateSet("alpine-x64", + "linux-arm", + "linux-arm64", + "linux-x64", + "osx-arm64", + "osx-x64", + "win-arm", + "win-arm64", + "win7-x64", + "win7-x86")] + [string] + $Runtime + ) + + function New-CrossGenAssembly { + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String[]] + $AssemblyPath, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $CrossgenPath, + + [Parameter(Mandatory = $true)] + [ValidateSet("alpine-x64", + "linux-arm", + "linux-arm64", + "linux-x64", + "osx-arm64", + "osx-x64", + "win-arm", + "win-arm64", + "win7-x64", + "win7-x86")] + [string] + $Runtime + ) + + $platformAssembliesPath = Split-Path $AssemblyPath[0] -Parent + + $targetOS, $targetArch = $Runtime -split '-' + + # Special cases where OS / Arch does not conform with runtime names + switch ($Runtime) { + 'alpine-x64' { + $targetOS = 'linux' + $targetArch = 'x64' + } + 'win-arm' { + $targetOS = 'windows' + $targetArch = 'arm' + } + 'win-arm64' { + $targetOS = 'windows' + $targetArch = 'arm64' + } + 'win7-x64' { + $targetOS = 'windows' + $targetArch = 'x64' + } + 'win7-x86' { + $targetOS = 'windows' + $targetArch = 'x86' + } + } + + $generatePdb = $targetos -eq 'windows' + + # The path to folder must end with directory separator + $dirSep = [System.IO.Path]::DirectorySeparatorChar + $platformAssembliesPath = if (-not $platformAssembliesPath.EndsWith($dirSep)) { $platformAssembliesPath + $dirSep } + + Start-NativeExecution { + $crossgen2Params = @( + "-r" + $platformAssembliesPath + "--out-near-input" + "--single-file-compilation" + "-O" + "--targetos" + $targetOS + "--targetarch" + $targetArch + ) + + if ($generatePdb) { + $crossgen2Params += "--pdb" + } + + $crossgen2Params += $AssemblyPath + + & $CrossgenPath $crossgen2Params + } + } + + if (-not (Test-Path $PublishPath)) { + throw "Path '$PublishPath' does not exist." + } + + # Get the path to crossgen + $crossGenExe = if ($environment.IsWindows) { "crossgen2.exe" } else { "crossgen2" } + + # The crossgen tool is only published for these particular runtimes + $crossGenRuntime = if ($environment.IsWindows) { + # for windows the tool architecture is the host machine architecture, so it is always x64. + # we can cross compile for x86, arm and arm64 + "win-x64" + } else { + $Runtime + } + + if (-not $crossGenRuntime) { + throw "crossgen is not available for this platform" + } + + $dotnetRuntimeVersion = $script:Options.Framework -replace 'net' + + # Get the CrossGen.exe for the correct runtime with the latest version + $crossGenPath = Get-ChildItem $script:Environment.nugetPackagesRoot $crossGenExe -Recurse | ` + Where-Object { $_.FullName -match $crossGenRuntime } | ` + Where-Object { $_.FullName -match $dotnetRuntimeVersion } | ` + Where-Object { (Split-Path $_.FullName -Parent).EndsWith('tools') } | ` + Sort-Object -Property FullName -Descending | ` + Select-Object -First 1 | ` + ForEach-Object { $_.FullName } + if (-not $crossGenPath) { + throw "Unable to find latest version of crossgen2.exe. 'Please run Start-PSBuild -Clean' first, and then try again." + } + Write-Verbose "Matched CrossGen2.exe: $crossGenPath" -Verbose + + # Common assemblies used by Add-Type or assemblies with high JIT and no pdbs to crossgen + $commonAssembliesForAddType = @( + "Microsoft.CodeAnalysis.CSharp.dll" + "Microsoft.CodeAnalysis.dll" + "System.Linq.Expressions.dll" + "Microsoft.CSharp.dll" + "System.Runtime.Extensions.dll" + "System.Linq.dll" + "System.Collections.Concurrent.dll" + "System.Collections.dll" + "Newtonsoft.Json.dll" + "System.IO.FileSystem.dll" + "System.Diagnostics.Process.dll" + "System.Threading.Tasks.Parallel.dll" + "System.Security.AccessControl.dll" + "System.Text.Encoding.CodePages.dll" + "System.Private.Uri.dll" + "System.Threading.dll" + "System.Security.Principal.Windows.dll" + "System.Console.dll" + "Microsoft.Win32.Registry.dll" + "System.IO.Pipes.dll" + "System.Diagnostics.FileVersionInfo.dll" + "System.Collections.Specialized.dll" + "Microsoft.ApplicationInsights.dll" + ) + + $fullAssemblyList = $commonAssembliesForAddType + + $assemblyFullPaths = @() + $assemblyFullPaths += foreach ($assemblyName in $fullAssemblyList) { + Join-Path $PublishPath $assemblyName + } + + New-CrossGenAssembly -CrossgenPath $crossGenPath -AssemblyPath $assemblyFullPaths -Runtime $Runtime + + # + # With the latest dotnet.exe, the default load context is only able to load TPAs, and TPA + # only contains IL assembly names. In order to make the default load context able to load + # the NI PS assemblies, we need to replace the IL PS assemblies with the corresponding NI + # PS assemblies, but with the same IL assembly names. + # + Write-Verbose "PowerShell Ngen assemblies have been generated. Deploying ..." -Verbose + foreach ($assemblyName in $fullAssemblyList) { + + # Remove the IL assembly and its symbols. + $assemblyPath = Join-Path $PublishPath $assemblyName + $symbolsPath = [System.IO.Path]::ChangeExtension($assemblyPath, ".pdb") + + Remove-Item $assemblyPath -Force -ErrorAction Stop + + # Rename the corresponding ni.dll assembly to be the same as the IL assembly + $niAssemblyPath = [System.IO.Path]::ChangeExtension($assemblyPath, "ni.dll") + Rename-Item $niAssemblyPath $assemblyPath -Force -ErrorAction Stop + + # No symbols are available for Microsoft.CodeAnalysis.CSharp.dll, Microsoft.CodeAnalysis.dll, + # Microsoft.CodeAnalysis.VisualBasic.dll, and Microsoft.CSharp.dll. + if ($commonAssembliesForAddType -notcontains $assemblyName) { + Remove-Item $symbolsPath -Force -ErrorAction Stop + } + } +} + +function Use-PSClass { + [CmdletBinding()] + param ( + [Parameter(ValueFromPipeline = $true, Mandatory = $true, Position = 0)] + [string[]]$Logfile, + [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 { + # attributes + [datetime]$timestamp + # child elements + [System.Collections.Generic.List[testAssembly]]$assembly + assemblies() { + $this.timestamp = [datetime]::now + $this.assembly = [System.Collections.Generic.List[testAssembly]]::new() + } + static [assemblies] op_Addition([assemblies]$ls, [assemblies]$rs) { + $newAssembly = [assemblies]::new() + $newAssembly.assembly.AddRange($ls.assembly) + $newAssembly.assembly.AddRange($rs.assembly) + return $newAssembly + } + [string]ToString() { + $sb = [text.stringbuilder]::new() + $sb.AppendLine('' -f $this.timestamp) + foreach ( $a in $this.assembly ) { + $sb.Append("$a") + } + $sb.AppendLine(""); + return $sb.ToString() + } + # use Write-Output to emit these into the pipeline + [array]GetTests() { + return $this.Assembly.collection.test + } + } + + class testAssembly { + # attributes + [string]$name # path to pester file + [string]${config-file} + [string]${test-framework} # Pester + [string]$environment + [string]${run-date} + [string]${run-time} + [decimal]$time + [int]$total + [int]$passed + [int]$failed + [int]$skipped + [int]$errors + testAssembly ( ) { + $this."config-file" = "no config" + $this."test-framework" = "Pester" + $this.environment = $script:environment + $this."run-date" = $script:rundate + $this."run-time" = $script:runtime + $this.collection = [System.Collections.Generic.List[collection]]::new() + } + # child elements + [error[]]$error + [System.Collections.Generic.List[collection]]$collection + [string]ToString() { + $sb = [System.Text.StringBuilder]::new() + $sb.AppendFormat(' ") + if ( $this.error ) { + $sb.AppendLine(" ") + foreach ( $e in $this.error ) { + $sb.AppendLine($e.ToString()) + } + $sb.AppendLine(" ") + } else { + $sb.AppendLine(" ") + } + foreach ( $col in $this.collection ) { + $sb.AppendLine($col.ToString()) + } + $sb.AppendLine(" ") + return $sb.ToString() + } + } + + class collection { + # attributes + [string]$name + [decimal]$time + [int]$total + [int]$passed + [int]$failed + [int]$skipped + # child element + [System.Collections.Generic.List[test]]$test + # constructor + collection () { + $this.test = [System.Collections.Generic.List[test]]::new() + } + [string]ToString() { + $sb = [Text.StringBuilder]::new() + if ( $this.test.count -eq 0 ) { + $sb.AppendLine(" ") + } else { + $sb.AppendFormat(' ' + "`n", + $this.total, $this.passed, $this.failed, $this.skipped, [security.securityelement]::escape($this.name), $this.time) + foreach ( $t in $this.test ) { + $sb.AppendLine(" " + $t.ToString()); + } + $sb.Append(" ") + } + return $sb.ToString() + } + } + + class errors { + [error[]]$error + } + class error { + # attributes + [string]$type + [string]$name + # child elements + [failure]$failure + [string]ToString() { + $sb = [system.text.stringbuilder]::new() + $sb.AppendLine('' -f $this.type, [security.securityelement]::escape($this.Name)) + $sb.AppendLine($this.failure -as [string]) + $sb.AppendLine("") + return $sb.ToString() + } + } + + class cdata { + [string]$text + cdata ( [string]$s ) { $this.text = $s } + [string]ToString() { + return '' + } + } + + class failure { + [string]${exception-type} + [cdata]$message + [cdata]${stack-trace} + failure ( [string]$message, [string]$stack ) { + $this."exception-type" = "Pester" + $this.Message = [cdata]::new($message) + $this."stack-trace" = [cdata]::new($stack) + } + [string]ToString() { + $sb = [text.stringbuilder]::new() + $sb.AppendLine(" ") + $sb.AppendLine(" " + ($this.message -as [string]) + "") + $sb.AppendLine(" " + ($this."stack-trace" -as [string]) + "") + $sb.Append(" ") + return $sb.ToString() + } + } + + enum resultenum { + Pass + Fail + Skip + } + + class trait { + # attributes + [string]$name + [string]$value + } + class traits { + [trait[]]$trait + } + class test { + # attributes + [string]$name + [string]$type + [string]$method + [decimal]$time + [resultenum]$result + # child elements + [trait[]]$traits + [failure]$failure + [cdata]$reason # skip reason + [string]ToString() { + $sb = [text.stringbuilder]::new() + $sb.appendformat(' ") + $sb.AppendLine($this.failure -as [string]) + $sb.append(' ') + } else { + $sb.Append("/>") + } + return $sb.ToString() + } + } + + function convert-pesterlog ( [xml]$x, $logpath, [switch]$includeEmpty ) { + <#$resultMap = @{ + Success = "Pass" + Ignored = "Skip" + Failure = "Fail" + }#> + + $resultMap = @{ + Success = "Pass" + Ignored = "Skip" + Failure = "Fail" + Inconclusive = "Skip" + } + + $configfile = $logpath + $runtime = $x."test-results".time + $environment = $x."test-results".environment.platform + "-" + $x."test-results".environment."os-version" + $rundate = $x."test-results".date + $suites = $x."test-results"."test-suite".results."test-suite" + $assemblies = [assemblies]::new() + foreach ( $suite in $suites ) { + $tCases = $suite.SelectNodes(".//test-case") + # only create an assembly group if we have tests + if ( $tCases.count -eq 0 -and ! $includeEmpty ) { continue } + $tGroup = $tCases | Group-Object result + $total = $tCases.Count + $asm = [testassembly]::new() + $asm.environment = $environment + $asm."run-date" = $rundate + $asm."run-time" = $runtime + $asm.Name = $suite.name + $asm."config-file" = $configfile + $asm.time = $suite.time + $asm.total = $suite.SelectNodes(".//test-case").Count + $asm.Passed = $tGroup| Where-Object -FilterScript {$_.Name -eq "Success"} | ForEach-Object -Process {$_.Count} + $asm.Failed = $tGroup| Where-Object -FilterScript {$_.Name -eq "Failure"} | ForEach-Object -Process {$_.Count} + $asm.Skipped = $tGroup| Where-Object -FilterScript { $_.Name -eq "Ignored" } | ForEach-Object -Process {$_.Count} + $asm.Skipped += $tGroup| Where-Object -FilterScript { $_.Name -eq "Inconclusive" } | ForEach-Object -Process {$_.Count} + $c = [collection]::new() + $c.passed = $asm.Passed + $c.failed = $asm.failed + $c.skipped = $asm.skipped + $c.total = $asm.total + $c.time = $asm.time + $c.name = $asm.name + foreach ( $tc in $suite.SelectNodes(".//test-case")) { + if ( $tc.result -match "Success|Ignored|Failure" ) { + $t = [test]::new() + $t.name = $tc.Name + $t.time = $tc.time + $t.method = $tc.description # the pester actually puts the name of the "it" as description + $t.type = $suite.results."test-suite".description | Select-Object -First 1 + $t.result = $resultMap[$tc.result] + if ( $tc.failure ) { + $t.failure = [failure]::new($tc.failure.message, $tc.failure."stack-trace") + } + $null = $c.test.Add($t) + } + } + $null = $asm.collection.add($c) + $assemblies.assembly.Add($asm) + } + $assemblies + } + + # convert it to our object model + # a simple conversion + function convert-xunitlog { + param ( $x, $logpath ) + $asms = [assemblies]::new() + $asms.timestamp = $x.assemblies.timestamp + foreach ( $assembly in $x.assemblies.assembly ) { + $asm = [testAssembly]::new() + $asm.environment = $assembly.environment + $asm."test-framework" = $assembly."test-framework" + $asm."run-date" = $assembly."run-date" + $asm."run-time" = $assembly."run-time" + $asm.total = $assembly.total + $asm.passed = $assembly.passed + $asm.failed = $assembly.failed + $asm.skipped = $assembly.skipped + $asm.time = $assembly.time + $asm.name = $assembly.name + foreach ( $coll in $assembly.collection ) { + $c = [collection]::new() + $c.name = $coll.name + $c.total = $coll.total + $c.passed = $coll.passed + $c.failed = $coll.failed + $c.skipped = $coll.skipped + $c.time = $coll.time + foreach ( $t in $coll.test ) { + $test = [test]::new() + $test.name = $t.name + $test.type = $t.type + $test.method = $t.method + $test.time = $t.time + $test.result = $t.result + $c.test.Add($test) + } + $null = $asm.collection.add($c) + } + $null = $asms.assembly.add($asm) + } + $asms + } + $Logs = @() + } + + PROCESS { + #### MAIN #### + foreach ( $log in $Logfile ) { + foreach ( $logpath in (Resolve-Path $log).path ) { + Write-Progress "converting file $logpath" + if ( ! $logpath) { throw "Cannot resolve $Logfile" } + $x = [xml](Get-Content -Raw -ReadCount 0 $logpath) + + if ( $x.psobject.properties['test-results'] ) { + $Logs += convert-pesterlog $x $logpath -includeempty:$includeempty + } elseif ( $x.psobject.properties['assemblies'] ) { + $Logs += convert-xunitlog $x $logpath -includeEmpty:$includeEmpty + } else { + Write-Error "Cannot determine log type" + } + } + } + } + + END { + if ( $MultipleLog ) { + $Logs + } else { + $combinedLog = $Logs[0] + for ( $i = 1; $i -lt $logs.count; $i++ ) { + $combinedLog += $Logs[$i] + } + $combinedLog + } + } +} + +function Start-PSPackage { + [CmdletBinding(DefaultParameterSetName='Version',SupportsShouldProcess=$true)] + param( + # PowerShell packages use Semantic Versioning https://semver.org/ + [Parameter(ParameterSetName = "Version")] + [string]$Version, + + [Parameter(ParameterSetName = "ReleaseTag")] + [ValidatePattern("^v\d+\.\d+\.\d+(-\w+(\.\d{1,2})?)?$")] + [ValidateNotNullOrEmpty()] + [string]$ReleaseTag, + + # Package name + [ValidatePattern("^powershell")] + [string]$Name = "powershell", + + # Ubuntu, CentOS, Fedora, macOS, and Windows packages are supported + [ValidateSet("msix", "deb", "osxpkg", "rpm", "msi", "zip", "zip-pdb", "nupkg", "tar", "tar-arm", "tar-arm64", "tar-alpine", "fxdependent", "fxdependent-win-desktop", "min-size")] + [string[]]$Type, + + # Generate windows downlevel package + [ValidateSet("win7-x86", "win7-x64", "win-arm", "win-arm64")] + [ValidateScript({$Environment.IsWindows})] + [string] $WindowsRuntime, + + [ValidateSet('osx-x64', 'osx-arm64')] + [ValidateScript({$Environment.IsMacOS})] + [string] $MacOSRuntime, + + [Switch] $Force, + + [Switch] $SkipReleaseChecks, + + [switch] $NoSudo, + + [switch] $LTS + ) + + DynamicParam { + if ($Type -in ('zip', 'min-size') -or $Type -like 'fxdependent*') { + # Add a dynamic parameter '-IncludeSymbols' when the specified package type is 'zip' only. + # The '-IncludeSymbols' parameter can be used to indicate that the package should only contain powershell binaries and symbols. + $ParameterAttr = New-Object "System.Management.Automation.ParameterAttribute" + $Attributes = New-Object "System.Collections.ObjectModel.Collection``1[System.Attribute]" + $Attributes.Add($ParameterAttr) > $null + + $Parameter = New-Object "System.Management.Automation.RuntimeDefinedParameter" -ArgumentList ("IncludeSymbols", [switch], $Attributes) + $Dict = New-Object "System.Management.Automation.RuntimeDefinedParameterDictionary" + $Dict.Add("IncludeSymbols", $Parameter) > $null + return $Dict + } + } + + End { + $IncludeSymbols = $null + if ($PSBoundParameters.ContainsKey('IncludeSymbols')) { + Write-Log 'setting IncludeSymbols' + $IncludeSymbols = $PSBoundParameters['IncludeSymbols'] + } + + # Runtime and Configuration settings required by the package + ($Runtime, $Configuration) = if ($WindowsRuntime) { + $WindowsRuntime, "Release" + } elseif ($MacOSRuntime) { + $MacOSRuntime, "Release" + } elseif ($Type -eq "tar-alpine") { + New-PSOptions -Configuration "Release" -Runtime "alpine-x64" -WarningAction SilentlyContinue | ForEach-Object { $_.Runtime, $_.Configuration } + } elseif ($Type -eq "tar-arm") { + New-PSOptions -Configuration "Release" -Runtime "Linux-ARM" -WarningAction SilentlyContinue | ForEach-Object { $_.Runtime, $_.Configuration } + } elseif ($Type -eq "tar-arm64") { + if ($IsMacOS) { + New-PSOptions -Configuration "Release" -Runtime "osx-arm64" -WarningAction SilentlyContinue | ForEach-Object { $_.Runtime, $_.Configuration } + } else { + New-PSOptions -Configuration "Release" -Runtime "Linux-ARM64" -WarningAction SilentlyContinue | ForEach-Object { $_.Runtime, $_.Configuration } + } + } else { + New-PSOptions -Configuration "Release" -WarningAction SilentlyContinue | ForEach-Object { $_.Runtime, $_.Configuration } + } + + if ($Environment.IsWindows) { + # Runtime will be one of win7-x64, win7-x86, "win-arm" and "win-arm64" on Windows. + # Build the name suffix for universal win-plat packages. + switch ($Runtime) { + "win-arm" { $NameSuffix = "win-arm32" } + "win-arm64" { $NameSuffix = "win-arm64" } + default { $NameSuffix = $_ -replace 'win\d+', 'win' } + } + } + + if ($Type -eq 'fxdependent') { + $NameSuffix = "win-fxdependent" + Write-Log "Packaging : '$Type'; Packaging Configuration: '$Configuration'" + } elseif ($Type -eq 'fxdependent-win-desktop') { + $NameSuffix = "win-fxdependentWinDesktop" + Write-Log "Packaging : '$Type'; Packaging Configuration: '$Configuration'" + } elseif ($MacOSRuntime) { + $NameSuffix = $MacOSRuntime + } else { + Write-Log "Packaging RID: '$Runtime'; Packaging Configuration: '$Configuration'" + } + + $Script:Options = Get-PSOptions + $actualParams = @() + + $crossGenCorrect = $false + if ($Runtime -match "arm" -or $Type -eq 'min-size') { + ## crossgen doesn't support arm32/64; + ## For the min-size package, we intentionally avoid crossgen. + $crossGenCorrect = $true + } + elseif ($Script:Options.CrossGen) { + $actualParams += '-CrossGen' + $crossGenCorrect = $true + } + + $PSModuleRestoreCorrect = $false + + # Require PSModuleRestore for packaging without symbols + # But Disallow it when packaging with symbols + if (!$IncludeSymbols.IsPresent -and $Script:Options.PSModuleRestore) { + $actualParams += '-PSModuleRestore' + $PSModuleRestoreCorrect = $true + } + elseif ($IncludeSymbols.IsPresent -and !$Script:Options.PSModuleRestore) { + $PSModuleRestoreCorrect = $true + } + else { + $actualParams += '-PSModuleRestore' + } + + $precheckFailed = if ($Type -like 'fxdependent*' -or $Type -eq 'tar-alpine') { + ## We do not check for runtime and crossgen for framework dependent package. + -not $Script:Options -or ## Start-PSBuild hasn't been executed yet + -not $PSModuleRestoreCorrect -or ## Last build didn't specify '-PSModuleRestore' correctly + $Script:Options.Configuration -ne $Configuration -or ## Last build was with configuration other than 'Release' + $Script:Options.Framework -ne $script:netCoreRuntime ## Last build wasn't for CoreCLR + } else { + -not $Script:Options -or ## Start-PSBuild hasn't been executed yet + -not $crossGenCorrect -or ## Last build didn't specify '-CrossGen' correctly + -not $PSModuleRestoreCorrect -or ## Last build didn't specify '-PSModuleRestore' correctly + $Script:Options.Runtime -ne $Runtime -or ## Last build wasn't for the required RID + $Script:Options.Configuration -ne $Configuration -or ## Last build was with configuration other than 'Release' + $Script:Options.Framework -ne $script:netCoreRuntime ## Last build wasn't for CoreCLR + } + + # Make sure the most recent build satisfies the package requirement + if ($precheckFailed) { + # It's possible that the most recent build doesn't satisfy the package requirement but + # an earlier build does. + # It's also possible that the last build actually satisfies the package requirement but + # then `Start-PSPackage` runs from a new PS session or `build.psm1` was reloaded. + # + # In these cases, the user will be asked to build again even though it's technically not + # necessary. However, we want it that way -- being very explict when generating packages. + # This check serves as a simple gate to ensure that the user knows what he is doing, and + # also ensure `Start-PSPackage` does what the user asks/expects, because once packages + # are generated, it'll be hard to verify if they were built from the correct content. + + + $params = @('-Clean') + + # CrossGen cannot be done for framework dependent package as it is runtime agnostic. + if ($Type -notlike 'fxdependent*') { + $params += '-CrossGen' + } + + if (!$IncludeSymbols.IsPresent) { + $params += '-PSModuleRestore' + } + + $actualParams += '-Runtime ' + $Script:Options.Runtime + + if ($Type -eq 'fxdependent') { + $params += '-Runtime', 'fxdependent' + } elseif ($Type -eq 'fxdependent-win-desktop') { + $params += '-Runtime', 'fxdependent-win-desktop' + } else { + $params += '-Runtime', $Runtime + } + + $params += '-Configuration', $Configuration + $actualParams += '-Configuration ' + $Script:Options.Configuration + + Write-Warning "Build started with unexpected parameters 'Start-PSBuild $actualParams" + throw "Please ensure you have run 'Start-PSBuild $params'!" + } + + if ($SkipReleaseChecks.IsPresent) { + Write-Warning "Skipping release checks." + } + elseif (!$Script:Options.RootInfo.IsValid){ + throw $Script:Options.RootInfo.Warning + } + + # If ReleaseTag is specified, use the given tag to calculate Version + if ($PSCmdlet.ParameterSetName -eq "ReleaseTag") { + $Version = $ReleaseTag -Replace '^v' + } + + # Use Git tag if not given a version + if (-not $Version) { + $Version = (git --git-dir="$RepoRoot/.git" describe) -Replace '^v' + } + + $Source = Split-Path -Path $Script:Options.Output -Parent + + # Copy the ThirdPartyNotices.txt so it's part of the package + Copy-Item "$RepoRoot/ThirdPartyNotices.txt" -Destination $Source -Force + + # Copy the default.help.txt so it's part of the package + Copy-Item "$RepoRoot/assets/default.help.txt" -Destination "$Source/en-US" -Force + + # If building a symbols package, we add a zip of the parent to publish + if ($IncludeSymbols.IsPresent) + { + $publishSource = $Source + $buildSource = Split-Path -Path $Source -Parent + $Source = New-TempFolder + $symbolsSource = New-TempFolder + + try + { + # Copy files which go into the root package + Get-ChildItem -Path $publishSource | Copy-Item -Destination $Source -Recurse + + $signingXml = [xml] (Get-Content (Join-Path $PSScriptRoot "..\releaseBuild\signing.xml" -Resolve)) + # Only include the files we sign for compliance scanning, those are the files we build. + $filesToInclude = $signingXml.SignConfigXML.job.file.src | Where-Object { -not $_.endswith('pwsh.exe') -and ($_.endswith(".dll") -or $_.endswith(".exe")) } | ForEach-Object { ($_ -split '\\')[-1] } + $filesToInclude += $filesToInclude | ForEach-Object { $_ -replace '.dll', '.pdb' } + Get-ChildItem -Path $buildSource | Where-Object { $_.Name -in $filesToInclude } | Copy-Item -Destination $symbolsSource -Recurse + + # Zip symbols.zip to the root package + $zipSource = Join-Path $symbolsSource -ChildPath '*' + $zipPath = Join-Path -Path $Source -ChildPath 'symbols.zip' + Save-PSOptions -PSOptionsPath (Join-Path -Path $source -ChildPath 'psoptions.json') -Options $Script:Options + Compress-Archive -Path $zipSource -DestinationPath $zipPath + } + finally + { + Remove-Item -Path $symbolsSource -Recurse -Force -ErrorAction SilentlyContinue + } + } + + Write-Log "Packaging Source: '$Source'" + + # Decide package output type + if (-not $Type) { + $Type = if ($Environment.IsLinux) { + if ($Environment.LinuxInfo.ID -match "ubuntu") { + "deb", "nupkg", "tar" + } elseif ($Environment.IsRedHatFamily) { + "rpm", "nupkg" + } elseif ($Environment.IsSUSEFamily) { + "rpm", "nupkg" + } else { + throw "Building packages for $($Environment.LinuxInfo.PRETTY_NAME) is unsupported!" + } + } elseif ($Environment.IsMacOS) { + "osxpkg", "nupkg", "tar" + } elseif ($Environment.IsWindows) { + "msi", "nupkg", "msix" + } + Write-Warning "-Type was not specified, continuing with $Type!" + } + Write-Log "Packaging Type: $Type" + + # Add the symbols to the suffix + # if symbols are specified to be included + if ($IncludeSymbols.IsPresent -and $NameSuffix) { + $NameSuffix = "symbols-$NameSuffix" + } + elseif ($IncludeSymbols.IsPresent) { + $NameSuffix = "symbols" + } + + switch ($Type) { + "zip" { + $Arguments = @{ + PackageNameSuffix = $NameSuffix + PackageSourcePath = $Source + PackageVersion = $Version + Force = $Force + } + + if ($PSCmdlet.ShouldProcess("Create Zip Package")) { + New-ZipPackage @Arguments + } + } + "zip-pdb" { + $Arguments = @{ + PackageNameSuffix = $NameSuffix + PackageSourcePath = $Source + PackageVersion = $Version + Force = $Force + } + + if ($PSCmdlet.ShouldProcess("Create Symbols Zip Package")) { + New-PdbZipPackage @Arguments + } + } + "min-size" { + # Remove symbol files, xml document files. + Remove-Item "$Source\*.pdb", "$Source\*.xml" -Force + + # Add suffix '-gc' because this package is for the Guest Config team. + if ($Environment.IsWindows) { + $Arguments = @{ + PackageNameSuffix = "$NameSuffix-gc" + PackageSourcePath = $Source + PackageVersion = $Version + Force = $Force + } + + if ($PSCmdlet.ShouldProcess("Create Zip Package")) { + New-ZipPackage @Arguments + } + } + elseif ($Environment.IsLinux) { + $Arguments = @{ + PackageSourcePath = $Source + Name = $Name + PackageNameSuffix = 'gc' + Version = $Version + Force = $Force + } + + if ($PSCmdlet.ShouldProcess("Create tar.gz Package")) { + New-TarballPackage @Arguments + } + } + } + { $_ -like "fxdependent*" } { + ## Remove PDBs from package to reduce size. + if(-not $IncludeSymbols.IsPresent) { + Get-ChildItem $Source -Filter *.pdb | Remove-Item -Force + } + + if ($Environment.IsWindows) { + $Arguments = @{ + PackageNameSuffix = $NameSuffix + PackageSourcePath = $Source + PackageVersion = $Version + Force = $Force + } + + if ($PSCmdlet.ShouldProcess("Create Zip Package")) { + New-ZipPackage @Arguments + } + } elseif ($Environment.IsLinux) { + $Arguments = @{ + PackageSourcePath = $Source + Name = $Name + PackageNameSuffix = 'fxdependent' + Version = $Version + Force = $Force + } + + if ($PSCmdlet.ShouldProcess("Create tar.gz Package")) { + New-TarballPackage @Arguments + } + } + } + "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 + ProductSourcePath = $Source + ProductVersion = $Version + Architecture = $WindowsRuntime.Split('-')[1] + Force = $Force + } + + if ($PSCmdlet.ShouldProcess("Create MSIX Package")) { + New-MSIXPackage @Arguments + } + } + 'nupkg' { + $Arguments = @{ + PackageNameSuffix = $NameSuffix + PackageSourcePath = $Source + PackageVersion = $Version + PackageRuntime = $Runtime + PackageConfiguration = $Configuration + Force = $Force + } + + if ($PSCmdlet.ShouldProcess("Create NuPkg Package")) { + New-NugetContentPackage @Arguments + } + } + "tar" { + $Arguments = @{ + PackageSourcePath = $Source + Name = $Name + Version = $Version + Force = $Force + } + + if ($MacOSRuntime) { + $Arguments['Architecture'] = $MacOSRuntime.Split('-')[1] + } + + if ($PSCmdlet.ShouldProcess("Create tar.gz Package")) { + New-TarballPackage @Arguments + } + } + "tar-arm" { + $Arguments = @{ + PackageSourcePath = $Source + Name = $Name + Version = $Version + Force = $Force + Architecture = "arm32" + ExcludeSymbolicLinks = $true + } + + if ($PSCmdlet.ShouldProcess("Create tar.gz Package")) { + New-TarballPackage @Arguments + } + } + "tar-arm64" { + $Arguments = @{ + PackageSourcePath = $Source + Name = $Name + Version = $Version + Force = $Force + Architecture = "arm64" + ExcludeSymbolicLinks = $true + } + + if ($PSCmdlet.ShouldProcess("Create tar.gz Package")) { + New-TarballPackage @Arguments + } + } + "tar-alpine" { + $Arguments = @{ + PackageSourcePath = $Source + Name = $Name + Version = $Version + Force = $Force + Architecture = "alpine-x64" + ExcludeSymbolicLinks = $true + } + + if ($PSCmdlet.ShouldProcess("Create tar.gz Package")) { + New-TarballPackage @Arguments + } + } + 'deb' { + $Arguments = @{ + Type = 'deb' + PackageSourcePath = $Source + Name = $Name + Version = $Version + Force = $Force + NoSudo = $NoSudo + LTS = $LTS + } + foreach ($Distro in $Script:DebianDistributions) { + $Arguments["Distribution"] = $Distro + if ($PSCmdlet.ShouldProcess("Create DEB Package for $Distro")) { + New-UnixPackage @Arguments + } + } + } + 'rpm' { + $Arguments = @{ + Type = 'rpm' + PackageSourcePath = $Source + Name = $Name + Version = $Version + Force = $Force + NoSudo = $NoSudo + LTS = $LTS + } + foreach ($Distro in $Script:RedhatDistributions) { + $Arguments["Distribution"] = $Distro + if ($PSCmdlet.ShouldProcess("Create RPM Package for $Distro")) { + New-UnixPackage @Arguments + } + } + } + default { + $Arguments = @{ + Type = $_ + PackageSourcePath = $Source + Name = $Name + Version = $Version + Force = $Force + NoSudo = $NoSudo + LTS = $LTS + } + + if ($PSCmdlet.ShouldProcess("Create $_ Package")) { + New-UnixPackage @Arguments + } + } + } + + if ($IncludeSymbols.IsPresent) + { + # Source is a temporary folder when -IncludeSymbols is present. So, we should remove it. + Remove-Item -Path $Source -Recurse -Force -ErrorAction SilentlyContinue + } + } +} + +function New-UnixPackage { + [CmdletBinding(SupportsShouldProcess=$true)] + param( + [Parameter(Mandatory)] + [ValidateSet("deb", "osxpkg", "rpm")] + [string]$Type, + + [Parameter(Mandatory)] + [string]$PackageSourcePath, + + # Must start with 'powershell' but may have any suffix + [Parameter(Mandatory)] + [ValidatePattern("^powershell")] + [string]$Name, + + [Parameter(Mandatory)] + [string]$Version, + + # Package iteration version (rarely changed) + # This is a string because strings are appended to it + [string]$Iteration = "1", + + [Switch] + $Force, + + [switch] + $NoSudo, + + [switch] + $LTS, + + [string] + $CurrentLocation = (Get-Location) + ) + + DynamicParam { + if ($Type -eq "deb" -or $Type -eq '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. + $ParameterAttr = New-Object "System.Management.Automation.ParameterAttribute" + if($type -eq 'deb') + { + $ValidateSetAttr = New-Object "System.Management.Automation.ValidateSetAttribute" -ArgumentList $Script:DebianDistributions + } + else + { + $ValidateSetAttr = New-Object "System.Management.Automation.ValidateSetAttribute" -ArgumentList $Script:RedHatDistributions + } + $Attributes = New-Object "System.Collections.ObjectModel.Collection``1[System.Attribute]" + $Attributes.Add($ParameterAttr) > $null + $Attributes.Add($ValidateSetAttr) > $null + + $Parameter = New-Object "System.Management.Automation.RuntimeDefinedParameter" -ArgumentList ("Distribution", [string], $Attributes) + $Dict = New-Object "System.Management.Automation.RuntimeDefinedParameterDictionary" + $Dict.Add("Distribution", $Parameter) > $null + return $Dict + } + } + + End { + # 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" } + + # Validate platform + $ErrorMessage = "Must be on {0} to build '$Type' packages!" + switch ($Type) { + "deb" { + $packageVersion = Get-LinuxPackageSemanticVersion -Version $Version + if (!$Environment.IsUbuntu -and !$Environment.IsDebian) { + throw ($ErrorMessage -f "Ubuntu or Debian") + } + + if ($PSBoundParameters.ContainsKey('Distribution')) { + $DebDistro = $PSBoundParameters['Distribution'] + } elseif ($Environment.IsUbuntu16) { + $DebDistro = "ubuntu.16.04" + } elseif ($Environment.IsUbuntu18) { + $DebDistro = "ubuntu.18.04" + } elseif ($Environment.IsUbuntu20) { + $DebDistro = "ubuntu.20.04" + } elseif ($Environment.IsDebian9) { + $DebDistro = "debian.9" + } else { + throw "The current Debian distribution is not supported." + } + + # iteration is "debian_revision" + # usage of this to differentiate distributions is allowed by non-standard + $Iteration += ".$DebDistro" + } + "rpm" { + if ($PSBoundParameters.ContainsKey('Distribution')) { + $DebDistro = $PSBoundParameters['Distribution'] + + } elseif ($Environment.IsRedHatFamily) { + $DebDistro = "rhel.7" + } else { + throw "The current distribution is not supported." + } + + $packageVersion = Get-LinuxPackageSemanticVersion -Version $Version + } + "osxpkg" { + $packageVersion = $Version + if (!$Environment.IsMacOS) { + throw ($ErrorMessage -f "macOS") + } + + $DebDistro = 'macOS' + } + } + + # Determine if the version is a preview version + $IsPreview = Test-IsPreview -Version $Version -IsLTS:$LTS + + # Preview versions have preview in the name + $Name = if($LTS) { + "powershell-lts" + } + elseif ($IsPreview) { + "powershell-preview" + } + else { + "powershell" + } + + # Verify dependencies are installed and in the path + Test-Dependencies + + $Description = $packagingStrings.Description + + # Break the version down into its components, we are interested in the major version + $VersionMatch = [regex]::Match($Version, '(\d+)(?:.(\d+)(?:.(\d+)(?:-preview(?:.(\d+))?)?)?)?') + $MajorVersion = $VersionMatch.Groups[1].Value + + # Suffix is used for side-by-side preview/release package installation + $Suffix = if ($IsPreview) { $MajorVersion + "-preview" } elseif ($LTS) { $MajorVersion + "-lts" } else { $MajorVersion } + + # Setup staging directory so we don't change the original source directory + $Staging = "$PSScriptRoot/staging" + if ($PSCmdlet.ShouldProcess("Create staging folder")) { + New-StagingFolder -StagingPath $Staging -PackageSourcePath $PackageSourcePath + } + + # Follow the Filesystem Hierarchy Standard for Linux and macOS + $Destination = if ($Environment.IsLinux) { + "/opt/microsoft/powershell/$Suffix" + } elseif ($Environment.IsMacOS) { + "/usr/local/microsoft/powershell/$Suffix" + } + + # Destination for symlink to powershell executable + $Link = Get-PwshExecutablePath -IsPreview:$IsPreview + $links = @(New-LinkInfo -LinkDestination $Link -LinkTarget "$Destination/pwsh") + + if($LTS) { + $links += New-LinkInfo -LinkDestination (Get-PwshExecutablePath -IsLTS:$LTS) -LinkTarget "$Destination/pwsh" + } + + if ($PSCmdlet.ShouldProcess("Create package file system")) + { + # 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 + + # Change permissions for packaging + Write-Log "Setting permissions..." + Start-NativeExecution { + find $Staging -type d | xargs chmod 755 + 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 + } + } + + # Add macOS powershell launcher + if ($Type -eq "osxpkg") + { + Write-Log "Adding macOS launch application..." + if ($PSCmdlet.ShouldProcess("Add macOS launch application")) + { + # Generate launcher app folder + $AppsFolder = New-MacOSLauncher -Version $Version + } + } + + $packageDependenciesParams = @{} + if ($DebDistro) + { + $packageDependenciesParams['Distribution']=$DebDistro + } + + # Setup package dependencies + $Dependencies = @(Get-PackageDependencies @packageDependenciesParams) + + $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 ` + -ErrorAction Stop + + # Build package + try { + if ($PSCmdlet.ShouldProcess("Create $type package")) { + Write-Log "Creating package with fpm..." + $Output = Start-NativeExecution { fpm $Arguments } + } + } finally { + if ($Environment.IsMacOS) { + Write-Log "Starting Cleanup for mac packaging..." + if ($PSCmdlet.ShouldProcess("Cleanup macOS launcher")) + { + 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 + } + } + if ($AfterScriptInfo.AfterInstallScript) { + Remove-Item -ErrorAction 'silentlycontinue' $AfterScriptInfo.AfterInstallScript -Force + } + if ($AfterScriptInfo.AfterRemoveScript) { + Remove-Item -ErrorAction 'silentlycontinue' $AfterScriptInfo.AfterRemoveScript -Force + } + Remove-Item -Path $ManGzipInfo.GzipFile -Force -ErrorAction SilentlyContinue + } + + # 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 -IsPreview:$IsPreview + } + } + + if (Test-Path $createdPackage) + { + Write-Verbose "Created package: $createdPackage" -Verbose + return $createdPackage + } + else + { + throw "Failed to create $createdPackage" + } + } +} diff --git a/test/perf/benchmarks/powershell-perf.csproj b/test/perf/benchmarks/powershell-perf.csproj new file mode 100644 index 00000000000..93c164b98b8 --- /dev/null +++ b/test/perf/benchmarks/powershell-perf.csproj @@ -0,0 +1,68 @@ + + + + + + + PowerShell Performance Tests + powershell-perf + Exe + + $(NoWarn);CS8002 + true + + AnyCPU + portable + true + + + $(PERF_TARGET_VERSION) + + + + netcoreapp3.1;net5.0;net6.0 + + 7.1.3 + 7.0.6 + + + + true + ../../../src/signing/visualstudiopublic.snk + true + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj new file mode 100644 index 00000000000..0cabba95a26 --- /dev/null +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj @@ -0,0 +1,17 @@ + + + + Library + netstandard2.0 + + + + + + + + + + + + diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/CommandLineOptions.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/CommandLineOptions.cs new file mode 100644 index 00000000000..aa60c7cc2e6 --- /dev/null +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/CommandLineOptions.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; + +namespace BenchmarkDotNet.Extensions +{ + public class CommandLineOptions + { + // Find and parse given parameter with expected int value, then remove it and its value from the list of arguments to then pass to BenchmarkDotNet + // Throws ArgumentException if the parameter does not have a value or that value is not parsable as an int + public static List ParseAndRemoveIntParameter(List argsList, string parameter, out int? parameterValue) + { + int parameterIndex = argsList.IndexOf(parameter); + parameterValue = null; + + if (parameterIndex != -1) + { + if (parameterIndex + 1 < argsList.Count && Int32.TryParse(argsList[parameterIndex+1], out int parsedParameterValue)) + { + // remove --partition-count args + parameterValue = parsedParameterValue; + argsList.RemoveAt(parameterIndex+1); + argsList.RemoveAt(parameterIndex); + } + else + { + throw new ArgumentException($"{parameter} must be followed by an integer"); + } + } + + return argsList; + } + + public static List ParseAndRemoveStringsParameter(List argsList, string parameter, out List parameterValue) + { + int parameterIndex = argsList.IndexOf(parameter); + parameterValue = new List(); + + if (parameterIndex + 1 < argsList.Count) + { + 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]); + argsList.RemoveAt(parameterIndex + 1); + } + } + //We only want to remove the --exclusion-filter if it exists + if (parameterIndex != -1) + { + argsList.RemoveAt(parameterIndex); + } + + return argsList; + } + + public static void ParseAndRemoveBooleanParameter(List argsList, string parameter, out bool parameterValue) + { + int parameterIndex = argsList.IndexOf(parameter); + + if (parameterIndex != -1) + { + argsList.RemoveAt(parameterIndex); + + parameterValue = true; + } + else + { + parameterValue = false; + } + } + + public static void ValidatePartitionParameters(int? count, int? index) + { + // Either count and index must both be specified or neither specified + if (!(count.HasValue == index.HasValue)) + { + throw new ArgumentException("If either --partition-count or --partition-index is specified, both must be specified"); + } + // Check values of count and index parameters + else if (count.HasValue && index.HasValue) + { + if (count < 2) + { + throw new ArgumentException("When specified, value of --partition-count must be greater than 1"); + } + else if (!(index < count)) + { + throw new ArgumentException("Value of --partition-index must be less than --partition-count"); + } + else if (index < 0) + { + throw new ArgumentException("Value of --partition-index must be greater than or equal to 0"); + } + } + } + } +} diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/DiffableDisassemblyExporter.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/DiffableDisassemblyExporter.cs new file mode 100644 index 00000000000..d45977ed5bf --- /dev/null +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/DiffableDisassemblyExporter.cs @@ -0,0 +1,90 @@ +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Disassemblers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace BenchmarkDotNet.Extensions +{ + // a simplified copy of internal BDN type: https://github.com/dotnet/BenchmarkDotNet/blob/0445917bf93059f17cb09e7d48cdb5e27a096c37/src/BenchmarkDotNet/Disassemblers/Exporters/GithubMarkdownDisassemblyExporter.cs#L35-L80 + internal static class DiffableDisassemblyExporter + { + private static readonly Lazy> GetSource = new Lazy>(() => GetElementGetter("Source")); + private static readonly Lazy> GetTextRepresentation = new Lazy>(() => GetElementGetter("TextRepresentation")); + + private static readonly Lazy>> Prettify + = new Lazy>>(GetPrettifyMethod); + + internal static string BuildDisassemblyString(DisassemblyResult disassemblyResult, DisassemblyDiagnoserConfig config) + { + StringBuilder sb = new StringBuilder(); + + int methodIndex = 0; + foreach (var method in disassemblyResult.Methods.Where(method => string.IsNullOrEmpty(method.Problem))) + { + sb.AppendLine("```assembly"); + + sb.AppendLine($"; {method.Name}"); + + var pretty = Prettify.Value.Invoke(method, disassemblyResult, config, $"M{methodIndex++:00}"); + + ulong totalSizeInBytes = 0; + foreach (var element in pretty) + { + if (element.Source() is Asm asm) + { + checked + { + totalSizeInBytes += (uint)asm.Instruction.Length; + } + + sb.AppendLine($" {element.TextRepresentation()}"); + } + else // it's a DisassemblyPrettifier.Label (internal type..) + { + sb.AppendLine($"{element.TextRepresentation()}:"); + } + } + + sb.AppendLine($"; Total bytes of code {totalSizeInBytes}"); + sb.AppendLine("```"); + } + + return sb.ToString(); + } + + private static SourceCode Source(this object element) => GetSource.Value.Invoke(element); + + private static string TextRepresentation(this object element) => GetTextRepresentation.Value.Invoke(element); + + private static Func GetElementGetter(string name) + { + var type = typeof(DisassemblyDiagnoser).Assembly.GetType("BenchmarkDotNet.Disassemblers.Exporters.DisassemblyPrettifier"); + + type = type.GetNestedType("Element", BindingFlags.Instance | BindingFlags.NonPublic); + + var property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.NonPublic); + + var method = property.GetGetMethod(nonPublic: true); + + var generic = typeof(Func<,>).MakeGenericType(type, typeof(T)); + + var @delegate = method.CreateDelegate(generic); + + return (obj) => (T)@delegate.DynamicInvoke(obj); // cast to (Func) throws + } + + private static Func> GetPrettifyMethod() + { + var type = typeof(DisassemblyDiagnoser).Assembly.GetType("BenchmarkDotNet.Disassemblers.Exporters.DisassemblyPrettifier"); + + var method = type.GetMethod("Prettify", BindingFlags.Static | BindingFlags.NonPublic); + + var @delegate = method.CreateDelegate(typeof(Func>)); + + return (Func>)@delegate; + } + } +} diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/ExclusionFilter.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/ExclusionFilter.cs new file mode 100644 index 00000000000..b3ee453123f --- /dev/null +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/ExclusionFilter.cs @@ -0,0 +1,52 @@ +using BenchmarkDotNet.Filters; +using BenchmarkDotNet.Running; +using System; +using System.Collections.Generic; +using System.Text; + +namespace BenchmarkDotNet.Extensions +{ + class ExclusionFilter : IFilter + { + private readonly GlobFilter globFilter; + + public ExclusionFilter(List _filter) + { + if (_filter != null && _filter.Count != 0) + { + globFilter = new GlobFilter(_filter.ToArray()); + } + } + + public bool Predicate(BenchmarkCase benchmarkCase) + { + if(globFilter == null) + { + return true; + } + return !globFilter.Predicate(benchmarkCase); + } + } + + class CategoryExclusionFilter : IFilter + { + private readonly AnyCategoriesFilter filter; + + public CategoryExclusionFilter(List patterns) + { + if (patterns != null) + { + filter = new AnyCategoriesFilter(patterns.ToArray()); + } + } + + public bool Predicate(BenchmarkCase benchmarkCase) + { + if (filter == null) + { + return true; + } + return !filter.Predicate(benchmarkCase); + } + } +} diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/Extensions.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/Extensions.cs new file mode 100644 index 00000000000..7d0631b896b --- /dev/null +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/Extensions.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using BenchmarkDotNet.Reports; + +namespace BenchmarkDotNet.Extensions +{ + public static class SummaryExtensions + { + public static int ToExitCode(this IEnumerable summaries) + { + // an empty summary means that initial filtering and validation did not allow to run + if (!summaries.Any()) + return 1; + + // if anything has failed, it's an error + if (summaries.Any(summary => summary.HasCriticalValidationErrors || summary.Reports.Any(report => !report.BuildResult.IsBuildSuccess || !report.AllMeasurements.Any()))) + return 1; + + 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 new file mode 100644 index 00000000000..7b3b2d38f3b --- /dev/null +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/MandatoryCategoryValidator.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using BenchmarkDotNet.Validators; + +namespace BenchmarkDotNet.Extensions +{ + /// + /// this class makes sure that every benchmark belongs to a mandatory category + /// categories are used by the CI for filtering + /// + public class MandatoryCategoryValidator : IValidator + { + private readonly ImmutableHashSet _mandatoryCategories; + + public bool TreatsWarningsAsErrors => true; + + public MandatoryCategoryValidator(ImmutableHashSet categories) => _mandatoryCategories = categories; + + public IEnumerable Validate(ValidationParameters validationParameters) + => validationParameters.Benchmarks + .Where(benchmark => !benchmark.Descriptor.Categories.Any(category => _mandatoryCategories.Contains(category))) + .Select(benchmark => benchmark.Descriptor.GetFilterName()) + .Distinct() + .Select(benchmarkId => + new ValidationError( + isCritical: TreatsWarningsAsErrors, + $"{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 new file mode 100644 index 00000000000..16ae22f3167 --- /dev/null +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/PartitionFilter.cs @@ -0,0 +1,27 @@ +using BenchmarkDotNet.Filters; +using System; +using System.Collections.Generic; +using System.Linq; +using BenchmarkDotNet.Running; + + +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) + return true; // the filter is not enabled so it does not filter anything out and can be added to RecommendedConfig + + 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/PerfLabExporter.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/PerfLabExporter.cs new file mode 100644 index 00000000000..86306da342a --- /dev/null +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/PerfLabExporter.cs @@ -0,0 +1,115 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Reports; +using Reporting; +using System.Linq; + +namespace BenchmarkDotNet.Extensions +{ + internal class PerfLabExporter : ExporterBase + { + protected override string FileExtension => "json"; + protected override string FileCaption => "perf-lab-report"; + + public PerfLabExporter() + { + } + + public override void ExportToLog(Summary summary, ILogger logger) + { + var reporter = Reporter.CreateReporter(); + + DisassemblyDiagnoser disassemblyDiagnoser = summary.Reports + .FirstOrDefault()? // disassembler was either enabled for all or none of them (so we use the first one) + .BenchmarkCase.Config.GetDiagnosers().OfType().FirstOrDefault(); + + foreach (var report in summary.Reports) + { + var test = new Test(); + test.Name = FullNameProvider.GetBenchmarkName(report.BenchmarkCase); + test.Categories = report.BenchmarkCase.Descriptor.Categories; + + var results = from result in report.AllMeasurements + where result.IterationMode == Engines.IterationMode.Workload && result.IterationStage == Engines.IterationStage.Result + orderby result.LaunchIndex, result.IterationIndex + select new { result.Nanoseconds, result.Operations}; + + var overheadResults = from result in report.AllMeasurements + where result.IsOverhead() && result.IterationStage != Engines.IterationStage.Jitting + orderby result.LaunchIndex, result.IterationIndex + select new { result.Nanoseconds, result.Operations }; + + test.Counters.Add(new Counter + { + Name = "Duration of single invocation", + TopCounter = true, + DefaultCounter = true, + HigherIsBetter = false, + MetricName = "ns", + Results = (from result in results + select result.Nanoseconds / result.Operations).ToList() + }); + test.Counters.Add(new Counter + { + Name = "Overhead invocation", + TopCounter = false, + DefaultCounter = false, + HigherIsBetter = false, + MetricName = "ns", + Results = (from result in overheadResults + select result.Nanoseconds / result.Operations).ToList() + }); + test.Counters.Add(new Counter + { + Name = "Duration", + TopCounter = false, + DefaultCounter = false, + HigherIsBetter = false, + MetricName = "ms", + Results = (from result in results + select result.Nanoseconds).ToList() + }); + + test.Counters.Add(new Counter + { + Name = "Operations", + TopCounter = false, + DefaultCounter = false, + HigherIsBetter = true, + MetricName = "Count", + Results = (from result in results + select (double)result.Operations).ToList() + }); + + foreach (var metric in report.Metrics.Keys) + { + var m = report.Metrics[metric]; + test.Counters.Add(new Counter + { + Name = m.Descriptor.DisplayName, + TopCounter = false, + DefaultCounter = false, + HigherIsBetter = m.Descriptor.TheGreaterTheBetter, + MetricName = m.Descriptor.Unit, + Results = new[] { m.Value } + }); + } + + if (disassemblyDiagnoser != null && disassemblyDiagnoser.Results.TryGetValue(report.BenchmarkCase, out var disassemblyResult)) + { + string disassembly = DiffableDisassemblyExporter.BuildDisassemblyString(disassemblyResult, disassemblyDiagnoser.Config); + test.AdditionalData["disasm"] = disassembly; + } + + reporter.AddTest(test); + } + + logger.WriteLine(reporter.GetJson()); + } + } +} diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/RecommendedConfig.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/RecommendedConfig.cs new file mode 100644 index 00000000000..a8aac9700b0 --- /dev/null +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/RecommendedConfig.cs @@ -0,0 +1,86 @@ +using System.Collections.Immutable; +using System.IO; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Exporters.Json; +using Perfolizer.Horology; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Reports; +using System.Collections.Generic; +using Reporting; +using BenchmarkDotNet.Loggers; +using System.Linq; +using BenchmarkDotNet.Exporters; + +namespace BenchmarkDotNet.Extensions +{ + public static class RecommendedConfig + { + public static IConfig Create( + DirectoryInfo artifactsPath, + ImmutableHashSet mandatoryCategories, + int? partitionCount = null, + int? partitionIndex = null, + List exclusionFilterValue = null, + List categoryExclusionFilterValue = null, + Job job = null, + bool getDiffableDisasm = false) + { + if (job is null) + { + job = Job.Default + .WithWarmupCount(1) // 1 warmup is enough for our purpose + .WithIterationTime(TimeInterval.FromMilliseconds(250)) // the default is 0.5s per iteration, which is slightly too much for us + .WithMinIterationCount(15) + .WithMaxIterationCount(20) // we don't want to run more that 20 iterations + .DontEnforcePowerPlan(); // make sure BDN does not try to enforce High Performance power plan on Windows + + // See https://github.com/dotnet/roslyn/issues/42393 + job = job.WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") }); + } + + var config = ManualConfig.CreateEmpty() + .AddLogger(ConsoleLogger.Default) // log output to console + .AddValidator(DefaultConfig.Instance.GetValidators().ToArray()) // copy default validators + .AddAnalyser(DefaultConfig.Instance.GetAnalysers().ToArray()) // copy default analysers + .AddExporter(MarkdownExporter.GitHub) // export to GitHub markdown + .AddColumnProvider(DefaultColumnProviders.Instance) // display default columns (method name, args etc) + .AddJob(job.AsDefault()) // tell BDN that this are our default settings + .WithArtifactsPath(artifactsPath.FullName) + .AddDiagnoser(MemoryDiagnoser.Default) // MemoryDiagnoser is enabled by default + .AddFilter(new PartitionFilter(partitionCount, partitionIndex)) + .AddFilter(new ExclusionFilter(exclusionFilterValue)) + .AddFilter(new CategoryExclusionFilter(categoryExclusionFilterValue)) + .AddExporter(JsonExporter.Full) // make sure we export to Json + .AddColumn(StatisticColumn.Median, StatisticColumn.Min, StatisticColumn.Max) + .AddValidator(TooManyTestCasesValidator.FailOnError) + .AddValidator(new UniqueArgumentsValidator()) // don't allow for duplicated arguments #404 + .AddValidator(new MandatoryCategoryValidator(mandatoryCategories)) + .WithSummaryStyle(SummaryStyle.Default.WithMaxParameterColumnWidth(36)); // the default is 20 and trims too aggressively some benchmark results + + if (Reporter.CreateReporter().InLab) + { + config = config.AddExporter(new PerfLabExporter()); + } + + if (getDiffableDisasm) + { + config = config.AddDiagnoser(CreateDisassembler()); + } + + return config; + } + + private static DisassemblyDiagnoser CreateDisassembler() + => new DisassemblyDiagnoser(new DisassemblyDiagnoserConfig( + maxDepth: 1, // TODO: is depth == 1 enough? + formatter: null, // TODO: enable diffable format + printSource: false, // we are not interested in getting C# + printInstructionAddresses: false, // would make the diffing hard, however could be useful to determine alignment + exportGithubMarkdown: false, + exportHtml: false, + exportCombinedDisassemblyReport: false, + exportDiff: false)); + } +} diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/TooManyTestCasesValidator.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/TooManyTestCasesValidator.cs new file mode 100644 index 00000000000..b001f603dcb --- /dev/null +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/TooManyTestCasesValidator.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using BenchmarkDotNet.Validators; + +namespace BenchmarkDotNet.Extensions +{ + /// + /// we need to tell our users that having more than 16 test cases per benchmark is a VERY BAD idea + /// + public class TooManyTestCasesValidator : IValidator + { + private const int Limit = 16; + + public static readonly IValidator FailOnError = new TooManyTestCasesValidator(); + + public bool TreatsWarningsAsErrors => true; + + public IEnumerable Validate(ValidationParameters validationParameters) + { + var byDescriptor = validationParameters.Benchmarks.GroupBy(benchmark => (benchmark.Descriptor, benchmark.Job)); // descriptor = type + method + + return byDescriptor.Where(benchmarkCase => benchmarkCase.Count() > Limit).Select(group => + new ValidationError( + isCritical: true, + message: $"{group.Key.Descriptor.Type.Name}.{group.Key.Descriptor.WorkloadMethod.Name} has {group.Count()} test cases. It MUST NOT have more than {Limit} test cases. We don't have infinite amount of time to run all the benchmarks!!", + 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 new file mode 100644 index 00000000000..7bfab8445cb --- /dev/null +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/UniqueArgumentsValidator.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using BenchmarkDotNet.Validators; +using System.Collections.Generic; +using System.Linq; +using BenchmarkDotNet.Running; + +namespace BenchmarkDotNet.Extensions +{ + public class UniqueArgumentsValidator : IValidator + { + public bool TreatsWarningsAsErrors => true; + + public IEnumerable Validate(ValidationParameters validationParameters) + => validationParameters.Benchmarks + .Where(benchmark => benchmark.HasArguments || benchmark.HasParameters) + .GroupBy(benchmark => (benchmark.Descriptor.Type, benchmark.Descriptor.WorkloadMethod, benchmark.Job)) + .Where(sameBenchmark => + { + int numberOfUniqueTestCases = sameBenchmark.Distinct(new BenchmarkArgumentsComparer()).Count(); + int numberOfTestCases = sameBenchmark.Count(); + + return numberOfTestCases != numberOfUniqueTestCases; + }) + .Select(duplicate => new ValidationError(true, $"Benchmark Arguments should be unique, {duplicate.Key.Type}.{duplicate.Key.WorkloadMethod} has duplicate arguments.", duplicate.First())); + + private class BenchmarkArgumentsComparer : IEqualityComparer + { + public bool Equals(BenchmarkCase x, BenchmarkCase y) + => Enumerable.SequenceEqual( + x.Parameters.Items.Select(argument => argument.Value), + y.Parameters.Items.Select(argument => argument.Value)); + + public int GetHashCode(BenchmarkCase obj) + => obj.Parameters.Items + .Where(item => item.Value != null) + .Aggregate(seed: 0, (hashCode, argument) => hashCode ^= argument.Value.GetHashCode()); + } + } +} diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/ValuesGenerator.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/ValuesGenerator.cs new file mode 100644 index 00000000000..87bf6d82d8d --- /dev/null +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/ValuesGenerator.cs @@ -0,0 +1,148 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; + +namespace BenchmarkDotNet.Extensions +{ + public static class ValuesGenerator + { + private const int Seed = 12345; // we always use the same seed to have repeatable results! + + public static T GetNonDefaultValue() + { + if (typeof(T) == typeof(byte)) // we can't use ArrayOfUniqueValues for byte + return Array(byte.MaxValue).First(value => !value.Equals(default)); + else + return ArrayOfUniqueValues(2).First(value => !value.Equals(default)); + } + + /// + /// does not support byte because there are only 256 unique byte values + /// + public static T[] ArrayOfUniqueValues(int count) + { + // allocate the array first to try to take advantage of memory randomization + // as it's usually the first thing called from GlobalSetup method + // which with MemoryRandomization enabled is the first method called right after allocation + // of random-sized memory by BDN engine + T[] result = new T[count]; + + var random = new Random(Seed); + + var uniqueValues = new HashSet(); + + while (uniqueValues.Count != count) + { + T value = GenerateValue(random); + + if (!uniqueValues.Contains(value)) + uniqueValues.Add(value); + } + + uniqueValues.CopyTo(result); + + return result; + } + + public static T[] Array(int count) + { + var result = new T[count]; + + var random = new Random(Seed); + + if (typeof(T) == typeof(byte) || typeof(T) == typeof(sbyte)) + { + random.NextBytes(Unsafe.As(result)); + } + else + { + for (int i = 0; i < result.Length; i++) + { + result[i] = GenerateValue(random); + } + } + + return result; + } + + public static Dictionary Dictionary(int count) + { + var dictionary = new Dictionary(); + + var random = new Random(Seed); + + while (dictionary.Count != count) + { + TKey key = GenerateValue(random); + + if (!dictionary.ContainsKey(key)) + dictionary.Add(key, GenerateValue(random)); + } + + return dictionary; + } + + private static T GenerateValue(Random random) + { + if (typeof(T) == typeof(char)) + return (T)(object)(char)random.Next(char.MinValue, char.MaxValue); + if (typeof(T) == typeof(short)) + return (T)(object)(short)random.Next(short.MaxValue); + if (typeof(T) == typeof(ushort)) + return (T)(object)(ushort)random.Next(short.MaxValue); + if (typeof(T) == typeof(int)) + return (T)(object)random.Next(); + if (typeof(T) == typeof(uint)) + return (T)(object)(uint)random.Next(); + if (typeof(T) == typeof(long)) + return (T)(object)(long)random.Next(); + if (typeof(T) == typeof(ulong)) + return (T)(object)(ulong)random.Next(); + if (typeof(T) == typeof(float)) + return (T)(object)(float)random.NextDouble(); + if (typeof(T) == typeof(double)) + return (T)(object)random.NextDouble(); + if (typeof(T) == typeof(bool)) + return (T)(object)(random.NextDouble() > 0.5); + if (typeof(T) == typeof(string)) + return (T)(object)GenerateRandomString(random, 1, 50); + if (typeof(T) == typeof(Guid)) + return (T)(object)GenerateRandomGuid(random); + + throw new NotImplementedException($"{typeof(T).Name} is not implemented"); + } + + private static string GenerateRandomString(Random random, int minLength, int maxLength) + { + var length = random.Next(minLength, maxLength); + + var builder = new StringBuilder(length); + for (int i = 0; i < length; i++) + { + var rangeSelector = random.Next(0, 3); + + if (rangeSelector == 0) + builder.Append((char) random.Next('a', 'z')); + else if (rangeSelector == 1) + builder.Append((char) random.Next('A', 'Z')); + else + builder.Append((char) random.Next('0', '9')); + } + + return builder.ToString(); + } + + private static Guid GenerateRandomGuid(Random random) + { + byte[] bytes = new byte[16]; + random.NextBytes(bytes); + return new Guid(bytes); + } + } +} \ No newline at end of file diff --git a/test/perf/dotnet-tools/README.md b/test/perf/dotnet-tools/README.md new file mode 100644 index 00000000000..fa3ce3b2a78 --- /dev/null +++ b/test/perf/dotnet-tools/README.md @@ -0,0 +1,14 @@ +## Tools + +The tools here are copied from [dotnet/performance](https://github.com/dotnet/performance), +the performance testing repository for the .NET runtime and framework libraries. + +- [BenchmarkDotNet.Extensions](https://github.com/dotnet/performance/tree/main/src/harness/BenchmarkDotNet.Extensions) + - It provides the needed extensions for running benchmarks, + such as the `RecommendedConfig` which defines the set of recommended configurations for running the dotnet benchmarks. +- [Reporting](https://github.com/dotnet/performance/tree/main/src/tools/Reporting) + - It provides additional result reporting support + which may be useful to us when running our benchmarks in lab. +- [ResultsComparer](https://github.com/dotnet/performance/tree/main/src/tools/ResultsComparer) + - It's a tool for comparing different benchmark results. + It's very useful to show the regression of new changes by comparing its benchmark results to the baseline results. diff --git a/test/perf/dotnet-tools/Reporting/Build.cs b/test/perf/dotnet-tools/Reporting/Build.cs new file mode 100644 index 00000000000..c98ac254f34 --- /dev/null +++ b/test/perf/dotnet-tools/Reporting/Build.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Text; + +namespace Reporting +{ + public sealed class Build + { + public string Repo { get; set; } + + public string Branch { get; set; } + + public string Architecture { get; set; } + + public string Locale { get; set; } + + public string GitHash { get; set; } + + public string BuildName { get; set; } + + public DateTime TimeStamp { get; set; } + + public Dictionary AdditionalData { get; set; } = new Dictionary(); + } +} diff --git a/test/perf/dotnet-tools/Reporting/Counter.cs b/test/perf/dotnet-tools/Reporting/Counter.cs new file mode 100644 index 00000000000..3952369c312 --- /dev/null +++ b/test/perf/dotnet-tools/Reporting/Counter.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace Reporting +{ + public class Counter + { + public string Name { get; set; } + + public bool TopCounter { get; set; } + + public bool DefaultCounter { get; set; } + + public bool HigherIsBetter { get; set; } + + public string MetricName { get; set; } + + public IList Results { get; set; } + } +} \ No newline at end of file diff --git a/test/perf/dotnet-tools/Reporting/EnvironmentProvider.cs b/test/perf/dotnet-tools/Reporting/EnvironmentProvider.cs new file mode 100644 index 00000000000..90d28729284 --- /dev/null +++ b/test/perf/dotnet-tools/Reporting/EnvironmentProvider.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Reporting +{ + public class EnvironmentProvider : IEnvironment + { + public string GetEnvironmentVariable(string variable) => Environment.GetEnvironmentVariable(variable); + } +} diff --git a/test/perf/dotnet-tools/Reporting/IEnvironment.cs b/test/perf/dotnet-tools/Reporting/IEnvironment.cs new file mode 100644 index 00000000000..c7dbfb9b002 --- /dev/null +++ b/test/perf/dotnet-tools/Reporting/IEnvironment.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Reporting +{ + public interface IEnvironment + { + string GetEnvironmentVariable(string variable); + } +} diff --git a/test/perf/dotnet-tools/Reporting/Os.cs b/test/perf/dotnet-tools/Reporting/Os.cs new file mode 100644 index 00000000000..692a75ee7c5 --- /dev/null +++ b/test/perf/dotnet-tools/Reporting/Os.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Reporting +{ + public class Os + { + public string Locale { get; set; } + + public string Architecture { get; set; } + + 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 new file mode 100644 index 00000000000..9291d6f36eb --- /dev/null +++ b/test/perf/dotnet-tools/Reporting/Reporter.cs @@ -0,0 +1,153 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment; + +namespace Reporting +{ + public class Reporter + { + private Run run; + private Os os; + private Build build; + private List tests = new List(); + protected IEnvironment environment; + + private Reporter() { } + + public void AddTest(Test test) + { + if (tests.Any(t => t.Name.Equals(test.Name))) + throw new Exception($"Duplicate test name, {test.Name}"); + tests.Add(test); + } + + /// + /// Get a Reporter. Relies on environment variables. + /// + /// Optional environment variable provider + /// A Reporter instance or null if the environment is incorrect. + public static Reporter CreateReporter(IEnvironment environment = null) + { + var ret = new Reporter(); + ret.environment = environment == null ? new EnvironmentProvider() : environment; + if (ret.InLab) + { + ret.Init(); + } + + return ret; + } + + private void Init() + { + run = new Run + { + CorrelationId = environment.GetEnvironmentVariable("HELIX_CORRELATION_ID"), + PerfRepoHash = environment.GetEnvironmentVariable("PERFLAB_PERFHASH"), + Name = environment.GetEnvironmentVariable("PERFLAB_RUNNAME"), + Queue = environment.GetEnvironmentVariable("PERFLAB_QUEUE"), + }; + Boolean.TryParse(environment.GetEnvironmentVariable("PERFLAB_HIDDEN"), out bool hidden); + run.Hidden = hidden; + var configs = environment.GetEnvironmentVariable("PERFLAB_CONFIGS"); + if (!String.IsNullOrEmpty(configs)) // configs should be optional. + { + foreach (var kvp in configs.Split(';')) + { + var split = kvp.Split('='); + run.Configurations.Add(split[0], split[1]); + } + } + + os = new Os() + { + Name = $"{RuntimeEnvironment.OperatingSystem} {RuntimeEnvironment.OperatingSystemVersion}", + Architecture = RuntimeInformation.OSArchitecture.ToString(), + Locale = CultureInfo.CurrentUICulture.ToString() + }; + + build = new Build + { + Repo = environment.GetEnvironmentVariable("PERFLAB_REPO"), + Branch = environment.GetEnvironmentVariable("PERFLAB_BRANCH"), + Architecture = environment.GetEnvironmentVariable("PERFLAB_BUILDARCH"), + Locale = environment.GetEnvironmentVariable("PERFLAB_LOCALE"), + GitHash = environment.GetEnvironmentVariable("PERFLAB_HASH"), + BuildName = environment.GetEnvironmentVariable("PERFLAB_BUILDNUM"), + TimeStamp = DateTime.Parse(environment.GetEnvironmentVariable("PERFLAB_BUILDTIMESTAMP")), + }; + build.AdditionalData["productVersion"] = environment.GetEnvironmentVariable("DOTNET_VERSION"); + } + public string GetJson() + { + if (!InLab) + { + return null; + } + var jsonobj = new + { + build, + os, + run, + tests + }; + var settings = new JsonSerializerSettings(); + var resolver = new DefaultContractResolver(); + resolver.NamingStrategy = new CamelCaseNamingStrategy() { ProcessDictionaryKeys = false }; + settings.ContractResolver = resolver; + return JsonConvert.SerializeObject(jsonobj, Formatting.Indented, settings); + } + + public string WriteResultTable() + { + StringBuilder ret = new StringBuilder(); + foreach (var test in tests) + { + var defaultCounter = test.Counters.Single(c => c.DefaultCounter); + var topCounters = test.Counters.Where(c => c.TopCounter && !c.DefaultCounter); + var restCounters = test.Counters.Where(c => !(c.TopCounter || c.DefaultCounter)); + var counterWidth = Math.Max(test.Counters.Max(c => c.Name.Length) + 1, 15); + var resultWidth = Math.Max(test.Counters.Max(c => c.Results.Max().ToString("F3").Length + c.MetricName.Length) + 2, 15); + ret.AppendLine(test.Name); + 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) + { + ret.AppendLine(Print(counter, counterWidth, resultWidth)); + } + foreach (var counter in restCounters) + { + ret.AppendLine(Print(counter, counterWidth, resultWidth)); + } + } + return ret.ToString(); + } + private string Print(Counter counter, int counterWidth, int resultWidth) + { + string average = $"{counter.Results.Average():F3} {counter.MetricName}"; + string max = $"{counter.Results.Max():F3} {counter.MetricName}"; + string min = $"{counter.Results.Min():F3} {counter.MetricName}"; + return $"{LeftJustify(counter.Name, counterWidth)}|{LeftJustify(average, resultWidth)}|{LeftJustify(min, resultWidth)}|{LeftJustify(max, resultWidth)}"; + } + + private string LeftJustify(string str, int width) + { + return String.Format("{0,-" + width + "}", str); + } + + public bool InLab => environment.GetEnvironmentVariable("PERFLAB_INLAB")?.Equals("1") ?? false; + } +} diff --git a/test/perf/dotnet-tools/Reporting/Reporting.csproj b/test/perf/dotnet-tools/Reporting/Reporting.csproj new file mode 100644 index 00000000000..72a831b9406 --- /dev/null +++ b/test/perf/dotnet-tools/Reporting/Reporting.csproj @@ -0,0 +1,13 @@ + + + + Library + netstandard2.0 + + + + + + + + diff --git a/test/perf/dotnet-tools/Reporting/Run.cs b/test/perf/dotnet-tools/Reporting/Run.cs new file mode 100644 index 00000000000..d39d30e5801 --- /dev/null +++ b/test/perf/dotnet-tools/Reporting/Run.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Reporting +{ + public class Run + { + public bool Hidden { get; set; } + + public string CorrelationId { get; set; } + + public string PerfRepoHash { get; set; } + + public string Name { get; set; } + + public string Queue { get; set; } + public IDictionary Configurations { get; set; } = new Dictionary(); + } +} diff --git a/test/perf/dotnet-tools/Reporting/Test.cs b/test/perf/dotnet-tools/Reporting/Test.cs new file mode 100644 index 00000000000..e22529de2b1 --- /dev/null +++ b/test/perf/dotnet-tools/Reporting/Test.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Reporting +{ + public class Test + { + public IList Categories { get; set; } = new List(); + + public string Name { get; set; } + public Dictionary AdditionalData { get; set; } = new Dictionary(); + + public IList Counters { get; set; } = new List(); + + public void AddCounter(Counter counter) + { + if (counter.DefaultCounter && Counters.Any(c => c.DefaultCounter)) + { + throw new Exception($"Duplicate default counter, name: ${counter.Name}"); + } + + if (Counters.Any(c => c.Name.Equals(counter.Name))) + { + throw new Exception($"Duplicate counter name, name: ${counter.Name}"); + } + + Counters.Add(counter); + } + + public void AddCounter(IEnumerable counters) + { + foreach (var counter in counters) + AddCounter(counter); + } + } +} diff --git a/test/perf/dotnet-tools/ResultsComparer/CommandLineOptions.cs b/test/perf/dotnet-tools/ResultsComparer/CommandLineOptions.cs new file mode 100644 index 00000000000..d3ad01be95b --- /dev/null +++ b/test/perf/dotnet-tools/ResultsComparer/CommandLineOptions.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.IO; +using CommandLine; +using CommandLine.Text; + +namespace ResultsComparer +{ + public class CommandLineOptions + { + [Option("base", HelpText = "Path to the folder/file with base results.")] + public string BasePath { get; set; } + + [Option("diff", HelpText = "Path to the folder/file with diff results.")] + public string DiffPath { get; set; } + + [Option("threshold", Required = true, HelpText = "Threshold for Statistical Test. Examples: 5%, 10ms, 100ns, 1s.")] + public string StatisticalTestThreshold { get; set; } + + [Option("noise", HelpText = "Noise threshold for Statistical Test. The difference for 1.0ns and 1.1ns is 10%, but it's just a noise. Examples: 0.5ns 1ns.", Default = "0.3ns" )] + public string NoiseThreshold { get; set; } + + [Option("top", HelpText = "Filter the diff to top/bottom N results. Optional.")] + public int? TopCount { get; set; } + + [Option("csv", HelpText = "Path to exported CSV results. Optional.")] + public FileInfo CsvPath { get; set; } + + [Option("xml", HelpText = "Path to exported XML results. Optional.")] + public FileInfo XmlPath { get; set; } + + [Option('f', "filter", HelpText = "Filter the benchmarks by name using glob pattern(s). Optional.")] + public IEnumerable Filters { get; set; } + + [Usage(ApplicationAlias = "")] + public static IEnumerable Examples + { + get + { + yield return new Example(@"Compare the results stored in 'C:\results\win' (base) vs 'C:\results\unix' (diff) using 5% threshold.", + new CommandLineOptions { BasePath = @"C:\results\win", DiffPath = @"C:\results\unix", StatisticalTestThreshold = "5%" }); + yield return new Example(@"Compare the results stored in 'C:\results\win' (base) vs 'C:\results\unix' (diff) using 5% threshold and show only top/bottom 10 results.", + new CommandLineOptions { BasePath = @"C:\results\win", DiffPath = @"C:\results\unix", StatisticalTestThreshold = "5%", TopCount = 10 }); + yield return new Example(@"Compare the results stored in 'C:\results\win' (base) vs 'C:\results\unix' (diff) using 5% threshold and 0.5ns noise filter.", + new CommandLineOptions { BasePath = @"C:\results\win", DiffPath = @"C:\results\unix", StatisticalTestThreshold = "5%", NoiseThreshold = "0.5ns" }); + yield return new Example(@"Compare the System.Math benchmark results stored in 'C:\results\ubuntu16' (base) vs 'C:\results\ubuntu18' (diff) using 5% threshold.", + new CommandLineOptions { Filters = new[] { "System.Math*" }, BasePath = @"C:\results\win", DiffPath = @"C:\results\unix", StatisticalTestThreshold = "5%" }); + } + } + } +} \ No newline at end of file diff --git a/test/perf/dotnet-tools/ResultsComparer/DataTransferContracts.cs b/test/perf/dotnet-tools/ResultsComparer/DataTransferContracts.cs new file mode 100644 index 00000000000..c3399ea4333 --- /dev/null +++ b/test/perf/dotnet-tools/ResultsComparer/DataTransferContracts.cs @@ -0,0 +1,133 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// + +using System.Collections.Generic; +using System.Linq; + +namespace DataTransferContracts // generated with http://json2csharp.com/# +{ + public class ChronometerFrequency + { + public int Hertz { get; set; } + } + + public class HostEnvironmentInfo + { + public string BenchmarkDotNetCaption { get; set; } + public string BenchmarkDotNetVersion { get; set; } + public string OsVersion { get; set; } + public string ProcessorName { get; set; } + public int? PhysicalProcessorCount { get; set; } + public int? PhysicalCoreCount { get; set; } + public int? LogicalCoreCount { get; set; } + public string RuntimeVersion { get; set; } + public string Architecture { get; set; } + public bool? HasAttachedDebugger { get; set; } + public bool? HasRyuJit { get; set; } + public string Configuration { get; set; } + public string JitModules { get; set; } + public string DotNetCliVersion { get; set; } + public ChronometerFrequency ChronometerFrequency { get; set; } + public string HardwareTimerKind { get; set; } + } + + public class ConfidenceInterval + { + public int N { get; set; } + public double Mean { get; set; } + public double StandardError { get; set; } + public int Level { get; set; } + public double Margin { get; set; } + public double Lower { get; set; } + public double Upper { get; set; } + } + + public class Percentiles + { + public double P0 { get; set; } + public double P25 { get; set; } + public double P50 { get; set; } + public double P67 { get; set; } + public double P80 { get; set; } + public double P85 { get; set; } + public double P90 { get; set; } + public double P95 { get; set; } + public double P100 { get; set; } + } + + public class Statistics + { + public int N { get; set; } + public double Min { get; set; } + public double LowerFence { get; set; } + public double Q1 { get; set; } + public double Median { get; set; } + public double Mean { get; set; } + public double Q3 { get; set; } + public double UpperFence { get; set; } + public double Max { get; set; } + public double InterquartileRange { get; set; } + public List LowerOutliers { get; set; } + public List UpperOutliers { get; set; } + public List AllOutliers { get; set; } + public double StandardError { get; set; } + public double Variance { get; set; } + public double StandardDeviation { get; set; } + public double Skewness { get; set; } + public double Kurtosis { get; set; } + public ConfidenceInterval ConfidenceInterval { get; set; } + public Percentiles Percentiles { get; set; } + } + + public class Memory + { + public int Gen0Collections { get; set; } + public int Gen1Collections { get; set; } + public int Gen2Collections { get; set; } + public long TotalOperations { get; set; } + public long BytesAllocatedPerOperation { get; set; } + } + + public class Measurement + { + public string IterationStage { get; set; } + public int LaunchIndex { get; set; } + public int IterationIndex { get; set; } + public long Operations { get; set; } + public double Nanoseconds { get; set; } + } + + public class Benchmark + { + public string DisplayInfo { get; set; } + public object Namespace { get; set; } + public string Type { get; set; } + public string Method { get; set; } + public string MethodTitle { get; set; } + public string Parameters { get; set; } + public string FullName { get; set; } + public Statistics Statistics { get; set; } + public Memory Memory { get; set; } + public List Measurements { get; set; } + + /// + /// this method was not auto-generated by a tool, it was added manually + /// + /// an array of the actual workload results (not warmup, not pilot) + internal double[] GetOriginalValues() + => Measurements + .Where(measurement => measurement.IterationStage == "Result") + .Select(measurement => measurement.Nanoseconds / measurement.Operations) + .ToArray(); + } + + public class BdnResult + { + public string Title { get; set; } + public HostEnvironmentInfo HostEnvironmentInfo { get; set; } + public List 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 new file mode 100644 index 00000000000..68615f61c78 --- /dev/null +++ b/test/perf/dotnet-tools/ResultsComparer/Program.cs @@ -0,0 +1,290 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Xml; +using Perfolizer.Mathematics.Multimodality; +using Perfolizer.Mathematics.SignificanceTesting; +using Perfolizer.Mathematics.Thresholds; +using CommandLine; +using DataTransferContracts; +using MarkdownLog; +using Newtonsoft.Json; + +namespace ResultsComparer +{ + public sealed class Program + { + private const string FullBdnJsonFileExtension = "full.json"; + + public static void Main(string[] args) + { + // we print a lot of numbers here and we want to make it always in invariant way + Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + + Parser.Default.ParseArguments(args).WithParsed(Compare); + } + + private static void Compare(CommandLineOptions args) + { + if (!Threshold.TryParse(args.StatisticalTestThreshold, out var testThreshold)) + { + Console.WriteLine($"Invalid Threshold {args.StatisticalTestThreshold}. Examples: 5%, 10ms, 100ns, 1s."); + return; + } + if (!Threshold.TryParse(args.NoiseThreshold, out var noiseThreshold)) + { + Console.WriteLine($"Invalid Noise Threshold {args.NoiseThreshold}. Examples: 0.3ns 1ns."); + return; + } + + var notSame = GetNotSameResults(args, testThreshold, noiseThreshold).ToArray(); + + if (!notSame.Any()) + { + Console.WriteLine($"No differences found between the benchmark results with threshold {testThreshold}."); + return; + } + + PrintSummary(notSame); + + PrintTable(notSame, EquivalenceTestConclusion.Slower, args); + PrintTable(notSame, EquivalenceTestConclusion.Faster, args); + + ExportToCsv(notSame, args.CsvPath); + ExportToXml(notSame, args.XmlPath); + } + + private static IEnumerable<(string id, Benchmark baseResult, Benchmark diffResult, EquivalenceTestConclusion conclusion)> GetNotSameResults(CommandLineOptions args, Threshold testThreshold, Threshold noiseThreshold) + { + foreach ((string id, Benchmark baseResult, Benchmark diffResult) in ReadResults(args) + .Where(result => result.baseResult.Statistics != null && result.diffResult.Statistics != null)) // failures + { + var baseValues = baseResult.GetOriginalValues(); + var diffValues = diffResult.GetOriginalValues(); + + var userTresholdResult = StatisticalTestHelper.CalculateTost(MannWhitneyTest.Instance, baseValues, diffValues, testThreshold); + if (userTresholdResult.Conclusion == EquivalenceTestConclusion.Same) + continue; + + var noiseResult = StatisticalTestHelper.CalculateTost(MannWhitneyTest.Instance, baseValues, diffValues, noiseThreshold); + if (noiseResult.Conclusion == EquivalenceTestConclusion.Same) + continue; + + yield return (id, baseResult, diffResult, userTresholdResult.Conclusion); + } + } + + private static void PrintSummary((string id, Benchmark baseResult, Benchmark diffResult, EquivalenceTestConclusion conclusion)[] notSame) + { + var better = notSame.Where(result => result.conclusion == EquivalenceTestConclusion.Faster); + var worse = notSame.Where(result => result.conclusion == EquivalenceTestConclusion.Slower); + var betterCount = better.Count(); + var worseCount = worse.Count(); + + // If the baseline doesn't have the same set of tests, you wind up with Infinity in the list of diffs. + // Exclude them for purposes of geomean. + worse = worse.Where(x => GetRatio(x) != double.PositiveInfinity); + better = better.Where(x => GetRatio(x) != double.PositiveInfinity); + + Console.WriteLine("summary:"); + + if (betterCount > 0) + { + var betterGeoMean = Math.Pow(10, better.Skip(1).Aggregate(Math.Log10(GetRatio(better.First())), (x, y) => x + Math.Log10(GetRatio(y))) / better.Count()); + Console.WriteLine($"better: {betterCount}, geomean: {betterGeoMean:F3}"); + } + + if (worseCount > 0) + { + var worseGeoMean = Math.Pow(10, worse.Skip(1).Aggregate(Math.Log10(GetRatio(worse.First())), (x, y) => x + Math.Log10(GetRatio(y))) / worse.Count()); + Console.WriteLine($"worse: {worseCount}, geomean: {worseGeoMean:F3}"); + } + + Console.WriteLine($"total diff: {notSame.Length}"); + Console.WriteLine(); + } + + private static void PrintTable((string id, Benchmark baseResult, Benchmark diffResult, EquivalenceTestConclusion conclusion)[] notSame, EquivalenceTestConclusion conclusion, CommandLineOptions args) + { + var data = notSame + .Where(result => result.conclusion == conclusion) + .OrderByDescending(result => GetRatio(conclusion, result.baseResult, result.diffResult)) + .Take(args.TopCount ?? int.MaxValue) + .Select(result => new + { + Id = result.id.Length > 80 ? result.id.Substring(0, 80) : result.id, + DisplayValue = GetRatio(conclusion, result.baseResult, result.diffResult), + BaseMedian = result.baseResult.Statistics.Median, + DiffMedian = result.diffResult.Statistics.Median, + Modality = GetModalInfo(result.baseResult) ?? GetModalInfo(result.diffResult) + }) + .ToArray(); + + if (!data.Any()) + { + Console.WriteLine($"No {conclusion} results for the provided threshold = {args.StatisticalTestThreshold} and noise filter = {args.NoiseThreshold}."); + Console.WriteLine(); + return; + } + + var table = data.ToMarkdownTable().WithHeaders(conclusion.ToString(), conclusion == EquivalenceTestConclusion.Faster ? "base/diff" : "diff/base", "Base Median (ns)", "Diff Median (ns)", "Modality"); + + foreach (var line in table.ToMarkdown().Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries)) + Console.WriteLine($"| {line.TrimStart()}|"); // the table starts with \t and does not end with '|' and it looks bad so we fix it + + Console.WriteLine(); + } + + private static IEnumerable<(string id, Benchmark baseResult, Benchmark diffResult)> ReadResults(CommandLineOptions args) + { + var baseFiles = GetFilesToParse(args.BasePath); + var diffFiles = GetFilesToParse(args.DiffPath); + + if (!baseFiles.Any() || !diffFiles.Any()) + throw new ArgumentException($"Provided paths contained no {FullBdnJsonFileExtension} files."); + + var baseResults = baseFiles.Select(ReadFromFile); + var diffResults = diffFiles.Select(ReadFromFile); + + var filters = args.Filters.Select(pattern => new Regex(WildcardToRegex(pattern), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)).ToArray(); + + var benchmarkIdToDiffResults = diffResults + .SelectMany(result => result.Benchmarks) + .Where(benchmarkResult => !filters.Any() || filters.Any(filter => filter.IsMatch(benchmarkResult.FullName))) + .ToDictionary(benchmarkResult => benchmarkResult.FullName, benchmarkResult => benchmarkResult); + + return baseResults + .SelectMany(result => result.Benchmarks) + .ToDictionary(benchmarkResult => benchmarkResult.FullName, benchmarkResult => benchmarkResult) // we use ToDictionary to make sure the results have unique IDs + .Where(baseResult => benchmarkIdToDiffResults.ContainsKey(baseResult.Key)) + .Select(baseResult => (baseResult.Key, baseResult.Value, benchmarkIdToDiffResults[baseResult.Key])); + } + + private static void ExportToCsv((string id, Benchmark baseResult, Benchmark diffResult, EquivalenceTestConclusion conclusion)[] notSame, FileInfo csvPath) + { + if (csvPath == null) + return; + + if (csvPath.Exists) + csvPath.Delete(); + + using (var textWriter = csvPath.CreateText()) + { + foreach (var (id, baseResult, diffResult, conclusion) in notSame) + { + textWriter.WriteLine($"\"{id.Replace("\"", "\"\"")}\";base;{conclusion};{string.Join(';', baseResult.GetOriginalValues())}"); + textWriter.WriteLine($"\"{id.Replace("\"", "\"\"")}\";diff;{conclusion};{string.Join(';', diffResult.GetOriginalValues())}"); + } + } + + Console.WriteLine($"CSV results exported to {csvPath.FullName}"); + } + + private static void ExportToXml((string id, Benchmark baseResult, Benchmark diffResult, EquivalenceTestConclusion conclusion)[] notSame, FileInfo xmlPath) + { + if (xmlPath == null) + { + Console.WriteLine("No file given"); + return; + } + + if (xmlPath.Exists) + xmlPath.Delete(); + + using (XmlWriter writer = XmlWriter.Create(xmlPath.Open(FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write))) + { + writer.WriteStartElement("performance-tests"); + foreach (var (id, baseResult, diffResult, conclusion) in notSame.Where(x => x.conclusion == EquivalenceTestConclusion.Slower)) + { + writer.WriteStartElement("test"); + writer.WriteAttributeString("name", id); + writer.WriteAttributeString("type", baseResult.Type); + writer.WriteAttributeString("method", baseResult.Method); + writer.WriteAttributeString("time", "0"); + writer.WriteAttributeString("result", "Fail"); + writer.WriteStartElement("failure"); + writer.WriteAttributeString("exception-type", "Regression"); + writer.WriteElementString("message", $"{id} has regressed, was {baseResult.Statistics.Median} is {diffResult.Statistics.Median}."); + writer.WriteEndElement(); + } + + foreach (var (id, baseResult, diffResult, conclusion) in notSame.Where(x => x.conclusion == EquivalenceTestConclusion.Faster)) + { + writer.WriteStartElement("test"); + writer.WriteAttributeString("name", id); + writer.WriteAttributeString("type", baseResult.Type); + writer.WriteAttributeString("method", baseResult.Method); + writer.WriteAttributeString("time", "0"); + writer.WriteAttributeString("result", "Skip"); + writer.WriteElementString("reason", $"{id} has improved, was {baseResult.Statistics.Median} is {diffResult.Statistics.Median}."); + writer.WriteEndElement(); + } + + writer.WriteEndElement(); + writer.Flush(); + } + + Console.WriteLine($"XML results exported to {xmlPath.FullName}"); + } + + private static string[] GetFilesToParse(string path) + { + if (Directory.Exists(path)) + return Directory.GetFiles(path, $"*{FullBdnJsonFileExtension}", SearchOption.AllDirectories); + else if (File.Exists(path) || !path.EndsWith(FullBdnJsonFileExtension)) + return new[] { path }; + else + throw new FileNotFoundException($"Provided path does NOT exist or is not a {path} file", path); + } + + // code and magic values taken from BenchmarkDotNet.Analysers.MultimodalDistributionAnalyzer + // See http://www.brendangregg.com/FrequencyTrails/modes.html + private static string GetModalInfo(Benchmark benchmark) + { + if (benchmark.Statistics.N < 12) // not enough data to tell + return null; + + double mValue = MValueCalculator.Calculate(benchmark.GetOriginalValues()); + if (mValue > 4.2) + return "multimodal"; + else if (mValue > 3.2) + return "bimodal"; + else if (mValue > 2.8) + return "several?"; + + return null; + } + + private static double GetRatio((string id, Benchmark baseResult, Benchmark diffResult, EquivalenceTestConclusion conclusion) item) => GetRatio(item.conclusion, item.baseResult, item.diffResult); + + private static double GetRatio(EquivalenceTestConclusion conclusion, Benchmark baseResult, Benchmark diffResult) + => conclusion == EquivalenceTestConclusion.Faster + ? baseResult.Statistics.Median / diffResult.Statistics.Median + : diffResult.Statistics.Median / baseResult.Statistics.Median; + + private static BdnResult ReadFromFile(string resultFilePath) + { + try + { + return JsonConvert.DeserializeObject(File.ReadAllText(resultFilePath)); + } + catch (JsonSerializationException) + { + Console.WriteLine($"Exception while reading the {resultFilePath} file."); + + throw; + } + } + + // https://stackoverflow.com/a/6907849/5852046 not perfect but should work for all we need + private static string WildcardToRegex(string pattern) => $"^{Regex.Escape(pattern).Replace(@"\*", ".*").Replace(@"\?", ".")}$"; + } +} diff --git a/test/perf/dotnet-tools/ResultsComparer/README.md b/test/perf/dotnet-tools/ResultsComparer/README.md new file mode 100644 index 00000000000..109ba901422 --- /dev/null +++ b/test/perf/dotnet-tools/ResultsComparer/README.md @@ -0,0 +1,41 @@ +# Results Comparer + +This simple tool allows for easy comparison of provided benchmark results. + +It can be used to compare: +* historical results (eg. before and after my changes) +* results for different OSes (eg. Windows vs Ubuntu) +* results for different CPU architectures (eg. x64 vs ARM64) +* results for different target frameworks (eg. .NET Core 3.1 vs 5.0) + +All you need to provide is: +* `--base` - path to folder/file with baseline results +* `--diff` - path to folder/file with diff results +* `--threshold` - threshold for Statistical Test. Examples: 5%, 10ms, 100ns, 1s + +Optional arguments: +* `--top` - filter the diff to top/bottom `N` results +* `--noise` - noise threshold for Statistical Test. The difference for 1.0ns and 1.1ns is 10%, but it's just a noise. Examples: 0.5ns 1ns. The default value is 0.3ns. +* `--csv` - path to exported CSV results. Optional. +* `-f|--filter` - filter the benchmarks by name using glob pattern(s). Optional. + +Sample: compare the results stored in `C:\results\windows` vs `C:\results\ubuntu` using `1%` threshold and print only TOP 10. + +```cmd +dotnet run --base "C:\results\windows" --diff "C:\results\ubuntu" --threshold 1% --top 10 +``` + +**Note**: the tool supports only `*full.json` results exported by BenchmarkDotNet. This exporter is enabled by default in this repository. + +## Sample results + +| Slower | diff/base | Base Median (ns) | Diff Median (ns) | Modality| +| --------------------------------------------------------------- | ---------:| ----------------:| ----------------:| -------:| +| PerfLabTests.BlockCopyPerf.CallBlockCopy(numElements: 100) | 1.60 | 9.22 | 14.76 | | +| System.Tests.Perf_String.Trim_CharArr(s: "Test", c: [' ', ' ']) | 1.41 | 6.18 | 8.72 | | + +| Faster | base/diff | Base Median (ns) | Diff Median (ns) | Modality| +| ----------------------------------- | ---------:| ----------------:| ----------------:| -------:| +| System.Tests.Perf_Array.ArrayCopy3D | 1.31 | 372.71 | 284.73 | | + +If there is no difference or if there is no match (we use full benchmark names to match the benchmarks), then the results are omitted. diff --git a/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj new file mode 100644 index 00000000000..ffb6075eefb --- /dev/null +++ b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj @@ -0,0 +1,15 @@ + + + Exe + $(PERFLAB_TARGET_FRAMEWORKS) + net5.0 + 13.0 + + + + + + + + + diff --git a/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.sln b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.sln new file mode 100644 index 00000000000..951a4d0fb5d --- /dev/null +++ b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResultsComparer", "ResultsComparer.csproj", "{00859394-44F8-466B-8624-41578CA94009}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {00859394-44F8-466B-8624-41578CA94009}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {00859394-44F8-466B-8624-41578CA94009}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00859394-44F8-466B-8624-41578CA94009}.Release|Any CPU.ActiveCfg = Release|Any CPU + {00859394-44F8-466B-8624-41578CA94009}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/test/perf/perf.psm1 b/test/perf/perf.psm1 new file mode 100644 index 00000000000..5b1ab239160 --- /dev/null +++ b/test/perf/perf.psm1 @@ -0,0 +1,207 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +$repoRoot = git rev-parse --show-toplevel +Import-Module "$repoRoot/build.psm1" + +function Start-Benchmarking +{ + <# + .SYNOPSIS + Start a benchmark run. + + .PARAMETER TargetPSVersion + The version of 'Microsoft.PowerShell.SDK' package that we want the benchmark to target. + The supported versions are 7.0.x and above, including preview versions. + + .PARAMETER TargetFramework + The target framework to run benchmarks against. + + .PARAMETER List + List the available benchmarks, in either 'flat' or 'tree' views. + + .PARAMETER Runtime + Run benchmarks against multiple .NET runtimes. + + .PARAMETER Filter + One or more wildcard patterns to filter the benchmarks to be executed or to be listed. + + .PARAMETER Artifacts + Path to the folder where you want to store the artifacts produced from running benchmarks. + + .PARAMETER KeepFiles + Indicates to keep all temporary files produced for running benchmarks. + #> + [CmdletBinding(DefaultParameterSetName = 'TargetFramework')] + param( + [Parameter(ParameterSetName = 'TargetPSVersion')] + [ValidatePattern( + '^7\.(0|1|2)\.\d+(-preview\.\d{1,2})?$', + ErrorMessage = 'The package version is invalid or not supported')] + [string] $TargetPSVersion, + + [Parameter(ParameterSetName = 'TargetFramework')] + [ValidateSet('netcoreapp3.1', 'net5.0', 'net6.0')] + [string] $TargetFramework = 'net6.0', + + [Parameter(ParameterSetName = 'TargetFramework')] + [ValidateSet('flat', 'tree')] + [string] $List, + + [Parameter(Mandatory, ParameterSetName = 'Runtimes')] + [ValidateSet('netcoreapp3.1', 'net5.0', 'net6.0')] + [string[]] $Runtime, + + [string[]] $Filter = '*', + [string] $Artifacts, + [switch] $KeepFiles + ) + + Begin { + Find-Dotnet + + if ($Artifacts) { + $Artifacts = $PSCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Artifacts) + } else { + $Artifacts = Join-Path $PSScriptRoot 'BenchmarkDotNet.Artifacts' + } + + if (Test-Path -Path $Artifacts) { + Remove-Item -Path $Artifacts -Recurse -Force -ErrorAction Stop + } + + if ($Runtime) { + ## Remove duplicate values. + $hash = [ordered]@{} + foreach ($item in $Runtime) { + if (-not $hash.Contains($item)) { + $hash.Add($item, $null) + } + } + $Runtime = $hash.Keys + } + } + + End { + try { + Push-Location -Path "$PSScriptRoot/benchmarks" + $savedOFS = $OFS; $OFS = $null + + ## Aggregate BDN arguments. + $runArgs = @('--filter') + foreach ($entry in $Filter) { $runArgs += $entry } + $runArgs += '--artifacts', $Artifacts + $runArgs += '--envVars', 'POWERSHELL_TELEMETRY_OPTOUT:1' + + if ($List) { $runArgs += '--list', $List } + if ($KeepFiles) { $runArgs += "--keepFiles" } + + switch ($PSCmdlet.ParameterSetName) { + 'TargetPSVersion' { + Write-Log -message "Run benchmarks targeting '$TargetFramework' and the 'Microsoft.PowerShell.SDK' version '$TargetPSVersion' ..." + $env:PERF_TARGET_VERSION = $TargetPSVersion + + ## Use 'Release' instead of 'release' (note the capital case) because BDN uses 'Release' when building the auto-generated + ## project, and MSBuild somehow recognizes 'release' and 'Release' as two different configurations and thus will rebuild + ## all dependencies unnecessarily. + dotnet run -c Release -f $TargetFramework $runArgs + } + + 'TargetFramework' { + $message = if ($TargetFramework -eq 'net6.0') { 'the current PowerShell code base ...' } else { "the corresponding latest version of 'Microsoft.PowerShell.SDK' ..." } + Write-Log -message "Run benchmarks targeting '$TargetFramework' and $message" + + ## Use 'Release' instead of 'release' (note the capital case) because BDN uses 'Release' when building the auto-generated + ## project, and MSBuild somehow recognizes 'release' and 'Release' as two different configurations and thus will rebuild + ## all dependencies unnecessarily. + dotnet run -c Release -f $TargetFramework $runArgs + } + + 'Runtimes' { + Write-Log -message "Run benchmarks targeting multiple .NET runtimes: $Runtime ..." + + ## Use 'Release' instead of 'release' (note the capital case) because BDN uses 'Release' when building the auto-generated + ## project, and MSBuild somehow recognizes 'release' and 'Release' as two different configurations and thus will rebuild + ## all dependencies unnecessarily. + dotnet run -c Release -f net6.0 --runtimes $Runtime $runArgs + } + } + + if (Test-Path $Artifacts) { + Write-Log -message "`nBenchmark artifacts can be found at $Artifacts" + } + } + finally { + $OFS = $savedOFS + $env:PERF_TARGET_VERSION = $null + Pop-Location + } + } +} + +function Compare-BenchmarkResult +{ + <# + .SYNOPSIS + Compare two benchmark run results to find possible regressions. + + When running benchmarks with 'Start-Benchmarking', you can define the result folder + where to save the artifacts by specifying '-Artifacts'. + + To compare two benchmark run results, you need to specify the result folder paths + for both runs, one as the base and one as the diff. + + .PARAMETER BaseResultPath + Path to the benchmark result used as baseline. + + .PARAMETER DiffResultPath + Path to the benchmark result to be compared with the baseline. + + .PARAMETER Threshold + Threshold for Statistical Test. Examples: 5%, 10ms, 100ns, 1s + + .PARAMETER Noise + Noise threshold for Statistical Test. + The difference for 1.0ns and 1.1ns is 10%, but it's really just noise. Examples: 0.5ns 1ns. + The default value is 0.3ns. + + .PARAMETER Top + Filter the diff to top `N` results + #> + param( + [Parameter(Mandatory)] + [string] $BaseResultPath, + + [Parameter(Mandatory)] + [string] $DiffResultPath, + + [Parameter(Mandatory)] + [ValidatePattern('^\d{1,2}%$|^\d+(ms|ns|s)$')] + [string] $Threshold, + + [ValidatePattern('^(\d\.)?\d+(ms|ns|s)$')] + [string] $Noise, + + [ValidateRange(1, 100)] + [int] $Top + ) + + Find-Dotnet + + try { + Push-Location -Path "$PSScriptRoot/dotnet-tools/ResultsComparer" + $savedOFS = $OFS; $OFS = $null + + $runArgs = @() + if ($Noise) { $runArgs += "--noise $Noise" } + if ($Top -gt 0) { $runArgs += "--top $Top" } + + dotnet run -c Release --base $BaseResultPath --diff $DiffResultPath --threshold $Threshold "$runArgs" + } + finally { + $OFS = $savedOFS + Pop-Location + } +} + +Export-ModuleMember -Function 'Start-Benchmarking', 'Compare-BenchmarkResult' diff --git a/test/powershell/Host/Base-Directory.Tests.ps1 b/test/powershell/Host/Base-Directory.Tests.ps1 index a55af971f09..203d214e937 100644 --- a/test/powershell/Host/Base-Directory.Tests.ps1 +++ b/test/powershell/Host/Base-Directory.Tests.ps1 @@ -44,7 +44,7 @@ Describe "Configuration file locations" -tags "CI","Slow" { } It @ItArgs "PSModulePath should contain the correct path" { - $env:PSModulePath = "" + $env:PSModulePath = $null $actual = & $powershell -noprofile -c `$env:PSModulePath $actual | Should -Match ([regex]::Escape($expectedModule)) } @@ -94,7 +94,7 @@ Describe "Configuration file locations" -tags "CI","Slow" { } It @ItArgs "PSModulePath should respect XDG_DATA_HOME" { - $env:PSModulePath = "" + $env:PSModulePath = $null $env:XDG_DATA_HOME = $TestDrive $expected = [IO.Path]::Combine($TestDrive, "powershell", "Modules") $actual = & $powershell -noprofile -c `$env:PSModulePath diff --git a/test/powershell/Host/ConsoleHost.Tests.ps1 b/test/powershell/Host/ConsoleHost.Tests.ps1 index 390b461fb90..ec0e80de5c5 100644 --- a/test/powershell/Host/ConsoleHost.Tests.ps1 +++ b/test/powershell/Host/ConsoleHost.Tests.ps1 @@ -24,6 +24,7 @@ Describe 'minishell for native executables' -Tag 'CI' { } It 'gets the error stream from minishell' { + $PSNativeCommandUseErrorActionPreference = $false $output = & $powershell -noprofile { Write-Error 'foo' } 2>&1 ($output | Measure-Object).Count | Should -Be 1 $output | Should -BeOfType System.Management.Automation.ErrorRecord @@ -92,6 +93,10 @@ Describe "ConsoleHost unit tests" -tags "Feature" { } It "Clear-Host does not injects data into PowerShell output stream" { + if (Test-IsWindowsArm64) { + Set-ItResult -Pending -Because "ARM64 runs in non-interactively mode and Clear-Host does not work." + } + & { Clear-Host; 'hi' } | Should -BeExactly 'hi' } @@ -147,21 +152,32 @@ Describe "ConsoleHost unit tests" -tags "Feature" { } It "-File should be default parameter" { - Set-Content -Path $testdrive/test -Value "'hello'" - $observed = & $powershell -NoProfile $testdrive/test + Set-Content -Path $testdrive/test.ps1 -Value "'hello'" + $observed = & $powershell -NoProfile $testdrive/test.ps1 $observed | Should -Be "hello" } - It "-File accepts scripts with and without .ps1 extension: " -TestCases @( - @{Filename="test.ps1"}, - @{Filename="test"} - ) { - param($Filename) + It "-File accepts scripts with .ps1 extension" { + $Filename = 'test.ps1' Set-Content -Path $testdrive/$Filename -Value "'hello'" $observed = & $powershell -NoProfile -File $testdrive/$Filename $observed | Should -Be "hello" } + It "-File accepts scripts without .ps1 extension to support shebang" -Skip:($IsWindows) { + $Filename = 'test.xxx' + Set-Content -Path $testdrive/$Filename -Value "'hello'" + $observed = & $powershell -NoProfile -File $testdrive/$Filename + $observed | Should -Be "hello" + } + + It "-File should fail for script without .ps1 extension" -Skip:(!$IsWindows) { + $Filename = 'test.xxx' + Set-Content -Path $testdrive/$Filename -Value "'hello'" + & $powershell -NoProfile -File $testdrive/$Filename 2>&1 $null + $LASTEXITCODE | Should -Be 64 + } + It "-File should pass additional arguments to script" { Set-Content -Path $testdrive/script.ps1 -Value 'foreach($arg in $args){$arg}' $observed = & $powershell -NoProfile $testdrive/script.ps1 foo bar @@ -208,11 +224,8 @@ Describe "ConsoleHost unit tests" -tags "Feature" { $observed | Should -Be $BoolValue } - It "-File '' should return exit code from script" -TestCases @( - @{Filename = "test.ps1"}, - @{Filename = "test"} - ) { - param($Filename) + It "-File should return exit code from script" { + $Filename = 'test.ps1' Set-Content -Path $testdrive/$Filename -Value 'exit 123' & $powershell $testdrive/$Filename $LASTEXITCODE | Should -Be 123 @@ -235,11 +248,16 @@ Describe "ConsoleHost unit tests" -tags "Feature" { $observed | Should -BeExactly "h-llo" } - It "Empty command should fail" { - & $powershell -noprofile -c '' + It "Missing command should fail" { + & $powershell -noprofile -c $LASTEXITCODE | Should -Be 64 } + It "Empty space command should succeed on non-Windows" -skip:$IsWindows { + & $powershell -noprofile -c '' | Should -BeNullOrEmpty + $LASTEXITCODE | Should -Be 0 + } + It "Whitespace command should succeed" { & $powershell -noprofile -c ' ' | Should -BeNullOrEmpty $LASTEXITCODE | Should -Be 0 @@ -270,7 +288,7 @@ export $envVarName='$guid' } It "Doesn't run the login profile when -Login not used" { - $result = & $powershell -Command "`$env:$envVarName" + $result = & $powershell -noprofile -Command "`$env:$envVarName" $result | Should -BeNullOrEmpty $LASTEXITCODE | Should -Be 0 } @@ -369,7 +387,20 @@ export $envVarName='$guid' } Context "Pipe to/from powershell" { - $p = [PSCustomObject]@{X=10;Y=20} + BeforeAll { + if ($null -ne $PSStyle) { + $outputRendering = $PSStyle.OutputRendering + $PSStyle.OutputRendering = 'plaintext' + } + + $p = [PSCustomObject]@{X=10;Y=20} + } + + AfterAll { + if ($null -ne $PSStyle) { + $PSStyle.OutputRendering = $outputRendering + } + } It "xml input" { $p | & $powershell -noprofile { $input | ForEach-Object {$a = 0} { $a += $_.X + $_.Y } { $a } } | Should -Be 30 @@ -388,20 +419,42 @@ export $envVarName='$guid' It "text output" { # Join (multiple lines) and remove whitespace (we don't care about spacing) to verify we converted to string (by generating a table) - -join (& $powershell -noprofile -outputFormat text { [PSCustomObject]@{X=10;Y=20} }) -replace "\s","" | Should -Be "XY--1020" + -join (& $powershell -noprofile -outputFormat text { $PSStyle.OutputRendering = 'PlainText'; [PSCustomObject]@{X=10;Y=20} }) -replace "\s","" | Should -Be "XY--1020" } It "errors are in text if error is redirected, encoded command, non-interactive, and outputformat specified" { $p = [Diagnostics.Process]::new() $p.StartInfo.FileName = "pwsh" - $encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes('$ErrorView="NormalView";throw "boom"')) + $encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes('throw "boom"')) $p.StartInfo.Arguments = "-EncodedCommand $encoded -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -OutputFormat text" $p.StartInfo.UseShellExecute = $false $p.StartInfo.RedirectStandardError = $true $p.Start() | Out-Null $out = $p.StandardError.ReadToEnd() $out | Should -Not -BeNullOrEmpty - $out.Split([Environment]::NewLine)[0] | Should -BeExactly "boom" + $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 } } @@ -465,7 +518,15 @@ export $envVarName='$guid' } Context "Redirected standard input for 'interactive' use" { - $nl = [Environment]::Newline + BeforeAll { + $nl = [Environment]::Newline + $oldColor = $env:NO_COLOR + $env:NO_COLOR = 1 + } + + AfterAll { + $env:NO_COLOR = $oldColor + } # All of the following tests replace the prompt (either via an initial command or interactively) # so that we can read StandardOutput and reliably know exactly what the prompt is. @@ -560,6 +621,8 @@ foo It "Redirected input w/ nested prompt" -Pending:($IsWindows) { $si = NewProcessStartInfo "-noprofile -noexit -c ""`$function:prompt = { 'PS' + ('>'*(`$NestedPromptLevel+1)) + ' ' }""" -RedirectStdIn $process = RunPowerShell $si + $process.StandardInput.Write("`$PSStyle.OutputRendering='plaintext'`n") + $null = $process.StandardOutput.ReadLine() $process.StandardInput.Write("`$Host.EnterNestedPrompt()`n") $process.StandardOutput.ReadLine() | Should -Be "PS> `$Host.EnterNestedPrompt()" $process.StandardInput.Write("exit`n") @@ -638,6 +701,20 @@ namespace StackTest { It "Should start if HOME is not defined" -Skip:($IsWindows) { bash -c "unset HOME;$powershell -c '1+1'" | Should -BeExactly 2 } + + It "Same user should use the same temporary HOME directory for different sessions" -Skip:($IsWindows) { + $results = bash -c @" +unset HOME; +$powershell -c '[System.Management.Automation.Platform]::SelectProductNameForDirectory([System.Management.Automation.Platform+XDG_Type]::DEFAULT)'; +$powershell -c '[System.Management.Automation.Platform]::SelectProductNameForDirectory([System.Management.Automation.Platform+XDG_Type]::DEFAULT)'; +"@ + $results | Should -HaveCount 2 + $results[0] | Should -BeExactly $results[1] + + $tempHomeName = "pwsh-{0}-98288ff9-5712-4a14-9a11-23693b9cd91a" -f [System.Environment]::UserName + $defaultPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "$tempHomeName/.config/powershell" + $results[0] | Should -BeExactly $defaultPath + } } Context "PATH environment variable" { @@ -646,7 +723,7 @@ namespace StackTest { } It "powershell starts if PATH is not set" -Skip:($IsWindows) { - bash -c "unset PATH;$powershell -c '1+1'" | Should -BeExactly 2 + bash -c "unset PATH;$powershell -nop -c '1+1'" | Should -BeExactly 2 } } @@ -807,6 +884,92 @@ namespace StackTest { $LASTEXITCODE | Should -Be $ExitCodeBadCommandLineParameter } } + + Context "Startup banner text tests" -Tag Slow { + BeforeAll { + $outputPath = "Temp:\StartupBannerTest-Output-${Pid}.txt" + $inputPath = "Temp:\StartupBannerTest-Input.txt" + "exit" > $inputPath + + # Not testing update notification banner text here + $oldPowerShellUpdateCheck = $env:POWERSHELL_UPDATECHECK + $env:POWERSHELL_UPDATECHECK = "Off" + + # Set TERM to "dumb" to avoid DECCKM codes in the output + $oldTERM = $env:TERM + $env:TERM = "dumb" + + $escPwd = [regex]::Escape($pwd) + $expectedPromptPattern = "^PS ${escPwd}> exit`$" + + $spArgs = @{ + FilePath = $powershell + ArgumentList = @("-NoProfile") + RedirectStandardInput = $inputPath + RedirectStandardOutput = $outputPath + WorkingDirectory = $pwd + PassThru = $true + NoNewWindow = $true + UseNewEnvironment = $false + } + } + AfterAll { + $env:TERM = $oldTERM + $env:POWERSHELL_UPDATECHECK = $oldPowerShellUpdateCheck + + Remove-Item $inputPath -Force -ErrorAction Ignore + Remove-Item $outputPath -Force -ErrorAction Ignore + } + BeforeEach { + Remove-Item $outputPath -Force -ErrorAction Ignore + } + It "Displays expected startup banner text by default" { + $process = Start-Process @spArgs + Wait-UntilTrue -sb { $process.HasExited } -TimeoutInMilliseconds 5000 -IntervalInMilliseconds 250 | Should -BeTrue + + $out = @(Get-Content $outputPath) + $out.Count | Should -Be 2 + $out[0] | Should -BeExactly "PowerShell $($PSVersionTable.GitCommitId)" + $out[1] | Should -MatchExactly $expectedPromptPattern + } + It "Displays only the prompt with -NoLogo" { + $spArgs["ArgumentList"] += "-NoLogo" + $process = Start-Process @spArgs + Wait-UntilTrue -sb { $process.HasExited } -TimeoutInMilliseconds 5000 -IntervalInMilliseconds 250 | Should -BeTrue + + $out = @(Get-Content $outputPath) + $out.Count | Should -Be 1 + $out[0] | Should -MatchExactly $expectedPromptPattern + } + } + + Context 'CommandWithArgs tests' { + It 'Should be able to run a pipeline with arguments using ' -TestCases @( + @{ param = '-commandwithargs' } + @{ param = '-cwa' } + ){ + param($param) + $out = pwsh -nologo -noprofile $param '$args | % { "[$_]" }' '$fun' '@times' + $out.Count | Should -Be 2 -Because ($out | Out-String) + $out[0] | Should -BeExactly '[$fun]' + $out[1] | Should -BeExactly '[@times]' + } + + It 'Should be able to handle boolean switch: ' -TestCases @( + @{ param = '-switch:$true'; expected = 'True'} + @{ param = '-switch:$false'; expected = 'False'} + ){ + param($param, $expected) + $out = pwsh -nologo -noprofile -cwa 'param([switch]$switch) $switch.IsPresent' $param + $out | Should -Be $expected + } + } + + It 'Errors for invalid ExecutionPolicy string' { + $out = pwsh -nologo -noprofile -executionpolicy NonExistingExecutionPolicy -c 'exit 0' 2>&1 + $out | Should -Not -BeNullOrEmpty + $LASTEXITCODE | Should -Be $ExitCodeBadCommandLineParameter + } } Describe "WindowStyle argument" -Tag Feature { @@ -865,6 +1028,10 @@ public enum ShowWindowCommands : int ) { param ($WindowStyle) + if (Test-IsWindowsArm64) { + Set-ItResult -Pending -Because "All windows are showing up as hidden or ARM64" + } + try { $ps = Start-Process $powershell -ArgumentList "-WindowStyle $WindowStyle -noexit -interactive" -PassThru $startTime = Get-Date @@ -998,8 +1165,42 @@ Describe 'Pwsh startup and PATH' -Tag CI { } Describe 'Console host name' -Tag CI { - It 'Name is pwsh' -Pending { - # waiting on https://github.com/dotnet/runtime/issues/33673 + It 'Name is pwsh' { (Get-Process -Id $PID).Name | Should -BeExactly 'pwsh' } } + +Describe 'TERM env var' -Tag CI { + BeforeAll { + $oldTERM = $env:TERM + } + + AfterAll { + $env:TERM = $oldTERM + } + + It 'TERM = "dumb"' { + $env:TERM = 'dumb' + pwsh -noprofile -command '$Host.UI.SupportsVirtualTerminal' | Should -BeExactly 'False' + } + + It 'TERM = ""' -TestCases @( + @{ term = "xterm-mono" } + @{ term = "xtermm" } + ) { + param ($term) + + $env:TERM = $term + pwsh -noprofile -command '$PSStyle.OutputRendering' | Should -BeExactly 'PlainText' + } + + It 'NO_COLOR' { + try { + $env:NO_COLOR = 1 + pwsh -noprofile -command '$PSStyle.OutputRendering' | Should -BeExactly 'PlainText' + } + finally { + $env:NO_COLOR = $null + } + } +} diff --git a/test/powershell/Host/HostUtilities.Tests.ps1 b/test/powershell/Host/HostUtilities.Tests.ps1 index a886891080b..e151a2cfc2b 100644 --- a/test/powershell/Host/HostUtilities.Tests.ps1 +++ b/test/powershell/Host/HostUtilities.Tests.ps1 @@ -35,10 +35,13 @@ Describe "InvokeOnRunspace method as nested command" -tags "Feature" { Describe "InvokeOnRunspace method on remote runspace" -tags "Feature","RequireAdminOnWindows" { BeforeAll { + $skipTest = (Test-IsWinWow64) -or !$IsWindows - if ($IsWindows) { - $script:remoteRunspace = New-RemoteRunspace + if ($skipTest) { + return } + + $script:remoteRunspace = New-RemoteRunspace } AfterAll { @@ -48,7 +51,7 @@ Describe "InvokeOnRunspace method on remote runspace" -tags "Feature","RequireAd } } - It "Method should successfully invoke command on remote runspace" -Skip:(!$IsWindows) { + It "Method should successfully invoke command on remote runspace" -Skip:$skipTest { $command = [System.Management.Automation.PSCommand]::new() $command.AddScript('"Hello!"') @@ -59,7 +62,7 @@ Describe "InvokeOnRunspace method on remote runspace" -tags "Feature","RequireAd } } -Describe 'PromptForCredential' { +Describe 'PromptForCredential' -Tags "CI" { BeforeAll { [System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('NoPromptForPassword', $true) } @@ -78,3 +81,18 @@ Describe 'PromptForCredential' { $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/Logging.Tests.ps1 b/test/powershell/Host/Logging.Tests.ps1 index 5159d46551f..53798dc1c3c 100644 --- a/test/powershell/Host/Logging.Tests.ps1 +++ b/test/powershell/Host/Logging.Tests.ps1 @@ -43,6 +43,29 @@ enum LogKeyword ManagedPlugin = 0x100 } +# mac log command can emit json, so just use that +# we need to deconstruct the eventmessage to get the event id +# we also need to filter out the non-default messages +function Get-MacOsSyslogItems { + param ([int]$processId, [string]$logId) + $logArgs = "show", "--process", "$processId", "--style", "json" + log $logArgs | + ConvertFrom-Json | + Where-Object { $_.category -eq "$logId" -and $_.messageType -eq "Default" } | + ForEach-Object { + $s = $_.eventMessage.IndexOf('[') + 1 + $e = $_.EventMessage.IndexOf(']') + $l = $e - $s + if ($l -gt 0) { + $eventId = $_.eventMessage.SubString($s, $l) + } + else { + $eventId = "unknown" + } + $_ | Add-Member -MemberType NoteProperty -Name EventId -Value $eventId -PassThru + } +} + <# .SYNOPSIS Creates a powershell.config.json file with syslog settings @@ -188,7 +211,9 @@ Creating Scriptblock text \(1 of 1\):#012{0}(⏎|#012)*ScriptBlock ID: [0-9a-z\- } } - It 'Verifies scriptblock logging' -Skip:(!$IsSupportedEnvironment) { + # Skip test as it is failing in PowerShell CI on Linux platform. + # Tracking Issue: https://github.com/PowerShell/PowerShell/issues/17092 + It 'Verifies scriptblock logging' -Skip <#-Skip:(!$IsSupportedEnvironment)#> { $configFile = WriteLogSettings -LogId $logId -ScriptBlockLogging -LogLevel Verbose $script = @' $PID @@ -213,11 +238,13 @@ $PID # Verify we log that we are the script to create the scriptblock $createdEvents[1].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f (Get-RegEx -SimpleMatch $Script.Replace([System.Environment]::NewLine,"⏎"))) - # Verify we log that we are excuting the created scriptblock + # Verify we log that we are executing the created scriptblock $createdEvents[2].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f "Write\-Verbose 'testheader123' ;Write\-verbose 'after'") } - It 'Verifies scriptblock logging with null character' -Skip:(!$IsSupportedEnvironment) { + # Skip test as it is failing in PowerShell CI on Linux platform. + # Tracking Issue: https://github.com/PowerShell/PowerShell/issues/17092 + It 'Verifies scriptblock logging with null character' -Skip <#-Skip:(!$IsSupportedEnvironment)#> { $configFile = WriteLogSettings -LogId $logId -ScriptBlockLogging -LogLevel Verbose $script = @' $PID @@ -242,18 +269,21 @@ $PID # Verify we log that we are the script to create the scriptblock $createdEvents[1].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f (Get-RegEx -SimpleMatch $Script.Replace([System.Environment]::NewLine,"⏎"))) - # Verify we log that we are excuting the created scriptblock + # Verify we log that we are executing the created scriptblock $createdEvents[2].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f "Write\-Verbose 'testheader123␀' ;Write\-verbose 'after'") } It 'Verifies logging level filtering works' -Skip:(!$IsSupportedEnvironment) { $configFile = WriteLogSettings -LogId $logId -LogLevel Warning - & $powershell -NoProfile -SettingsFile $configFile -Command '$env:PSModulePath | out-null' + $result = & $powershell -NoProfile -SettingsFile $configFile -Command '$PID' + $result | Should -Not -BeNullOrEmpty # by default, PowerShell only logs informational events on startup. With Level = Warning, nothing should - # have been logged. - $items = Get-PSSysLog -Path $SyslogFile -Id $logId -Tail 100 -TotalCount 1 - $items | Should -Be $null + # have been logged. We'll collect all the syslog entries and look for $PID (there should be none). + $items = Get-PSSysLog -Path $SyslogFile + @($items).Count | Should -BeGreaterThan 0 + $logs = $items | Where-Object { $_.ProcessId -eq $result } + $logs | Should -BeNullOrEmpty } } @@ -262,6 +292,9 @@ Describe 'Basic os_log tests on MacOS' -Tag @('CI','RequireSudoOnUnix') { [bool] $IsSupportedEnvironment = $IsMacOS [bool] $persistenceEnabled = $false + $currentWarningPreference = $WarningPreference + $WarningPreference = "SilentlyContinue" + if ($IsSupportedEnvironment) { # Check the current state. @@ -299,6 +332,7 @@ Path:.* } AfterAll { + $WarningPreference = $currentWarningPreference if ($IsSupportedEnvironment -and !$persistenceEnabled) { # disable persistence if it wasn't enabled @@ -306,26 +340,19 @@ Path:.* } } - It 'Verifies basic logging with no customizations' -Skip:(!$IsSupportedEnvironment) { + It 'Verifies basic logging with no customizations' -Skip:(!$IsMacOS) { try { + $timeString = [DateTime]::Now.ToString('yyyy-MM-dd HH:mm:ss') $configFile = WriteLogSettings -LogId $logId + copy-item $configFile /tmp/pwshtest.config.json $testPid = & $powershell -NoProfile -SettingsFile $configFile -Command '$PID' - - Export-PSOsLog -After $after -LogPid $testPid -TimeoutInMilliseconds 30000 -IntervalInMilliseconds 3000 -MinimumCount 3 | - Set-Content -Path $contentFile - $items = @(Get-PSOsLog -Path $contentFile -Id $logId -After $after -TotalCount 3 -Verbose) + $items = Get-MacOsSyslogItems -processId $testPid -logId $logId $items | Should -Not -Be $null $items.Count | Should -BeGreaterThan 2 - $items[0].EventId | Should -BeExactly 'Perftrack_ConsoleStartupStart:PowershellConsoleStartup.WinStart.Informational' - $items[1].EventId | Should -BeExactly 'NamedPipeIPC_ServerListenerStarted:NamedPipe.Open.Informational' - $items[2].EventId | Should -BeExactly 'Perftrack_ConsoleStartupStop:PowershellConsoleStartup.WinStop.Informational' - # if there are more items than expected... - if ($items.Count -gt 3) - { - # Force reporting of the first unexpected item to help diagnosis - $items[3] | Should -Be $null - } + $items.EventId | Should -Contain 'Perftrack_ConsoleStartupStart:PowershellConsoleStartup.WinStart.Informational' + $items.EventId | Should -Contain 'NamedPipeIPC_ServerListenerStarted:NamedPipe.Open.Informational' + $items.EventId | Should -Contain 'Perftrack_ConsoleStartupStop:PowershellConsoleStartup.WinStop.Informational' } catch { if (Test-Path $contentFile) { @@ -335,7 +362,7 @@ Path:.* } } - It 'Verifies scriptblock logging' -Skip:(!$IsSupportedEnvironment) { + It 'Verifies scriptblock logging' -Skip:(!$IsMacOS) { try { $script = @' $PID @@ -346,24 +373,23 @@ $PID $testScriptPath = Join-Path -Path $TestDrive -ChildPath $testFileName $script | Out-File -FilePath $testScriptPath -Force $testPid = & $powershell -NoProfile -SettingsFile $configFile -Command $testScriptPath - - Export-PSOsLog -After $after -LogPid $testPid -TimeoutInMilliseconds 30000 -IntervalInMilliseconds 3000 -MinimumCount 17 | - Set-Content -Path $contentFile - $items = @(Get-PSOsLog -Path $contentFile -Id $logId -After $after -Verbose) + $items = Get-MacOsSyslogItems -processId $testPid -logId $logId $items | Should -Not -Be $null $items.Count | Should -BeGreaterThan 2 $createdEvents = $items | Where-Object {$_.EventId -eq 'ScriptBlock_Compile_Detail:ExecuteCommand.Create.Verbose'} $createdEvents.Count | Should -BeGreaterOrEqual 3 + $createdEvents | ConvertTo-Json | set-content /tmp/createdEvents.json + # Verify we log that we are executing a file - $createdEvents[0].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f ".*/$testFileName") + $createdEvents[0].EventMessage | Should -Match $testFileName # Verify we log that we are the script to create the scriptblock - $createdEvents[1].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f (Get-RegEx -SimpleMatch $Script)) + $createdEvents[1].EventMessage | Should -Match (Get-RegEx -SimpleMatch $Script) - # Verify we log that we are excuting the created scriptblock - $createdEvents[2].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f "Write\-Verbose 'testheader123' ;Write\-verbose 'after'") + # Verify we log that we are executing the created scriptblock + $createdEvents[2].EventMessage | Should -Match "Write-Verbose 'testheader123' ;Write-verbose 'after'" } catch { if (Test-Path $contentFile) { @@ -373,35 +399,28 @@ $PID } } - It 'Verifies scriptblock logging with null character' -Skip:(!$IsSupportedEnvironment) { + It 'Verifies scriptblock logging with null character' -Skip:(!$IsMacOS) { try { $script = @' $PID & ([scriptblock]::create("Write-Verbose 'testheader123$([char]0x0000)' ;Write-verbose 'after'")) '@ $configFile = WriteLogSettings -ScriptBlockLogging -LogId $logId -LogLevel Verbose - $testFileName = 'test01.ps1' + $testFileName = 'test02.ps1' $testScriptPath = Join-Path -Path $TestDrive -ChildPath $testFileName $script | Out-File -FilePath $testScriptPath -Force - $testPid = & $powershell -NoProfile -SettingsFile $configFile -Command $testScriptPath + $testPid = & $powershell -NoProfile -SettingsFile $configFile -Command $testScriptPath | Select-Object -First 1 - Export-PSOsLog -After $after -LogPid $testPid -TimeoutInMilliseconds 30000 -IntervalInMilliseconds 3000 -MinimumCount 17 | - Set-Content -Path $contentFile - $items = @(Get-PSOsLog -Path $contentFile -Id $logId -After $after -Verbose) + $items = Get-MacOsSyslogItems -processId $testPid -logId $logId + $items | convertto-json | set-content /tmp/items.json - $items | Should -Not -Be $null - $items.Count | Should -BeGreaterThan 2 $createdEvents = $items | Where-Object {$_.EventId -eq 'ScriptBlock_Compile_Detail:ExecuteCommand.Create.Verbose'} - $createdEvents.Count | Should -BeGreaterOrEqual 3 # Verify we log that we are executing a file - $createdEvents[0].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f ".*/$testFileName") + $createdEvents[0].EventMessage | Should -Match $testFileName - # Verify we log that we are the script to create the scriptblock - $createdEvents[1].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f (Get-RegEx -SimpleMatch $Script)) - - # Verify we log that we are excuting the created scriptblock - $createdEvents[2].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f "Write\-Verbose 'testheader123␀' ;Write\-verbose 'after'") + # Verify we log the null in the message + $createdEvents[1].EventMessage | Should -Match "Write-Verbose 'testheader123\`$\(\[char\]0x0000\)' ;Write-verbose 'after'" } catch { if (Test-Path $contentFile) { @@ -411,25 +430,13 @@ $PID } } - # This is pending because it results in false postitives (-Skip:(!$IsSupportedEnvironment) ) - It 'Verifies logging level filtering works' -Pending { - try { - $configFile = WriteLogSettings -LogId $logId -LogLevel Warning - $testPid = & $powershell -NoLogo -NoProfile -SettingsFile $configFile -Command '$PID' - - Export-PSOsLog -After $after -LogPid $testPid | - Set-Content -Path $contentFile - # by default, powershell startup should only logs informational events. - # With Level = Warning, nothing should be logged. - $items = Get-PSOsLog -Path $contentFile -Id $logId -After $after -TotalCount 3 - $items | Should -Be $null - } - catch { - if (Test-Path $contentFile) { - Send-VstsLogFile -Path $contentFile - } - throw - } + # this is now specific to MacOS + It 'Verifies logging level filtering works' -skip:(!$IsMacOs) { + $configFile = WriteLogSettings -LogId $logId -LogLevel Warning + $testPid = & $powershell -NoLogo -NoProfile -SettingsFile $configFile -Command '$PID' + + $items = Get-MacOsSyslogItems -processId $testPid -logId $logId + $items | Should -Be $null -Because ("{0} Warning event logs were found" -f @($items).Count) } } @@ -437,6 +444,10 @@ Describe 'Basic EventLog tests on Windows' -Tag @('CI','RequireAdminOnWindows') BeforeAll { [bool] $IsSupportedEnvironment = $IsWindows [string] $powershell = Join-Path -Path $PSHOME -ChildPath 'pwsh' + + $currentWarningPreference = $WarningPreference + $WarningPreference = "SilentlyContinue" + $scriptBlockLoggingCases = @( @{ name = 'normal script block' @@ -456,6 +467,10 @@ Describe 'Basic EventLog tests on Windows' -Tag @('CI','RequireAdminOnWindows') } } + AfterAll { + $WarningPreference = $currentWarningPreference + } + BeforeEach { if ($IsSupportedEnvironment) { diff --git a/test/powershell/Host/PSVersionTable.Tests.ps1 b/test/powershell/Host/PSVersionTable.Tests.ps1 index e691b7661be..777ccb6c132 100644 --- a/test/powershell/Host/PSVersionTable.Tests.ps1 +++ b/test/powershell/Host/PSVersionTable.Tests.ps1 @@ -3,6 +3,7 @@ Describe "PSVersionTable" -Tags "CI" { BeforeAll { + Set-StrictMode -Version 3 $sma = Get-Item (Join-Path $PSHOME "System.Management.Automation.dll") $formattedVersion = $sma.VersionInfo.ProductVersion @@ -22,10 +23,6 @@ Describe "PSVersionTable" -Tags "CI" { $expectedGitCommitIdPattern = "^$mainVersionPattern$" $unexpectectGitCommitIdPattern = $fullVersionPattern } - - $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" - $powerShellCompatibleVersions = $PSVersionTable.PSCompatibleVersions | - ForEach-Object {$_.ToString(2).SubString(0,3)} } It "Should have version table entries" { @@ -163,15 +160,31 @@ Describe "PSVersionTable" -Tags "CI" { } } - It "Verify PSCompatibleVersions has an entry for all known versions of PowerShell" { - foreach ($version in $powerShellVersions) { - $version | Should -BeIn $powerShellCompatibleVersions + Context "PSCompatibleVersions property" { + It "Is of type System.Version[]" { + Should -ActualValue $PSVersionTable.PSCompatibleVersions -BeOfType System.Version[] + } + + It "Is sorted in ascending order" { + $array = $PSVersionTable.PSCompatibleVersions + [array]::Sort($array) + + $PSVersionTable.PSCompatibleVersions | Should -Be $array } - } - It "Verify PSCompatibleVersions has no unknown PowerShell entries" { - foreach ($version in $powerShellCompatibleVersions) { - $version | Should -BeIn $powerShellVersions + It "Has no unexpected items present" { + $expectedItems = @( + [version]::new(1, 0) + [version]::new(2, 0) + [version]::new(3, 0) + [version]::new(4, 0) + [version]::new(5, 0) + [version]::new(5, 1) + [version]::new(6, 0) + [version]::new(7, 0) + ) + + Compare-Object $expectedItems $PSVersionTable.PSCompatibleVersions | Should -Be $null } } } diff --git a/test/powershell/Host/ScreenReader.Tests.ps1 b/test/powershell/Host/ScreenReader.Tests.ps1 index 35f86459861..1b11a0e3ffa 100644 --- a/test/powershell/Host/ScreenReader.Tests.ps1 +++ b/test/powershell/Host/ScreenReader.Tests.ps1 @@ -44,8 +44,12 @@ Describe "Validate start of console host" -Tag CI { } 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 + $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'." diff --git a/test/powershell/Host/Startup.Tests.ps1 b/test/powershell/Host/Startup.Tests.ps1 index 6ce9dfc463d..35c22fefe58 100644 --- a/test/powershell/Host/Startup.Tests.ps1 +++ b/test/powershell/Host/Startup.Tests.ps1 @@ -7,7 +7,6 @@ Describe "Validate start of console host" -Tag CI { 'Microsoft.ApplicationInsights.dll' 'Microsoft.Management.Infrastructure.dll' 'Microsoft.PowerShell.ConsoleHost.dll' - 'Microsoft.PowerShell.Security.dll' 'Microsoft.Win32.Primitives.dll' 'Microsoft.Win32.Registry.dll' 'netstandard.dll' @@ -15,7 +14,6 @@ Describe "Validate start of console host" -Tag CI { 'pwsh.dll' 'System.Collections.Concurrent.dll' 'System.Collections.dll' - 'System.Collections.NonGeneric.dll' 'System.Collections.Specialized.dll' 'System.ComponentModel.dll' 'System.ComponentModel.Primitives.dll' @@ -26,7 +24,6 @@ Describe "Validate start of console host" -Tag CI { 'System.Diagnostics.TraceSource.dll' 'System.Diagnostics.Tracing.dll' 'System.IO.FileSystem.AccessControl.dll' - 'System.IO.FileSystem.dll' 'System.IO.FileSystem.DriveInfo.dll' 'System.IO.Pipes.dll' 'System.Linq.dll' @@ -36,6 +33,7 @@ Describe "Validate start of console host" -Tag CI { 'System.Net.Mail.dll' 'System.Net.NetworkInformation.dll' 'System.Net.Primitives.dll' + 'System.Numerics.Vectors.dll' 'System.ObjectModel.dll' 'System.Private.CoreLib.dll' 'System.Private.Uri.dll' @@ -45,15 +43,14 @@ Describe "Validate start of console host" -Tag CI { 'System.Reflection.Primitives.dll' 'System.Runtime.dll' 'System.Runtime.InteropServices.dll' - 'System.Runtime.InteropServices.RuntimeInformation.dll' 'System.Runtime.Loader.dll' 'System.Runtime.Numerics.dll' 'System.Runtime.Serialization.Formatters.dll' 'System.Runtime.Serialization.Primitives.dll' 'System.Security.AccessControl.dll' - 'System.Security.Cryptography.Encoding.dll' - 'System.Security.Cryptography.X509Certificates.dll' + 'System.Security.Cryptography.dll' 'System.Security.Principal.Windows.dll' + 'System.Text.Encoding.CodePages.dll' 'System.Text.Encoding.Extensions.dll' 'System.Text.RegularExpressions.dll' 'System.Threading.dll' @@ -66,16 +63,15 @@ Describe "Validate start of console host" -Tag CI { if ($IsWindows) { $allowedAssemblies += @( 'Microsoft.PowerShell.CoreCLR.Eventing.dll' - 'System.Diagnostics.FileVersionInfo.dll' 'System.DirectoryServices.dll' 'System.Management.dll' 'System.Security.Claims.dll' - 'System.Security.Cryptography.Primitives.dll' 'System.Threading.Overlapped.dll' ) } else { $allowedAssemblies += @( + 'System.Diagnostics.DiagnosticSource.dll' 'System.Net.Sockets.dll' ) } @@ -90,7 +86,7 @@ Describe "Validate start of console host" -Tag CI { Remove-Item $profileDataFile -Force } - $loadedAssemblies = & "$PSHOME/pwsh" -noprofile -command '([System.AppDomain]::CurrentDomain.GetAssemblies()).manifestmodule | Where-Object { $_.Name -notlike ""<*>"" } | ForEach-Object { $_.Name }' + $loadedAssemblies = & "$PSHOME/pwsh" -noprofile -command '([System.AppDomain]::CurrentDomain.GetAssemblies()).manifestmodule | Where-Object { $_.Name -notlike "<*>" } | ForEach-Object { $_.Name }' } It "No new assemblies are loaded" { diff --git a/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 b/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 index 78be093a5bb..32fd73f8c09 100644 --- a/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 @@ -28,10 +28,9 @@ Describe "Tab completion bug fix" -Tags "CI" { It "Issue#1345 - 'Import-Module -n' should work" { $cmd = "Import-Module -n" $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length - $result.CompletionMatches | Should -HaveCount 3 + $result.CompletionMatches | Should -HaveCount 2 $result.CompletionMatches[0].CompletionText | Should -BeExactly "-Name" $result.CompletionMatches[1].CompletionText | Should -BeExactly "-NoClobber" - $result.CompletionMatches[2].CompletionText | Should -BeExactly "-NoOverwrite" } It "Issue#11227 - [CompletionCompleters]::CompleteVariable and [CompletionCompleters]::CompleteType should work" { @@ -44,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 @@ -85,8 +106,24 @@ Describe "Tab completion bug fix" -Tags "CI" { $result.CurrentMatchIndex | Should -Be -1 $result.ReplacementIndex | Should -Be 40 $result.ReplacementLength | Should -Be 0 - $result.CompletionMatches[0].CompletionText | Should -BeExactly 'Expression' - $result.CompletionMatches[1].CompletionText | Should -BeExactly 'Ascending' - $result.CompletionMatches[2].CompletionText | Should -BeExactly 'Descending' + $result.CompletionMatches[0].CompletionText | Should -BeExactly 'Ascending' + $result.CompletionMatches[1].CompletionText | Should -BeExactly 'Descending' + } + + It "Issue#19912 - Tab completion should not crash" { + $ISS = [initialsessionstate]::CreateDefault() + $Runspace = [runspacefactory]::CreateRunspace($ISS) + $Runspace.Open() + $OldRunspace = [runspace]::DefaultRunspace + try + { + [runspace]::DefaultRunspace = $Runspace + {[System.Management.Automation.CommandCompletion]::CompleteInput('Get-', 3, $null)} | Should -Not -Throw + } + finally + { + [runspace]::DefaultRunspace = $OldRunspace + $Runspace.Dispose() + } } } diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 77667306aed..93bdfe89882 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(' @@ -43,6 +109,22 @@ Describe "TabCompletion" -Tags CI { $res.CompletionMatches[0].CompletionText | Should -BeExactly 'CompareTo(' } + It 'should complete generic type parameters for static methods' { + $script = '[array]::Empty[pscu' + + $results = TabExpansion2 -inputScript $script -cursorColumn $script.Length + $results.CompletionMatches.CompletionText | Should -Contain 'pscustomobject' + } + + It 'should complete generic type parameters for instance methods' { + $script = ' + $dict = [System.Collections.Concurrent.ConcurrentDictionary[string, int]]::new() + $dict.AddOrUpdate[pscu' + + $results = TabExpansion2 -inputScript $script -cursorColumn $script.Length + $results.CompletionMatches.CompletionText | Should -Contain 'pscustomobject' + } + It 'Should complete Magic foreach' { $res = TabExpansion2 -inputScript '(1..10).Fo' -cursorColumn '(1..10).Fo'.Length $res.CompletionMatches[0].CompletionText | Should -BeExactly 'ForEach(' @@ -58,6 +140,356 @@ Describe "TabCompletion" -Tags CI { $res.CompletionMatches[0].CompletionText | Should -BeExactly 'pscustomobject' } + It 'Should complete foreach variable' { + $res = TabExpansion2 -inputScript 'foreach ($CurrentItem in 1..10){$CurrentIt' + $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 + } + + It 'Should complete variable in default value of a parameter' { + $res = TabExpansion2 -inputScript 'param($PS = $P' + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + } + + It 'Should complete variable with description and 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 ' -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' + $res.CompletionMatches.Count | Should -Be 0 + } + + foreach ($Operator in [System.Management.Automation.CompletionCompleters]::CompleteOperator("")) + { + It "Should complete $($Operator.CompletionText)" { + $res = TabExpansion2 -inputScript "'' $($Operator.CompletionText)" -cursorColumn ($Operator.CompletionText.Length + 3) + $res.CompletionMatches[0].CompletionText | Should -BeExactly $Operator.CompletionText + } + } + + 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 ' -TestCases @( + @{ + Intent = 'Hashtable with no user input' + Expected = "'PSVersion'" + TestString = '$PSVersionTable[^' + } + @{ + Intent = 'Hashtable with partial input' + Expected = "'PSVersion'" + TestString = '$PSVersionTable[ PSvers^' + } + @{ + Intent = 'Hashtable with partial quoted input' + Expected = "'PSVersion'" + TestString = '$PSVersionTable["PSvers^' + } + @{ + Intent = 'Hashtable from Ast' + Expected = "'Hello'" + TestString = '$Table = @{Hello = "World"};$Table[^' + } + @{ + Intent = 'Hashtable with cursor on new line' + Expected = "'Hello'" + TestString = @' +$Table = @{Hello = "World"} +$Table[ +^ +'@ + } + ) -Test { + param($Expected, $TestString) + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -cursorColumn $CursorIndex -inputScript $TestString.Remove($CursorIndex, 1) + $res.CompletionMatches[0].CompletionText | Should -BeExactly $Expected + } + + it 'should add quotes when completing hashtable key from Ast with member syntax' -Test { + $res = TabExpansion2 -inputScript '$Table = @{"Hello World" = "World"};$Table.' + $res.CompletionMatches.CompletionText | Where-Object {$_ -eq "'Hello World'"} | Should -BeExactly "'Hello World'" + } + + It '' -TestCases @( + @{ + Intent = 'Complete member with space between dot and cursor' + Expected = 'value__' + TestString = '[System.Management.Automation.ActionPreference]::Break. ^' + } + @{ + Intent = 'Complete member when cursor is in-between existing members and spaces' + Expected = 'value__' + TestString = '[System.Management.Automation.ActionPreference]::Break. ^ ToString()' + } + @{ + Intent = 'Complete static member with space between colons and cursor' + Expected = 'Break' + TestString = '[System.Management.Automation.ActionPreference]:: ^' + } + @{ + Intent = 'Complete static member with new line between colons and cursor' + Expected = 'Break' + TestString = @' +[System.Management.Automation.ActionPreference]:: +^ +'@ + } + @{ + Intent = 'Complete static member with partial input and incomplete input at end of line' + Expected = 'Break' + TestString = '[System.Management.Automation.ActionPreference]:: Brea^. value__.' + } + @{ + Intent = 'Complete static member with partial input and valid input at end of line' + Expected = 'Break' + TestString = '[System.Management.Automation.ActionPreference]:: Brea^. value__' + } + @{ + Intent = 'Complete member with new line between colons and cursor' + Expected = 'value__' + TestString = '[System.Management.Automation.ActionPreference]::Break. ^ ToString()' + } + @{ + Intent = 'Complete type with incomplete expression input at end of line' + Expected = 'System.Management.Automation.ActionPreference' + TestString = '[System.Management.Automation.ActionPreference^]::' + } + @{ + Intent = 'Complete member inside switch expression' + Expected = 'Length' + TestString = @' +switch ($x) +{ + 'RandomString'.^ + {} +} +'@ + } + @{ + Intent = 'Complete member in commandast' + Expected = 'Length' + TestString = 'ls "".^' + } + ){ + param($Expected, $TestString) + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -cursorColumn $CursorIndex -inputScript $TestString.Remove($CursorIndex, 1) + $res.CompletionMatches[0].CompletionText | Should -BeExactly $Expected + } + + It 'Should Complete and replace existing member with space in front of cursor and cursor in front of word' { + $TestString = '[System.Management.Automation.ActionPreference]:: ^Break' + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -cursorColumn $CursorIndex -inputScript $TestString.Remove($CursorIndex, 1) + $res.ReplacementIndex | Should -BeExactly $CursorIndex + $res.ReplacementLength | Should -Be 5 + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Break' + } + + It 'Complete and replace existing member with colons in front of cursor and cursor in front of word' { + $TestString = '[System.Management.Automation.ActionPreference]::^Break' + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -cursorColumn $CursorIndex -inputScript $TestString.Remove($CursorIndex, 1) + $res.ReplacementIndex | Should -BeExactly $CursorIndex + $res.ReplacementLength | Should -Be 5 + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Break' + } + It 'Should complete namespaces' { $res = TabExpansion2 -inputScript 'using namespace Sys' -cursorColumn 'using namespace Sys'.Length $res.CompletionMatches[0].CompletionText | Should -BeExactly 'System' @@ -118,15 +550,144 @@ Describe "TabCompletion" -Tags CI { $completionText -join ' ' | Should -BeExactly 'Ascending Descending Expression' } - It 'Should complete New-Object hashtable' { - class X { - $A - $B - $C + It 'Should complete variable assigned in other scriptblock' { + $res = TabExpansion2 -inputScript 'ForEach-Object -Begin {$Test1 = "Hello"} -Process {$Test' + $res.CompletionMatches[0].CompletionText | Should -Be '$Test1' + } + + It 'Should complete variable assigned in an array of scriptblocks' { + $res = TabExpansion2 -inputScript 'ForEach-Object -Process @({"Block1"},{$Test1="Hello"});$Test' + $res.CompletionMatches[0].CompletionText | Should -Be '$Test1' + } + + It 'Should not complete variable assigned in an ampersand executed scriptblock' { + $res = TabExpansion2 -inputScript '& {$AmpeersandVarCompletionTest = "Hello"};$AmpeersandVarCompletionTes' + $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 { + $A + $B + $C + } + function RandomTestTypeClassTestCompletion([RandomTestType]$Param1){} + Class LevelOneClass { + [LevelTwoClass] $Property1 + } + class LevelTwoClass { + [string] $Property2 + } + function LevelOneClassTestCompletion([LevelOneClass[]]$Param1){} + Add-Type -TypeDefinition 'public interface IRandomInterfaceTest{string DemoProperty { get; set; }}' + function functionWithInterfaceParam ([IRandomInterfaceTest]$Param1){} } - $res = TabExpansion2 -inputScript 'New-Object -TypeName X -Property @{ ' -cursorColumn 'New-Object -TypeName X -Property @{ '.Length - $res.CompletionMatches | Should -HaveCount 3 - $res.CompletionMatches.CompletionText -join ' ' | Should -BeExactly 'A B C' + It 'Should complete New-Object hashtable' { + $res = TabExpansion2 -inputScript 'New-Object -TypeName RandomTestType -Property @{ ' + $res.CompletionMatches | Should -HaveCount 3 + $res.CompletionMatches.CompletionText -join ' ' | Should -BeExactly 'A B C' + } + + It 'Complete hashtable key without duplicate keys' { + $TestString = '[RandomTestType]@{A="";^}' + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -inputScript $TestString.Remove($CursorIndex, 1) -cursorColumn $CursorIndex + $res.CompletionMatches | Should -HaveCount 2 + $res.CompletionMatches.CompletionText -join ' ' | Should -BeExactly 'B C' + } + + It 'Complete hashtable key on empty line after key/value pair' { + $TestString = @' +[RandomTestType]@{ + B="" + ^ +} +'@ + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -inputScript $TestString.Remove($CursorIndex, 1) -cursorColumn $CursorIndex + $res.CompletionMatches | Should -HaveCount 2 + $res.CompletionMatches.CompletionText -join ' ' | Should -BeExactly 'A C' + } + + It 'Should complete class properties for typed variable declaration with hashtable' { + $res = TabExpansion2 -inputScript '[RandomTestType]$TestVar = @{' + $res.CompletionMatches | Should -HaveCount 3 + $res.CompletionMatches.CompletionText -join ' ' | Should -BeExactly 'A B C' + } + + It 'Should complete class properties for typed command parameter with hashtable input' { + $res = TabExpansion2 -inputScript 'RandomTestTypeClassTestCompletion -Param1 @{' + $res.CompletionMatches | Should -HaveCount 3 + $res.CompletionMatches.CompletionText -join ' ' | Should -BeExactly 'A B C' + } + + It 'Should complete class properties for nested hashtable' { + $res = TabExpansion2 -inputScript '[LevelOneClass]@{Property1=@{' + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Property2' + } + + It 'Should complete class properties for underlying type in array parameter' { + $res = TabExpansion2 -inputScript 'LevelOneClassTestCompletion @{' + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Property1' + } + + It 'Should complete class properties for new class assignment to property' { + $res = TabExpansion2 -inputScript '$Var=[LevelOneClass]::new();$Var.Property1=@{' + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Property2' + } + + It 'Should not complete class properties from class with constructor that takes arguments' { + $res = TabExpansion2 -inputScript 'class ClassWithCustomConstructor {ClassWithCustomConstructor ($Param){}$A};[ClassWithCustomConstructor]@{' + $res.CompletionMatches[0].CompletionText | Should -BeNullOrEmpty + } + + It 'Should complete class properties for function with an interface type' { + $res = TabExpansion2 -inputScript 'functionWithInterfaceParam -Param1 @{' + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'DemoProperty' + } + } + + It 'Complete hashtable keys for Get-WinEvent FilterHashtable' -Skip:(!$IsWindows) { + $TestString = 'Get-WinEvent -FilterHashtable @{^' + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -inputScript $TestString.Remove($CursorIndex, 1) -cursorColumn $CursorIndex + $res.CompletionMatches | Should -HaveCount 11 + $res.CompletionMatches.CompletionText -join ' ' | Should -BeExactly 'LogName ProviderName Path Keywords ID Level StartTime EndTime UserID Data SuppressHashFilter' + } + + It 'Complete hashtable keys for Get-WinEvent SuppressHashFilter' -Skip:(!$IsWindows) { + $TestString = 'Get-WinEvent -FilterHashtable @{SuppressHashFilter=@{' + $res = TabExpansion2 -inputScript $TestString + $res.CompletionMatches | Should -HaveCount 10 + $res.CompletionMatches.CompletionText -join ' ' | Should -BeExactly 'LogName ProviderName Path Keywords ID Level StartTime EndTime UserID Data' + } + + It 'Complete hashtable keys for hashtable in array of arguments' { + $res = TabExpansion2 -inputScript 'Get-ChildItem | Format-Table -Property Attributes,@{' + $res.CompletionMatches.CompletionText -join ' ' | Should -BeExactly 'Expression FormatString Label Width Alignment' + } + + It 'Complete hashtable keys for a hashtable used for splatting' { + $TestString = '$GetChildItemParams=@{^};Get-ChildItem @GetChildItemParams -Force -Recurse' + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -inputScript $TestString.Remove($CursorIndex, 1) -cursorColumn $CursorIndex + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Path' } It 'Should complete "Get-Process -Id " with Id and name in tooltip' { @@ -146,11 +707,25 @@ Describe "TabCompletion" -Tags CI { $res.CompletionMatches.Count | Should -BeGreaterThan 0 } - It 'Should complete keyword' -Skip { + It 'Should complete keyword with partial input' { $res = TabExpansion2 -inputScript 'using nam' -cursorColumn 'using nam'.Length $res.CompletionMatches[0].CompletionText | Should -BeExactly 'namespace' } + It 'Should complete keyword with no input' { + $res = TabExpansion2 -inputScript 'using ' -cursorColumn 'using '.Length + $res.CompletionMatches.CompletionText | Should -BeExactly 'assembly','module','namespace','type' + } + + It 'Should complete keyword with no input after line continuation' { + $InputScript = @' +using ` + +'@ + $res = TabExpansion2 -inputScript $InputScript -cursorColumn $InputScript.Length + $res.CompletionMatches.CompletionText | Should -BeExactly 'assembly','module','namespace','type' + } + It 'Should first suggest -Full and then -Functionality when using Get-Help -Fu' -Skip { $res = TabExpansion2 -inputScript 'Get-Help -Fu' -cursorColumn 'Get-Help -Fu'.Length $res.CompletionMatches[0].CompletionText | Should -BeExactly '-Full' @@ -163,6 +738,32 @@ Describe "TabCompletion" -Tags CI { $res.CompletionMatches[1].CompletionText | Should -BeExactly '-Functionality' } + It 'Should not remove braces when completing variable with braces' { + $Text = '"Hello${psversiont}World"' + $res = TabExpansion2 -inputScript $Text -cursorColumn $Text.IndexOf('p') + $res.CompletionMatches[0].CompletionText | Should -BeExactly '${PSVersionTable}' + } + + It 'Should work for property assignment of enum type:' { + $res = TabExpansion2 -inputScript '$psstyle.Progress.View="Clas' + $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: ' -TestCases @( @{ inputStr = '$ErrorActionPreference = '; filter = ''; doubleQuotes = $false } @{ inputStr = '$ErrorActionPreference='; filter = ''; doubleQuotes = $false } @@ -301,6 +902,874 @@ Describe "TabCompletion" -Tags CI { $actual | Should -BeExactly $expected } + It 'ForEach-Object member completion results should include methods' { + $res = TabExpansion2 -inputScript '1..10 | ForEach-Object -MemberName ' + $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 + } + + It 'Should complete custom constructor from class using the AST' { + $res = TabExpansion2 -inputScript 'class ConstructorTestClass{ConstructorTestClass ([string] $s){}};[ConstructorTestClass]::' + $res.CompletionMatches | Should -HaveCount 3 + $completionText = $res.CompletionMatches.CompletionText | Sort-Object + $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 + $completionText = $res.CompletionMatches.ToolTip + $completionText.replace("`r`n", [System.Environment]::NewLine).trim() + + $expected = @' +ConstructorTestClass(string s) +ConstructorTestClass(int i) +ConstructorTestClass(int i, bool b) +'@ + $expected.replace("`r`n", [System.Environment]::NewLine).trim() + $completionText.replace("`r`n", [System.Environment]::NewLine).trim() | Should -BeExactly $expected + } + + It 'Should complete parameter in param block' { + $res = TabExpansion2 -inputScript 'Param($Param1=(Get-ChildItem -))' -cursorColumn 30 + $res.CompletionMatches[0].CompletionText | Should -BeExactly '-Path' + } + + It 'Should complete member in param block' { + $res = TabExpansion2 -inputScript 'Param($Param1=($PSVersionTable.))' -cursorColumn 31 + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Count' + } + + It 'Should complete attribute argument in param block' { + $res = TabExpansion2 -inputScript 'Param([Parameter()]$Param1)' -cursorColumn 17 + $names = [Parameter].GetProperties() | Where-Object CanWrite | ForEach-Object Name + + $diffs = Compare-Object -ReferenceObject $res.CompletionMatches.CompletionText -DifferenceObject $names + $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 + } + + It 'Should complete argument with validateset attribute after comma' { + $TestString = 'function Test-ValidateSet{Param([ValidateSet("Cat","Dog")]$Param1,$Param2)};Test-ValidateSet -Param1 Dog, -Param2' + $res = TabExpansion2 -inputScript $TestString -cursorColumn ($TestString.LastIndexOf(',') + 1) + $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 + } + + it 'Should complete "Value" parameter value in "Where-Object" for Enum property with partial input' { + $res = TabExpansion2 -inputScript 'Get-Command | where-Object CommandType -ne Ali' + $res.CompletionMatches[0].CompletionText | Should -BeExactly Alias + } + + it 'Should complete the right hand side of a comparison operator when left is an Enum with no input' { + $res = TabExpansion2 -inputScript 'Get-Command | Where-Object -FilterScript {$_.CommandType -like ' + $res.CompletionMatches[0].CompletionText | Should -BeExactly "'Alias'" + } + + it 'Should complete the right hand side of a comparison operator when left is an Enum with partial input' { + $TempVar = Get-Command + $res = TabExpansion2 -inputScript '$tempVar[0].CommandType -notlike "Ali"' + $res.CompletionMatches[0].CompletionText | Should -BeExactly "'Alias'" + } + + it 'Should complete the right hand side of a comparison operator when left is an Enum when cursor is on a newline' { + $res = TabExpansion2 -inputScript "Get-Command | Where-Object -FilterScript {`$_.CommandType -like`n" + $res.CompletionMatches[0].CompletionText | Should -BeExactly "'Alias'" + } + + it 'Should complete provider dynamic parameters with quoted path' { + $Script = if ($IsWindows) + { + 'Get-ChildItem -Path "C:\" -Director' + } + else + { + 'Get-ChildItem -Path "/" -Director' + } + $res = TabExpansion2 -inputScript $Script + $res.CompletionMatches[0].CompletionText | Should -BeExactly '-Directory' + } + + it 'Should complete dynamic parameters while providing values to non-string parameters' { + $res = TabExpansion2 -inputScript 'Get-Content -Path $HOME -Verbose:$false -' + $res.CompletionMatches.CompletionText | Should -Contain '-Raw' + } + + It 'Should enumerate types when completing member names for Select-Object' { + $TestString = '"Hello","World" | select-object ' + $res = TabExpansion2 -inputScript $TestString + $res | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Length' + } + + It 'Should complete psobject members for variable' { + $TestVar = Get-Command Get-Command | Select-Object CommandType + $res = TabExpansion2 -inputScript '$TestVar | ForEach-Object {$_.commandtype' + $res | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'CommandType' + } + + It 'Should not complete variables that appear after the cursor' { + $TestString = '$TestVar1 = 1; $TestVar^ ; $TestVar2 = 2' + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -cursorColumn $CursorIndex -inputScript $TestString.Remove($CursorIndex, 1) + $res | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly '$TestVar1' + } + + It 'Should not complete pipeline variables outside the pipeline' { + $TestString = 'Get-ChildItem -PipelineVariable TestVar1;$TestVar^' + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -cursorColumn $CursorIndex -inputScript $TestString.Remove($CursorIndex, 1) + $res.CompletionMatches | Should -HaveCount 0 + } + + It 'Should complete pipeline variables inside the pipeline' { + $TestString = 'Get-ChildItem -PipelineVariable TestVar1 | ForEach-Object -Process {$TestVar^}' + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -cursorColumn $CursorIndex -inputScript $TestString.Remove($CursorIndex, 1) + $res | Should -HaveCount 1 + $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' + } + + Context 'Start-Process -Verb parameter completion' { + BeforeAll { + 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 -Path $cmdPath + $cmdVerbsSingleQuote = GetProcessInfoVerbs -Path $cmdPath -SingleQuote + $cmdVerbsDoubleQuote = GetProcessInfoVerbs -Path $cmdPath -DoubleQuote + $exePath = Join-Path -Path $TestDrive -ChildPath 'test.exe' + $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 -Path $txtPath + $wavPath = Join-Path -Path $TestDrive -ChildPath 'test.wav' + $wavVerbs = GetProcessInfoVerbs -Path $wavPath + $docxPath = Join-Path -Path $TestDrive -ChildPath 'test.docx' + $docxVerbs = GetProcessInfoVerbs -Path $docxPath + $fileWithNoExtensionPath = Join-Path -Path $TestDrive -ChildPath 'test' + $fileWithNoExtensionVerbs = GetProcessInfoVerbs -Path $fileWithNoExtensionPath + } + + It "Should complete Verb parameter for ''" -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 ' ' } + @{ TextInput = "Start-Process -FilePath $docxPath -Verb "; ExpectedVerbs = $docxVerbs -join ' ' } + @{ TextInput = "Start-Process -FilePath $fileWithNoExtensionPath -Verb "; ExpectedVerbs = $fileWithNoExtensionVerbs -join ' ' } + ) { + param($TextInput, $ExpectedVerbs) + $res = TabExpansion2 -inputScript $TextInput -cursorColumn $TextInput.Length + $completionText = $res.CompletionMatches.CompletionText | Sort-Object + $completionText -join ' ' | Should -BeExactly $ExpectedVerbs + } + } + + 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 '' for ''" -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) + foreach ($command in $Commands) { + $joinedCommand = "$command $ParameterInput" + $res = TabExpansion2 -inputScript $joinedCommand -cursorColumn $joinedCommand.Length + $completionText = $res.CompletionMatches.CompletionText | Sort-Object + $completionText -join ' ' | Should -BeExactly $ExpectedScopes + } + } + } + + 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 ''" -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 } + @{ TextInput = 'Get-Verb -Group NonExistentGroup -Verb '; ExpectedVerbs = '' } + @{ TextInput = 'Get-Verb -Verb Conv'; ExpectedVerbs = $verbsStartingWithConv } + @{ TextInput = 'Get-Command -Verb '; ExpectedVerbs = $allVerbs } + @{ TextInput = 'Get-Command -Verb Re'; ExpectedVerbs = $verbsStartingWithRe } + @{ TextInput = 'Get-Command -Verb Ex'; ExpectedVerbs = $verbsStartingWithEx } + @{ 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) + $res = TabExpansion2 -inputScript $TextInput -cursorColumn $TextInput.Length + $completionText = $res.CompletionMatches.CompletionText | Sort-Object + $completionText -join ' ' | Should -BeExactly $ExpectedVerbs + } + } + + 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 ''" -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) + $res = TabExpansion2 -inputScript $TextInput -cursorColumn $TextInput.Length + $completionText = $res.CompletionMatches.CompletionText | Sort-Object + $completionText -join ' ' | Should -BeExactly $ExpectedVersions + } + } + + Context 'Help Module parameter completion' { + BeforeAll { + $utilityModule = 'Microsoft.PowerShell.Utility' + $managementModule = 'Microsoft.PowerShell.Management' + $allMicrosoftPowerShellModules = (Get-Module -Name Microsoft.PowerShell* -ListAvailable).Name + Import-Module -Name $allMicrosoftPowerShellModules -ErrorAction SilentlyContinue + $allMicrosoftPowerShellModules = ($allMicrosoftPowerShellModules | Sort-Object -Unique) -join ' ' + } + + It "Should complete Module for ''" -TestCases @( + @{ TextInput = "Save-Help -Module Microsoft.PowerShell.U"; ExpectedModules = $utilityModule } + @{ TextInput = "Update-Help -Module Microsoft.PowerShell.U"; ExpectedModules = $utilityModule } + @{ TextInput = "Save-Help -Module Microsoft.PowerShell.Man"; ExpectedModules = $managementModule } + @{ TextInput = "Update-Help -Module Microsoft.PowerShell.Man"; ExpectedModules = $managementModule } + @{ TextInput = "Save-Help -Module Microsoft.Powershell"; ExpectedModules = $allMicrosoftPowerShellModules } + @{ TextInput = "Update-Help -Module Microsoft.PowerShell"; ExpectedModules = $allMicrosoftPowerShellModules } + @{ TextInput = "Save-Help -Module NonExistentModulePrefix"; ExpectedModules = '' } + @{ TextInput = "Update-Help -Module NonExistentModulePrefix"; ExpectedModules = '' } + ) { + param($TextInput, $ExpectedModules) + $res = TabExpansion2 -inputScript $TextInput -cursorColumn $TextInput.Length + $completionText = $res.CompletionMatches.CompletionText | Sort-Object -Unique + $completionText -join ' ' | Should -BeExactly $ExpectedModules + } + } + + 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 ''" -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 ''" -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 ''" -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 ''" -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 ''" -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 ''" -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 = @' + + + + + R A M + + System.Diagnostics.Process + + + + + + 40 + Center + + + + 40 + Center + + + + 40 + Center + + + + + + + Center + Name + + + Center + PagedMemorySize + + + Center + PeakWorkingSet + + + + + + + + +'@ + + $tempViewFile = Join-Path -Path $TestDrive -ChildPath 'processViewDefinition.ps1xml' + Set-Content -LiteralPath $tempViewFile -Value $viewDefinition -Force + + $ps = [PowerShell]::Create() + $null = $ps.AddScript("Update-FormatData -AppendPath $tempViewFile") + $ps.Invoke() + $ps.HadErrors | Should -BeFalse + $ps.Commands.Clear() + + Remove-Item -LiteralPath $tempViewFile -Force -ErrorAction SilentlyContinue + } + + It 'Should complete Get-ChildItem | -View' -TestCases ( + @{ cmd = 'Format-Table'; expected = "children childrenWithHardlink$(if (!$IsWindows) { ' childrenWithUnixStat' })" }, + @{ cmd = 'Format-List'; expected = 'children' }, + @{ cmd = 'Format-Wide'; expected = 'children' }, + @{ cmd = 'Format-Custom'; expected = '' } + ) { + param($cmd, $expected) + + # The completion is based on OutputTypeAttribute() of the cmdlet. + $res = TabExpansion2 -inputScript "Get-ChildItem | $cmd -View " -cursorColumn "Get-ChildItem | $cmd -View ".Length + $completionText = $res.CompletionMatches.CompletionText | Sort-Object + $completionText -join ' ' | Should -BeExactly $expected + } + + It 'Should complete $processList = Get-Process; $processList | ' -TestCases ( + @{ cmd = 'Format-Table -View '; expected = "'R A M'", "Priority", "process", "ProcessModule", "ProcessWithUserName", "StartTime" }, + @{ cmd = 'Format-List -View '; expected = '' }, + @{ cmd = 'Format-Wide -View '; expected = 'process' }, + @{ cmd = 'Format-Custom -View '; expected = '' }, + @{ cmd = 'Format-Table -View S'; expected = "StartTime" }, + @{ cmd = "Format-Table -View 'S"; expected = "'StartTime'" }, + @{ cmd = "Format-Table -View R"; expected = "'R A M'" } + ) { + param($cmd, $expected) + + $null = $ps.AddScript({ + param ($cmd) + $processList = Get-Process + $res = TabExpansion2 -inputScript "`$processList | $cmd" -cursorColumn "`$processList | $cmd".Length + $completionText = $res.CompletionMatches.CompletionText | Sort-Object + $completionText + }).AddArgument($cmd) + + $result = $ps.Invoke() + $ps.Commands.Clear() + $expected = ($expected | Sort-Object) -join ' ' + $result -join ' ' | Should -BeExactly $expected + } + } + Context NativeCommand { BeforeAll { $nativeCommand = (Get-Command -CommandType Application -TotalCount 1).Name @@ -377,6 +1846,92 @@ Describe "TabCompletion" -Tags CI { $completionText -join ' ' | Should -BeExactly 'blg csv tsv' } + it 'Should include positionally bound parameters when completing in front of parameter value' { + $TestString = 'Get-ChildItem -^ $HOME' + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -inputScript $TestString.Remove($CursorIndex, 1) -cursorColumn $CursorIndex + $res.CompletionMatches.CompletionText | Should -Contain "-Path" + } + + it 'Should find the closest positional parameter match' { + $TestString = @' +function Verb-Noun +{ + Param + ( + [Parameter(Position = 0)] + [string] + $Param1, + [Parameter(Position = 1)] + [System.Management.Automation.ActionPreference] + $Param2 + ) +} +Verb-Noun -Param1 Hello ^ +'@ + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -inputScript $TestString.Remove($CursorIndex, 1) -cursorColumn $CursorIndex + $res.CompletionMatches[0].CompletionText | Should -Be "Break" + } + + it 'Should complete command with an empty arrayexpression element' { + $res = TabExpansion2 -inputScript 'Get-ChildItem @()' -cursorColumn 1 + $res.CompletionMatches[0].CompletionText | Should -Be "Get-ChildItem" + } + + it 'Should not complete TabExpansion2 variables' { + $res = TabExpansion2 -inputScript '$' -cursorColumn 1 + $res.CompletionMatches.CompletionText | Should -Not -Contain '$positionOfCursor' + } + + it 'Should prefer the default parameterset when completing positional parameters' { + $ScriptInput = 'Get-ChildItem | Where-Object ' + $res = TabExpansion2 -inputScript $ScriptInput -cursorColumn $ScriptInput.Length + $res.CompletionMatches[0].CompletionText | Should -Be "Attributes" + } + + it 'Should complete base class members of types without type definition AST' { + $res = TabExpansion2 -inputScript @' +class InheritedClassTest : System.Attribute +{ + [void] TestMethod() + { + $this. +'@ + $res.CompletionMatches.CompletionText | Should -Contain 'TypeId' + } + + it 'Should not complete parameter aliases if the real parameter is in the completion results' { + $res = TabExpansion2 -inputScript 'Get-ChildItem -p' + $res.CompletionMatches.CompletionText | Should -Not -Contain '-proga' + $res.CompletionMatches.CompletionText | Should -Contain '-ProgressAction' + } + + it 'Should not complete parameter aliases if the real parameter is in the completion results (Non ambiguous parameters)' { + $res = TabExpansion2 -inputScript 'Get-ChildItem -prog' + $res.CompletionMatches.CompletionText | Should -Not -Contain '-proga' + $res.CompletionMatches.CompletionText | Should -Contain '-ProgressAction' + } + + It 'Should complete dynamic parameters with partial input' { + # See issue: #19498 + try + { + Push-Location function: + $res = TabExpansion2 -inputScript 'Get-ChildItem -LiteralPath $PSHOME -Fi' + $res.CompletionMatches[1].CompletionText | Should -Be '-File' + } + finally + { + Pop-Location + } + } + it 'Should complete enum class members for Enums in script text' { + $res = TabExpansion2 -inputScript 'enum Test1 {Val1};([Test1]"").' + $res.CompletionMatches.CompletionText[0] | Should -Be 'value__' + $res.CompletionMatches.CompletionText | Should -Contain 'HasFlag(' + } + Context "Script name completion" { BeforeAll { Setup -f 'install-powershell.ps1' -Content "" @@ -422,6 +1977,36 @@ Describe "TabCompletion" -Tags CI { } } + 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" @@ -546,38 +2131,192 @@ Describe "TabCompletion" -Tags CI { $res = TabExpansion2 -inputScript $beforeTab -cursorColumn $beforeTab.Length $res.CompletionMatches | Should -HaveCount $testItems.Count - # order isn't guaranteed so we'll sort them first - $completions = ($res.CompletionMatches | Sort-Object CompletionText -CaseSensitive).CompletionText -join ":" - $expected = ($testItems | Sort-Object -CaseSensitive | ForEach-Object { "./$_" }) -join ":" + # order isn't guaranteed so we'll sort them first + $completions = ($res.CompletionMatches | Sort-Object CompletionText -CaseSensitive).CompletionText -join ":" + $expected = ($testItems | Sort-Object -CaseSensitive | ForEach-Object { "./$_" }) -join ":" + + $completions | Should -BeExactly $expected + } + + It "Test case insensitive file and folder path completing for " -Skip:(!$IsLinux) -TestCases @( + @{ type = "File" ; beforeTab = "Get-Content f"; expected = "foo","Foo" }, # Get-Content passes thru to provider + @{ type = "Directory"; beforeTab = "cd f" ; expected = "Foo" } # Set-Location is aware of Files vs Folders + ) { + param ($beforeTab, $expected) + + $filePath = Join-Path $caseTestPath "foo" + $folderPath = Join-Path $caseTestPath "Foo" + New-Item -ItemType File -Path $filePath + New-Item -ItemType Directory -Path $folderPath + Push-Location $caseTestPath + $res = TabExpansion2 -inputScript $beforeTab -cursorColumn $beforeTab.Length + $res.CompletionMatches | Should -HaveCount $expected.Count + + # order isn't guaranteed so we'll sort them first + $completions = ($res.CompletionMatches | Sort-Object CompletionText -CaseSensitive).CompletionText -join ":" + $expected = ($expected | Sort-Object -CaseSensitive | ForEach-Object { "./$_" }) -join ":" + + } + + It "PSScriptRoot path completion when AST extent has file identity" { + $scriptText = '"$PSScriptRoot\BugFix.Tests"' + $tokens = $null + $scriptAst = [System.Management.Automation.Language.Parser]::ParseInput( + $scriptText, + $PSCommandPath, + [ref] $tokens, + [ref] $null) + + $cursorPosition = $scriptAst.Extent.StartScriptPosition. + GetType(). + GetMethod('CloneWithNewOffset', [System.Reflection.BindingFlags]'NonPublic, Instance'). + Invoke($scriptAst.Extent.StartScriptPosition, @($scriptText.Length - 1)) + + $res = TabExpansion2 -ast $scriptAst -tokens $tokens -positionOfCursor $cursorPosition + $res.CompletionMatches | Should -HaveCount 1 + $expectedPath = Join-Path $PSScriptRoot -ChildPath BugFix.Tests.ps1 + $res.CompletionMatches[0].CompletionText | Should -Be "`"$expectedPath`"" + } + + It "Relative path completion for using 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') + $observedResult = $res.CompletionMatches.Where({$_.CompletionText.IndexOf("&") -eq -1})[0].CompletionText + $completedText = $res.CompletionMatches.CompletionText -join "," + if ($IsWindows) { + $observedResult | Should -BeLike "$home$separator*" -Because "$completedText" + } else { + $observedResult | Should -BeLike "~$separator*" -Because "$completedText" + } + } + + It "Should use '~' as relative filter text when not followed by separator" { + $TempDirName = "~TempDir" + $TempDirPath = Join-Path -Path $TestDrive -ChildPath "~TempDir" + $TempDir = New-Item -Path $TempDirPath -ItemType Directory -Force + Push-Location -Path $TestDrive + $res = TabExpansion2 -inputScript ~ + $res.CompletionMatches[0].CompletionText | Should -Be ".${separator}${TempDirName}" + } + + It 'Escapes backtick properly for path: ' -TestCases @( + @{LiteralPath = 'BacktickTest['; BacktickSingle = 1; BacktickDouble = 2; LiteralBacktickSingle = 0; LiteralBacktickDouble = 0} + @{LiteralPath = 'BacktickTest`['; BacktickSingle = 3; BacktickDouble = 6; LiteralBacktickSingle = 1; LiteralBacktickDouble = 2} + @{LiteralPath = 'BacktickTest``['; BacktickSingle = 5; BacktickDouble = 10; LiteralBacktickSingle = 2; LiteralBacktickDouble = 4} + @{LiteralPath = 'BacktickTest$'; BacktickSingle = 0; BacktickDouble = 1; LiteralBacktickSingle = 0; LiteralBacktickDouble = 1} + @{LiteralPath = 'BacktickTest`$'; BacktickSingle = 2; BacktickDouble = 3; LiteralBacktickSingle = 1; LiteralBacktickDouble = 3} + @{LiteralPath = 'BacktickTest``$'; BacktickSingle = 4; BacktickDouble = 7; LiteralBacktickSingle = 2; LiteralBacktickDouble = 5} + ) { + param($LiteralPath, $BacktickSingle, $BacktickDouble, $LiteralBacktickSingle, $LiteralBacktickDouble) + $NewPath = Join-Path -Path $TestDrive -ChildPath $LiteralPath + $null = New-Item -Path $NewPath -Force + Push-Location $TestDrive + + $InputText = "Get-ChildItem -Path {0}.${separator}BacktickTest" + $InputTextLiteral = "Get-ChildItem -LiteralPath {0}.${separator}BacktickTest" + + $Text = (TabExpansion2 -inputScript ($InputText -f "'")).CompletionMatches[0].CompletionText + $Text.Length - $Text.Replace('`','').Length | Should -Be $BacktickSingle - $completions | Should -BeExactly $expected + $Text = (TabExpansion2 -inputScript ($InputText -f '"')).CompletionMatches[0].CompletionText + $Text.Length - $Text.Replace('`','').Length | Should -Be $BacktickDouble + + $Text = (TabExpansion2 -inputScript ($InputTextLiteral -f "'")).CompletionMatches[0].CompletionText + $Text.Length - $Text.Replace('`','').Length | Should -Be $LiteralBacktickSingle + + $Text = (TabExpansion2 -inputScript ($InputTextLiteral -f '"')).CompletionMatches[0].CompletionText + $Text.Length - $Text.Replace('`','').Length | Should -Be $LiteralBacktickDouble + + Remove-Item -LiteralPath $LiteralPath } - It "Test case insensitive file and folder path completing for " -Skip:(!$IsLinux) -TestCases @( - @{ type = "File" ; beforeTab = "Get-Content f"; expected = "foo","Foo" }, # Get-Content passes thru to provider - @{ type = "Directory"; beforeTab = "cd f" ; expected = "Foo" } # Set-Location is aware of Files vs Folders - ) { - param ($beforeTab, $expected) + 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 + } - $filePath = Join-Path $caseTestPath "foo" - $folderPath = Join-Path $caseTestPath "Foo" - New-Item -ItemType File -Path $filePath - New-Item -ItemType Directory -Path $folderPath - Push-Location $caseTestPath - $res = TabExpansion2 -inputScript $beforeTab -cursorColumn $beforeTab.Length - $res.CompletionMatches | Should -HaveCount $expected.Count + 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 + } - # order isn't guaranteed so we'll sort them first - $completions = ($res.CompletionMatches | Sort-Object CompletionText -CaseSensitive).CompletionText -join ":" - $expected = ($expected | Sort-Object -CaseSensitive | ForEach-Object { "./$_" }) -join ":" + 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) { + $Res = TabExpansion2 -inputScript 'Get-ChildItem //localhost/c$/Windows' + $Res.CompletionMatches[0].CompletionText | Should -Be "'\\localhost\c$\Windows'" + } + + It 'Should keep custom drive names when completing file paths' { + $TempDriveName = "asdf" + $null = New-PSDrive -Name $TempDriveName -PSProvider FileSystem -Root $HOME + + $completions = (TabExpansion2 -inputScript "${TempDriveName}:\") + # select the first answer which does not have a space in the completion (those completions look like & '3D Objects') + $observedResult = $completions.CompletionMatches.Where({$_.CompletionText.IndexOf("&") -eq -1})[0].CompletionText + $completedText = $completions.CompletionMatches.CompletionText -join "," + + $observedResult | Should -BeLike "${TempDriveName}:*" -Because "$completionText" + Remove-PSDrive -Name $TempDriveName + } + Context "Cmdlet name completion" { BeforeAll { $testCases = @( - @{ inputStr = "get-c*item"; expected = "Get-ChildItem" } + @{ inputStr = "get-ch*item"; expected = "Get-ChildItem" } @{ inputStr = "set-alia?"; expected = "Set-Alias" } @{ inputStr = "s*-alias"; expected = "Set-Alias" } @{ inputStr = "se*-alias"; expected = "Set-Alias" } @@ -627,7 +2366,7 @@ Describe "TabCompletion" -Tags CI { @{ inputStr = '[math].G'; expected = 'GenericParameterAttributes'; setup = $null } @{ inputStr = '[Environment+specialfolder]::App'; expected = 'ApplicationData'; setup = $null } @{ inputStr = 'icm {get-pro'; expected = 'Get-Process'; setup = $null } - @{ inputStr = 'write-ouput (get-pro'; expected = 'Get-Process'; setup = $null } + @{ inputStr = 'write-output (get-pro'; expected = 'Get-Process'; setup = $null } @{ inputStr = 'iex "get-pro'; expected = '"Get-Process"'; setup = $null } @{ inputStr = '$variab'; expected = '$variableA'; setup = { $variableB = 2; $variableA = 1 } } @{ inputStr = 'a -'; expected = '-keys'; setup = { function a {param($keys) $a} } } @@ -677,6 +2416,7 @@ Describe "TabCompletion" -Tags CI { @{ 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 } @@ -698,7 +2438,7 @@ Describe "TabCompletion" -Tags CI { ## if $PSHOME contains a space tabcompletion adds ' around the path @{ inputStr = 'cd $PSHOME\Modu'; expected = if($PSHOME.Contains(' ')) { "'$(Join-Path $PSHOME 'Modules')'" } else { Join-Path $PSHOME 'Modules' }; setup = $null } @{ inputStr = 'cd "$PSHOME\Modu"'; expected = "`"$(Join-Path $PSHOME 'Modules')`""; setup = $null } - @{ inputStr = '$PSHOME\System.Management.Au'; expected = if($PSHOME.Contains(' ')) { "`& '$(Join-Path $PSHOME 'System.Management.Automation.dll')'" } else { Join-Path $PSHOME 'System.Management.Automation.dll'; Setup = $null }} + @{ inputStr = '$PSHOME\System.Management.Au'; expected = if($PSHOME.Contains(' ')) { "`& '$(Join-Path $PSHOME 'System.Management.Automation.dll')'" } else { Join-Path $PSHOME 'System.Management.Automation.dll'}; Setup = $null } @{ inputStr = '"$PSHOME\System.Management.Au"'; expected = "`"$(Join-Path $PSHOME 'System.Management.Automation.dll')`""; setup = $null } @{ inputStr = '& "$PSHOME\System.Management.Au"'; expected = "`"$(Join-Path $PSHOME 'System.Management.Automation.dll')`""; setup = $null } ## tab completion AST-based tests @@ -713,7 +2453,7 @@ Describe "TabCompletion" -Tags CI { @{ inputStr = '[System.Management.Automation.Runspaces.runspacef'; expected = 'System.Management.Automation.Runspaces.RunspaceFactory'; setup = $null } @{ inputStr = '[specialfol'; expected = 'System.Environment+SpecialFolder'; setup = $null } ## tab completion for variable names in '{}' - @{ inputStr = '${PSDefault'; expected = '$PSDefaultParameterValues'; setup = $null } + @{ inputStr = '${PSDefault'; expected = '${PSDefaultParameterValues}'; setup = $null } ) } @@ -727,17 +2467,30 @@ Describe "TabCompletion" -Tags CI { } It "Tab completion UNC path" -Skip:(!$IsWindows) { - $homeDrive = $env:HOMEDRIVE.Replace(":", "$") - $beforeTab = "\\localhost\$homeDrive\wind" - $afterTab = "& '\\localhost\$homeDrive\Windows'" + $beforeTab = "\\localhost\ADMIN$\boo" + $afterTab = "& '\\localhost\ADMIN$\Boot'" $res = TabExpansion2 -inputScript $beforeTab -cursorColumn $beforeTab.Length $res.CompletionMatches.Count | Should -BeGreaterThan 0 $res.CompletionMatches[0].CompletionText | Should -BeExactly $afterTab } + It "Tab completion UNC path with forward slashes" -Skip:(!$IsWindows) { + $beforeTab = "//localhost/admin" + # it is expected that tab completion turns forward slashes into backslashes + $afterTab = "\\localhost\ADMIN$" + $res = TabExpansion2 -inputScript $beforeTab -cursorColumn $beforeTab.Length + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $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' - $afterTab = 'registry::HKEY_LOCAL_MACHINE' + $afterTab = 'Registry::HKEY_LOCAL_MACHINE' $res = TabExpansion2 -inputScript $beforeTab -cursorColumn $beforeTab.Length $res.CompletionMatches | Should -HaveCount 1 $res.CompletionMatches[0].CompletionText | Should -BeExactly $afterTab @@ -745,7 +2498,7 @@ Describe "TabCompletion" -Tags CI { It "Tab completion for wsman provider" -Skip:(!$IsWindows) { $beforeTab = 'wsman::localh' - $afterTab = 'wsman::localhost' + $afterTab = 'WSMan::localhost' $res = TabExpansion2 -inputScript $beforeTab -cursorColumn $beforeTab.Length $res.CompletionMatches | Should -HaveCount 1 $res.CompletionMatches[0].CompletionText | Should -BeExactly $afterTab @@ -758,7 +2511,7 @@ Describe "TabCompletion" -Tags CI { New-Item -ItemType Directory -Path "$tempFolder/helloworld" > $null $tempFolder | Should -Exist $beforeTab = 'filesystem::{0}hello' -f $tempFolder - $afterTab = 'filesystem::{0}helloworld' -f $tempFolder + $afterTab = 'FileSystem::{0}helloworld' -f $tempFolder $res = TabExpansion2 -inputScript $beforeTab -cursorColumn $beforeTab.Length $res.CompletionMatches.Count | Should -BeGreaterThan 0 $res.CompletionMatches[0].CompletionText | Should -BeExactly $afterTab @@ -836,6 +2589,21 @@ Describe "TabCompletion" -Tags CI { $res.CompletionMatches[1].CompletionText | Should -BeExactly 'dog' } + It "Tab completion for validateSet attribute takes precedence over enums" { + function foo { param([ValidateSet('DarkBlue','DarkCyan')][ConsoleColor]$p) } + $inputStr = "foo " + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + $res.CompletionMatches | Should -HaveCount 2 + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'DarkBlue' + $res.CompletionMatches[1].CompletionText | Should -BeExactly 'DarkCyan' + } + + It "Tab completion for attribute type" { + $inputStr = '[validateset()]$var1' + $res = TabExpansion2 -inputScript $inputStr -cursorColumn 2 + $res.CompletionMatches.CompletionText | Should -Contain 'ValidateSet' + } + It "Tab completion for ArgumentCompleter when AST is passed to CompleteInput" { $scriptBl = { function Test-Completion { @@ -876,6 +2644,46 @@ Describe "TabCompletion" -Tags CI { $res.CompletionMatches[1].CompletionText | Should -BeExactly 'dog' } + It "Tab completion for enum members after colon with space" -TestCases @( + @{ Space = 0 } + @{ Space = 1 } + ) { + param ($Space) + $inputStr = "Get-Command -Type:$(' ' * $Space)Al" + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + $res.CompletionMatches | Should -HaveCount 2 + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Alias' + $res.CompletionMatches[1].CompletionText | Should -BeExactly 'All' + } + + It "Tab completion for enum members between colon with space and space with value" -TestCases @( + @{ LeftSpace = 0; RightSpace = 0 } + @{ LeftSpace = 0; RightSpace = 1 } + @{ LeftSpace = 1; RightSpace = 0 } + @{ LeftSpace = 1; RightSpace = 1 } + ) { + param ($LeftSpace, $RightSpace) + $inputStrEndsWithCursor = "Get-Command -Type:$(' ' * $LeftSpace)" + $inputStr = $inputStrEndsWithCursor + "$(' ' * $RightSpace)Alias" + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStrEndsWithCursor.Length + $expectedArray = [enum]::GetNames([System.Management.Automation.CommandTypes]) | Sort-Object + $res.CompletionMatches.CompletionText | Should -Be $expectedArray + } + + It "Tab completion for enum members between comma with space and space with parameter" -TestCases @( + @{ LeftSpace = 0; RightSpace = 0 } + @{ LeftSpace = 0; RightSpace = 1 } + @{ LeftSpace = 1; RightSpace = 0 } + @{ LeftSpace = 1; RightSpace = 1 } + ) { + param ($LeftSpace, $RightSpace) + $inputStrEndsWithCursor = "Get-Command -Type Alias,$(' ' * $LeftSpace)" + $inputStr = $inputStrEndsWithCursor + "$(' ' * $RightSpace)-All" + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStrEndsWithCursor.Length + $expectedArray = [enum]::GetNames([System.Management.Automation.CommandTypes]) | Sort-Object + $res.CompletionMatches.CompletionText | Should -Be $expectedArray + } + It "Tab completion for enum members after comma" { $inputStr = "Get-Command -Type Alias,c" $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length @@ -884,6 +2692,36 @@ Describe "TabCompletion" -Tags CI { $res.CompletionMatches[1].CompletionText | Should -BeExactly 'Configuration' } + It 'Tab completion for enum parameter is filtered against ' -TestCases @( + @{ Name = 'ValidateRange with enum-values'; Attribute = '[ValidateRange([System.ConsoleColor]::Blue, [System.ConsoleColor]::Cyan)]' } + @{ Name = 'ValidateRange with int-values'; Attribute = '[ValidateRange(9, 11)]' } + @{ Name = 'multiple ValidateRange-attributes'; Attribute = '[ValidateRange([System.ConsoleColor]::Blue, [System.ConsoleColor]::Cyan)][ValidateRange([System.ConsoleColor]::Gray, [System.ConsoleColor]::Red)]' } + ) { + param($Name, $Attribute) + $functionDefinition = 'param ( {0}[consolecolor]$color )' -f $Attribute + Set-Item -Path function:baz -Value $functionDefinition + $inputStr = 'baz -color ' + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + $res.CompletionMatches | Should -HaveCount 3 + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Blue' + $res.CompletionMatches[1].CompletionText | Should -BeExactly 'Cyan' + $res.CompletionMatches[2].CompletionText | Should -BeExactly 'Green' + } + + It 'Tab completion for enum parameter is filtered with ValidateRange using rangekind' { + $functionDefinition = 'param ( [ValidateRange([System.Management.Automation.ValidateRangeKind]::NonPositive)][consolecolor]$color )' -f $Attribute + Set-Item -Path function:baz -Value $functionDefinition + $inputStr = 'baz -color ' + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Black' # 0 = NonPositive + } + + It 'Tab completion of $_ inside incomplete switch condition' { + $res = TabExpansion2 -inputScript 'Get-PSDrive | Sort-Object -Property {switch ($_.nam' + $res.CompletionMatches[0].CompletionText | Should -Be 'Name' + } + It "Test [CommandCompletion]::GetNextResult" { $inputStr = "Get-Command -Type Alias,c" $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length @@ -908,6 +2746,75 @@ Describe "TabCompletion" -Tags CI { $res.CompletionMatches[0].CompletionText | Should -BeExactly "Test history completion" } + It "Test #requires parameter completion" { + $res = TabExpansion2 -inputScript "#requires -" -cursorColumn 11 + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches[0].CompletionText | Should -BeExactly "Modules" + } + + It "Test #requires parameter value completion" { + $res = TabExpansion2 -inputScript "#requires -PSEdition " -cursorColumn 21 + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches[0].CompletionText | Should -BeExactly "Core" + } + + It "Test no completion after #requires -RunAsAdministrator" { + $res = TabExpansion2 -inputScript "#requires -RunAsAdministrator -" -cursorColumn 31 + $res.CompletionMatches | Should -HaveCount 0 + } + + It "Test no suggestions for already existing parameters in #requires" { + $res = TabExpansion2 -inputScript "#requires -Modules -" -cursorColumn 20 + $res.CompletionMatches.CompletionText | Should -Not -Contain "Modules" + } + + It "Test module completion in #requires without quotes" { + $res = TabExpansion2 -inputScript "#requires -Modules P" -cursorColumn 20 + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches.CompletionText | Should -Contain "Pester" + } + + It "Test module completion in #requires with quotes" { + $res = TabExpansion2 -inputScript '#requires -Modules "' -cursorColumn 20 + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches.CompletionText | Should -Contain "Pester" + } + + It "Test module completion in #requires with multiple modules" { + $res = TabExpansion2 -inputScript "#requires -Modules Pester," -cursorColumn 26 + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches.CompletionText | Should -Contain "Pester" + } + + It "Test hashtable key completion in #requires statement for modules" { + $res = TabExpansion2 -inputScript "#requires -Modules @{" + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches[0].CompletionText | Should -BeExactly "GUID" + } + + It "Test no suggestions for already existing hashtable keys in #requires statement for modules" { + $res = TabExpansion2 -inputScript '#requires -Modules @{ModuleName="Pester";' -cursorColumn 41 + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches.CompletionText | Should -Not -Contain "ModuleName" + } + + It "Test no suggestions for mutually exclusive hashtable keys in #requires statement for modules" { + $res = TabExpansion2 -inputScript '#requires -Modules @{ModuleName="Pester";RequiredVersion="1.0";' -cursorColumn 63 + $res.CompletionMatches.CompletionText | Should -BeExactly "GUID" + } + + It "Test no suggestions for RequiredVersion key in #requires statement when ModuleVersion is specified" { + $res = TabExpansion2 -inputScript '#requires -Modules @{ModuleName="Pester";ModuleVersion="1.0";' -cursorColumn 61 + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches.CompletionText | Should -Not -Contain "RequiredVersion" + } + + It "Test module completion in #requires statement for hashtables" { + $res = TabExpansion2 -inputScript '#requires -Modules @{ModuleName="p' -cursorColumn 34 + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches.CompletionText | Should -Contain "Pester" + } + It "Test Attribute member completion" { $inputStr = "function bar { [parameter(]param() }" $res = TabExpansion2 -inputScript $inputStr -cursorColumn ($inputStr.IndexOf('(') + 1) @@ -915,14 +2822,63 @@ Describe "TabCompletion" -Tags CI { $entry = $res.CompletionMatches | Where-Object CompletionText -EQ "Position" $entry.CompletionText | Should -BeExactly "Position" } + It "Test Attribute member completion multiple members" { $inputStr = "function bar { [parameter(Position,]param() }" $res = TabExpansion2 -inputScript $inputStr -cursorColumn ($inputStr.IndexOf(',') + 1) - $res.CompletionMatches | Should -HaveCount 10 + $res.CompletionMatches | Should -HaveCount 9 $entry = $res.CompletionMatches | Where-Object CompletionText -EQ "Mandatory" $entry.CompletionText | Should -BeExactly "Mandatory" } + It "Should complete member in attribute argument value" { + $inputStr = '[ValidateRange(1,[int]::Maxva^)]$a' + $CursorIndex = $inputStr.IndexOf('^') + $res = TabExpansion2 -cursorColumn $CursorIndex -inputScript $inputStr.Remove($CursorIndex, 1) + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly "MaxValue" + } + + It "Test Attribute scriptblock completion" { + $inputStr = '[ValidateScript({Get-Child})]$Test=ls' + $res = TabExpansion2 -inputScript $inputStr -cursorColumn ($inputStr.IndexOf('}')) + $res.CompletionMatches | Should -HaveCount 1 + $entry = $res.CompletionMatches | Where-Object CompletionText -EQ "Get-ChildItem" + $entry.CompletionText | Should -BeExactly "Get-ChildItem" + } + + It '' -TestCases @( + @{ + Intent = 'Complete attribute members on empty line' + Expected = @('Position','ParameterSetName','Mandatory','ValueFromPipeline','ValueFromPipelineByPropertyName','ValueFromRemainingArguments','HelpMessage','HelpMessageBaseName','HelpMessageResourceId','DontShow') + TestString = @' +function bar { [parameter( + + +^ + + )]param() } +'@ + } + @{ + Intent = 'Complete attribute members on empty line with preceding member' + Expected = @('Position','ParameterSetName','Mandatory','ValueFromPipeline','ValueFromPipelineByPropertyName','ValueFromRemainingArguments','HelpMessage','HelpMessageBaseName','HelpMessageResourceId','DontShow') + TestString = @' +function bar { [parameter( +Mandatory, + +^ + + )]param() } +'@ + } + ){ + param($Expected, $TestString) + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -cursorColumn $CursorIndex -inputScript $TestString.Remove($CursorIndex, 1) + $res.CompletionMatches[0].CompletionText | Should -BeIn $Expected + } + It "Test completion with line continuation" { $inputStr = @' dir -Recurse ` @@ -950,8 +2906,99 @@ dir -Recurse ` It "Test completion with exact match" { $inputStr = 'get-content -wa' $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + $res.CompletionMatches | Should -HaveCount 3 + [string]::Join(',', ($res.CompletionMatches.completiontext | Sort-Object)) | Should -BeExactly "-Wait,-WarningAction,-WarningVariable" + } + + It "Test completion with splatted variable" { + $inputStr = 'Get-Content @Splat -P' + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + $res.CompletionMatches | Should -HaveCount 4 + [string]::Join(',', ($res.CompletionMatches.completiontext | Sort-Object)) | Should -BeExactly "-Path,-PipelineVariable,-ProgressAction,-PSPath" + } + + It "Test completion for HttpVersion parameter name" { + $inputStr = 'Invoke-WebRequest -HttpV' + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly "-HttpVersion" + } + + It "Test completion for HttpVersion parameter" { + $inputStr = 'Invoke-WebRequest -HttpVersion ' + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length $res.CompletionMatches | Should -HaveCount 4 - [string]::Join(',', ($res.CompletionMatches.completiontext | Sort-Object)) | Should -BeExactly "-wa,-Wait,-WarningAction,-WarningVariable" + [string]::Join(',', ($res.CompletionMatches.completiontext | Sort-Object)) | Should -BeExactly "1.0,1.1,2.0,3.0" + } + + It "Test completion for HttpVersion parameter with input" { + $inputStr = 'Invoke-WebRequest -HttpVersion 1' + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + $res.CompletionMatches | Should -HaveCount 2 + [string]::Join(',', ($res.CompletionMatches.completiontext | Sort-Object)) | Should -BeExactly "1.0,1.1" + } + + It 'Should complete Select-Object properties without duplicates' { + $res = TabExpansion2 -inputScript '$PSVersionTable | Select-Object -Property Count,' + $res.CompletionMatches.CompletionText | Should -Not -Contain "Count" + } + + It '' -TestCases @( + @{ + Intent = 'Complete loop labels with no input' + Expected = 'Outer','Inner' + TestString = ':Outer while ($true){:Inner while ($true){ break ^ }}' + } + @{ + Intent = 'Complete loop labels that are accessible' + Expected = 'Outer' + TestString = ':Outer do {:Inner while ($true){ break } continue ^ } until ($false)' + } + @{ + Intent = 'Complete loop labels with partial input' + Expected = 'Outer' + TestString = ':Outer do {:Inner while ($true){ break } continue o^ut } while ($true)' + } + @{ + Intent = 'Complete loop label for incomplete switch' + Expected = 'Outer' + TestString = ':Outer switch ($x){"randomValue"{ continue ^' + } + @{ + Intent = 'Complete loop label for incomplete do loop' + Expected = 'Outer' + TestString = ':Outer do {:Inner while ($true){ break } continue ^' + } + @{ + Intent = 'Complete loop label for incomplete for loop' + Expected = 'forLoop' + TestString = ':forLoop for ($i = 0; $i -lt $SomeCollection.Count; $i++) {continue ^' + } + @{ + Intent = 'Complete loop label for incomplete while loop' + Expected = 'WhileLoop' + TestString = ':WhileLoop while ($true){ break ^' + } + @{ + Intent = 'Complete loop label for incomplete foreach loop' + Expected = 'foreachLoop' + TestString = ':foreachLoop foreach ($x in $y) { break ^' + } + @{ + Intent = 'Not Complete loop labels with colon' + Expected = $null + TestString = ':Outer foreach ($x in $y){:Inner for ($i = 0; $i -lt $X.Count; $i++){ break :O^}}' + } + @{ + Intent = 'Not Complete loop labels if cursor is in front of existing label' + Expected = $null + TestString = ':Outer switch ($x){"Value1"{break ^ Outer}}' + } + ){ + param($Expected, $TestString) + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -cursorColumn $CursorIndex -inputScript $TestString.Remove($CursorIndex, 1) + $res.CompletionMatches.CompletionText | Should -BeExactly $Expected } } @@ -970,7 +3017,7 @@ dir -Recurse ` } It "Test complete module file name" { - $inputStr = "using module test" + $inputStr = "using module testm" $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length $res.CompletionMatches | Should -HaveCount 1 $res.CompletionMatches[0].CompletionText | Should -BeExactly ".${separator}testModule.psm1" @@ -1022,6 +3069,21 @@ dir -Recurse ` $res.CompletionMatches[0].CompletionText | Should -BeExactly $expected } + It "Tab completion for file array element between comma with space and space with parameter" -TestCases @( + @{ LeftSpace = 0; RightSpace = 0 } + @{ LeftSpace = 0; RightSpace = 1 } + @{ LeftSpace = 1; RightSpace = 0 } + @{ LeftSpace = 1; RightSpace = 1 } + ) { + param ($LeftSpace, $RightSpace) + $inputStrEndsWithCursor = "dir .\commaA.txt,$(' ' * $LeftSpace)" + $inputStr = $inputStrEndsWithCursor + "$(' ' * $RightSpace)-File" + $expected = ".${separator}commaA.txt" + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStrEndsWithCursor.Length + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly $expected + } + It "Test comma with Enum array element" { $inputStr = "gcm -CommandType Cmdlet," $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length @@ -1112,31 +3174,6 @@ dir -Recurse ` } } - Context "User-overridden TabExpansion implementations" { - It "Override TabExpansion with function" { - function TabExpansion ($line, $lastword) { - "Overridden-TabExpansion-Function" - } - - $inputStr = '$PID.' - $res = [System.Management.Automation.CommandCompletion]::CompleteInput($inputStr, $inputst.Length, $null) - $res.CompletionMatches | Should -HaveCount 1 - $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Overridden-TabExpansion-Function' - } - - It "Override TabExpansion with alias" { - function OverrideTabExpansion ($line, $lastword) { - "Overridden-TabExpansion-Alias" - } - Set-Alias -Name TabExpansion -Value OverrideTabExpansion - - $inputStr = '$PID.' - $res = [System.Management.Automation.CommandCompletion]::CompleteInput($inputStr, $inputst.Length, $null) - $res.CompletionMatches | Should -HaveCount 1 - $res.CompletionMatches[0].CompletionText | Should -BeExactly "Overridden-TabExpansion-Alias" - } - } - Context "No tab completion tests" { BeforeAll { $testCases = @( @@ -1152,6 +3189,13 @@ dir -Recurse ` $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length $res.CompletionMatches | Should -BeNullOrEmpty } + + It "A single dash should not complete to anything" { + function test-{} + $inputStr = 'git -' + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + $res.CompletionMatches | Should -BeNullOrEmpty + } } Context "Tab completion error tests" { @@ -1176,6 +3220,14 @@ dir -Recurse ` param($inputStr, $expected) $inputStr | Should -Throw -ErrorId $expected } + + It "Should not throw errors in tab completion with empty input string" { + {[System.Management.Automation.CommandCompletion]::CompleteInput("", 0, $null)} | Should -Not -Throw + } + + It "Should not throw errors in tab completion with empty input ast" { + {[System.Management.Automation.CommandCompletion]::CompleteInput({}.Ast, @(), {}.Ast.Extent.StartScriptPosition, $null)} | Should -Not -Throw + } } Context "DSC tab completion tests" { @@ -1213,6 +3265,11 @@ dir -Recurse ` It "Input '' should successfully complete" -TestCases $testCases -Skip:(!$IsWindows) { param($inputStr, $expected) + if (Test-IsWindowsArm64) { + Set-ItResult -Pending -Because "TBD" + } + + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length $res.CompletionMatches.Count | Should -BeGreaterThan 0 $res.CompletionMatches[0].CompletionText | Should -BeExactly $expected @@ -1247,6 +3304,18 @@ dir -Recurse ` @{ inputStr = '[Microsoft.Management.Infrastructure.CimClass]$c = $null; $c.CimClassNam'; expected = 'CimClassName' } @{ inputStr = '[Microsoft.Management.Infrastructure.CimClass]$c = $null; $c.CimClassName.Substrin'; expected = 'Substring(' } @{ inputStr = 'Get-CimInstance -ClassName Win32_Process | %{ $_.ExecutableP'; expected = 'ExecutablePath' } + @{ inputStr = 'Get-CimInstance -ClassName Win32_Process | Invoke-CimMethod -MethodName SetPriority -Arguments @{'; expected = 'Priority' } + @{ inputStr = 'Get-CimInstance -ClassName Win32_Service | Invoke-CimMethod -MethodName Change -Arguments @{d'; expected = 'DesktopInteract' } + @{ inputStr = 'Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{'; expected = 'CommandLine' } + @{ inputStr = 'New-CimInstance Win32_Environment -Property @{'; expected = 'Caption' } + @{ inputStr = 'Get-CimInstance Win32_Environment | Set-CimInstance -Property @{'; expected = 'Name' } + @{ inputStr = 'Set-CimInstance -Namespace root/CIMV'; expected = 'root/CIMV2' } + @{ inputStr = 'Get-CimInstance Win32_Process -Property '; expected = 'Caption' } + @{ inputStr = 'Get-CimInstance Win32_Process -Property Caption,'; expected = 'Description' } + ) + $FailCases = @( + @{ inputStr = "Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments " } + @{ inputStr = "New-CimInstance Win32_Process -Property " } ) } @@ -1257,26 +3326,37 @@ dir -Recurse ` $res.CompletionMatches.Count | Should -BeGreaterThan 0 $res.CompletionMatches[0].CompletionText | Should -Be $expected } + + It "CIM cmdlet input '' should not successfully complete" -TestCases $FailCases -Skip:(!$IsWindows) { + param($inputStr) + + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + $res.CompletionMatches[0].ResultType | should -Not -Be 'Property' + } } Context "Module cmdlet completion tests" { It "ArugmentCompleter for PSEdition should work for ''" -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 ' ') } } Context "Tab completion help test" { BeforeAll { - if ([System.Management.Automation.Platform]::IsWindows) { + New-Item -ItemType File (Join-Path ${TESTDRIVE} "pwsh.xml") + if ($IsWindows) { $userHelpRoot = Join-Path $HOME "Documents/PowerShell/Help/" } else { $userModulesRoot = [System.Management.Automation.Platform]::SelectProductNameForDirectory([System.Management.Automation.Platform+XDG_Type]::USER_MODULES) @@ -1285,29 +3365,331 @@ dir -Recurse ` } It 'Should complete about help topic' { - $aboutHelpPathUserScope = Join-Path $userHelpRoot (Get-Culture).Name - $aboutHelpPathAllUsersScope = Join-Path $PSHOME (Get-Culture).Name + $helpName = "about_Splatting" + $helpFileName = "${helpName}.help.txt" + $inputScript = "get-help about_spla" + $culture = "en-US" + $aboutHelpPathUserScope = Join-Path $userHelpRoot $culture + $aboutHelpPathAllUsersScope = Join-Path $PSHOME $culture + $expectedCompletionCount = 0 ## If help content does not exist, tab completion will not work. So update it first. - $userScopeHelp = Test-Path (Join-Path $aboutHelpPathUserScope "about_Splatting.help.txt") - $allUserScopeHelp = Test-Path (Join-Path $aboutHelpPathAllUsersScope "about_Splatting.help.txt") - if ((-not $userScopeHelp) -and (-not $aboutHelpPathAllUsersScope)) { + $userHelpPath = Join-Path $aboutHelpPathUserScope $helpFileName + $userScopeHelp = Test-Path $userHelpPath + if ($userScopeHelp) { + $expectedCompletionCount++ + } else { Update-Help -Force -ErrorAction SilentlyContinue -Scope 'CurrentUser' + if (Test-Path $userHelpPath) { + $expectedCompletionCount++ + } + } + + $allUserScopeHelpPath = Test-Path (Join-Path $aboutHelpPathAllUsersScope $helpFileName) + if ($allUserScopeHelpPath) { + $expectedCompletionCount++ + } + + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $inputScript.Length + $res.CompletionMatches | Should -HaveCount $expectedCompletionCount + $res.CompletionMatches[0].CompletionText | Should -BeExactly $helpName + } + + It 'Should complete about help topic regardless of culture' { + try + { + ## Save original culture and temporarily set it to da-DK because there's no localized help for da-DK. + $OriginalCulture = [cultureinfo]::CurrentCulture + $defaultCulture = "en-US" + $culture = "da-DK" + [cultureinfo]::CurrentCulture = $culture + $helpName = "about_Splatting" + $helpFileName = "${helpName}.help.txt" + + $aboutHelpPathUserScope = Join-Path $userHelpRoot $culture + $aboutHelpPathAllUsersScope = Join-Path $PSHOME $culture + $expectedCompletionCount = 0 + + ## If help content does not exist, tab completion will not work. So update it first. + $userHelpPath = Join-Path $aboutHelpPathUserScope $helpFileName + $userScopeHelp = Test-Path $userHelpPath + if ($userScopeHelp) { + $expectedCompletionCount++ + } + else { Update-Help -Force -ErrorAction SilentlyContinue -Scope 'CurrentUser' + if (Test-Path $userHelpPath) { + $expectedCompletionCount++ + } + else { + $aboutHelpPathUserScope = Join-Path $userHelpRoot $defaultCulture + $aboutHelpPathAllUsersScope = Join-Path $PSHOME $defaultCulture + $userHelpDefaultPath = Join-Path $aboutHelpPathUserScope $helpFileName + $userDefaultScopeHelp = Test-Path $userHelpDefaultPath + + if ($userDefaultScopeHelp) { + $expectedCompletionCount++ + } + } + } + + $allUserScopeHelpPath = Test-Path (Join-Path $aboutHelpPathAllUsersScope $helpFileName) + if ($allUserScopeHelpPath) { + $expectedCompletionCount++ + } + else { + $aboutHelpPathAllUsersDefaultScope = Join-Path $PSHOME $defaultCulture + $allUsersDefaultScopeHelpPath = Test-Path (Join-Path $aboutHelpPathAllUsersDefaultScope $helpFileName) + + if ($allUsersDefaultScopeHelpPath) { + $expectedCompletionCount++ + } + } + + $res = TabExpansion2 -inputScript 'get-help about_spla' -cursorColumn 'get-help about_spla'.Length + $res.CompletionMatches | Should -HaveCount $expectedCompletionCount + $res.CompletionMatches[0].CompletionText | Should -BeExactly $helpName + } + finally + { + [cultureinfo]::CurrentCulture = $OriginalCulture + } + } + It '' -TestCases @( + @{ + Intent = 'Complete help keywords with minimal input' + Expected = @( + "COMPONENT", + "DESCRIPTION", + "EXAMPLE", + "EXTERNALHELP", + "FORWARDHELPCATEGORY", + "FORWARDHELPTARGETNAME", + "FUNCTIONALITY", + "INPUTS", + "LINK", + "NOTES", + "OUTPUTS", + "PARAMETER", + "REMOTEHELPRUNSPACE", + "ROLE", + "SYNOPSIS" + ) + TestString = @' +<# +.^ +#> +'@ + } + @{ + Intent = 'Complete help keywords without duplicates' + Expected = $null + TestString = @' +<# +.SYNOPSIS +.S^ +#> +'@ + } + @{ + Intent = 'Complete help keywords with allowed duplicates' + Expected = 'PARAMETER' + TestString = @' +<# +.PARAMETER +.Paramet^ +#> +'@ + } + @{ + Intent = 'Complete help keyword FORWARDHELPTARGETNAME argument' + Expected = 'Get-ChildItem' + TestString = @' +<# +.FORWARDHELPTARGETNAME Get-Child^ +#> +'@ + } + @{ + Intent = 'Complete help keyword FORWARDHELPCATEGORY argument' + Expected = 'Cmdlet' + TestString = @' +<# +.FORWARDHELPCATEGORY C^ +#> +'@ + } + @{ + Intent = 'Complete help keyword REMOTEHELPRUNSPACE argument' + Expected = 'PSEdition' + TestString = @' +<# +.REMOTEHELPRUNSPACE PSEditi^ +#> +'@ + } + @{ + Intent = 'Complete help keyword EXTERNALHELP argument' + Expected = Join-Path $TESTDRIVE "pwsh.xml" + TestString = @" +<# +.EXTERNALHELP $TESTDRIVE\pwsh.^ +#> +"@ + } + @{ + Intent = 'Complete help keyword PARAMETER argument for script' + Expected = 'Param1' + TestString = @' +<# +.PARAMETER ^ +#> +param($Param1) +'@ + } + @{ + Intent = 'Complete help keyword PARAMETER argument for function with help inside' + Expected = 'param2' + TestString = @' +function MyFunction ($param1, $param2) +{ +<# +.PARAMETER param1 +.PARAMETER ^ +#> +} +'@ } + @{ + Intent = 'Complete help keyword PARAMETER argument for function with help before it' + Expected = 'param1','param2' + TestString = @' +<# +.PARAMETER ^ +#> +function MyFunction ($param1, $param2) +{ +} +'@ + } + @{ + Intent = 'Complete help keyword PARAMETER argument for advanced function with help inside' + Expected = 'Param1' + TestString = @' +function Verb-Noun +{ +<# +.PARAMETER ^ +#> + [CmdletBinding()] + Param + ( + $Param1 + ) + + Begin + { + } + Process + { + } + End + { + } +} +'@ + } + @{ + Intent = 'Complete help keyword PARAMETER argument for nested function with help before it' + Expected = 'param3','param4' + TestString = @' +function MyFunction ($param1, $param2) +{ + <# + .PARAMETER ^ + #> + function MyFunction2 ($param3, $param4) + { + } +} +'@ + } + @{ + Intent = 'Complete help keyword PARAMETER argument for function inside advanced function' + Expected = 'param1','param2' + TestString = @' +function Verb-Noun +{ + Param + ( + [Parameter()] + [string[]] + $ParamA + ) + Begin + { + <# + .Parameter ^ + #> + function MyFunction ($param1, $param2) + { + } + } +} +'@ + } + @{ + Intent = 'Not complete help keyword PARAMETER argument if following function is too far away' + Expected = $null + TestString = @' +<# +.PARAMETER ^ +#> + + +function MyFunction ($param1, $param2) +{ +} +'@ + } + ){ + param($Expected, $TestString) + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -cursorColumn $CursorIndex -inputScript $TestString.Remove($CursorIndex, 1) + $res.CompletionMatches.CompletionText | Should -BeExactly $Expected + } + } + + 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 + } - # If help content is present on both scopes, expect 2 or else expect 1 completion. - $expectedCompletions = if ($userScopeHelp -and $allUserScopeHelp) { 2 } else { 1 } + It 'Should not fallback to file completion when completing typenames' { + $Text = '[abcdefghijklmnopqrstuvwxyz]' + $res = TabExpansion2 -inputScript $Text -cursorColumn ($Text.Length - 1) + $res.CompletionMatches | Should -HaveCount 0 + } +} - $res = TabExpansion2 -inputScript 'get-help about_spla' -cursorColumn 'get-help about_spla'.Length - $res.CompletionMatches | Should -HaveCount $expectedCompletions - $res.CompletionMatches[0].CompletionText | Should -BeExactly 'about_Splatting' +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 { - if ($IsWindows) { + $skipTest = -not $IsWindows + $pendingTest = $IsWindows -and (Test-IsWinWow64) + + if (-not $skipTest -and -not $pendingTest) { $session = New-RemoteSession $powershell = [powershell]::Create() $powershell.Runspace = $session.Runspace @@ -1325,11 +3707,16 @@ Describe "Tab completion tests with remote Runspace" -Tags Feature,RequireAdminO ) } else { $defaultParameterValues = $PSDefaultParameterValues.Clone() - $PSDefaultParameterValues["It:Skip"] = $true + + if ($skipTest) { + $PSDefaultParameterValues["It:Skip"] = $true + } elseif ($pendingTest) { + $PSDefaultParameterValues["It:Pending"] = $true + } } } AfterAll { - if ($IsWindows) { + if (-not $skipTest -and -not $pendingTest) { Remove-PSSession $session $powershell.Dispose() } else { @@ -1397,7 +3784,7 @@ Describe "WSMan Config Provider tab complete tests" -Tags Feature,RequireAdminOn @{path = "localhost\plugin"; parameter = "-ru"; expected = "RunAsCredential"}, @{path = "localhost\plugin"; parameter = "-us"; expected = "UseSharedProcess"}, @{path = "localhost\plugin"; parameter = "-au"; expected = "AutoRestart"}, - @{path = "localhost\plugin"; parameter = "-pr"; expected = "ProcessIdleTimeoutSec"}, + @{path = "localhost\plugin"; parameter = "-proc"; expected = "ProcessIdleTimeoutSec"}, @{path = "localhost\Plugin\microsoft.powershell\Resources\"; parameter = "-re"; expected = "ResourceUri"}, @{path = "localhost\Plugin\microsoft.powershell\Resources\"; parameter = "-ca"; expected = "Capability"} ) { diff --git a/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 index 9bef836e19b..4e5668071d7 100644 --- a/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 +++ b/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 @@ -682,6 +682,19 @@ Describe 'ScriptScopeAccessFromClassMethod' -Tags "CI" { } Describe 'Hidden Members Test ' -Tags "CI" { + BeforeAll { + if ($null -ne $PSStyle) { + $outputRendering = $PSStyle.OutputRendering + $PSStyle.OutputRendering = 'plaintext' + } + } + + AfterAll { + if ($null -ne $PSStyle) { + $PSStyle.OutputRendering = $outputRendering + } + } + class C1 { [int]$visibleX diff --git a/test/powershell/Language/Classes/Scripting.Classes.NoRunspaceAffinity.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.NoRunspaceAffinity.Tests.ps1 new file mode 100644 index 00000000000..bc08db2a063 --- /dev/null +++ b/test/powershell/Language/Classes/Scripting.Classes.NoRunspaceAffinity.Tests.ps1 @@ -0,0 +1,52 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "Class can be defined without Runspace affinity" -Tags "CI" { + + It "Applying the 'NoRunspaceAffinity' attribute make the class not affiliate with a particular Runspace/SessionState" { + [NoRunspaceAffinity()] + class NoAffinity { + [string] $Name; + [int] $RunspaceId; + + NoAffinity() { + $this.RunspaceId = [runspace]::DefaultRunspace.Id + } + + static [int] Echo() { + return [runspace]::DefaultRunspace.Id + } + + [int] SetAndEcho([string] $value) { + $this.Name = $value + return [runspace]::DefaultRunspace.Id + } + } + + $t = [NoAffinity] + $o = [NoAffinity]::new() + + ## Running directly should use the current Runspace/SessionState. + $t::Echo() | Should -Be $Host.Runspace.Id + $o.RunspaceId | Should -Be $Host.Runspace.Id + $o.SetAndEcho('Blue') | Should -Be $Host.Runspace.Id + $o.Name | Should -Be 'Blue' + + ## Running in a new Runspace should use that Runspace and its current SessionState. + try { + $ps = [powershell]::Create() + $ps.AddScript('function CallEcho($type) { $type::Echo() }').Invoke() > $null; $ps.Commands.Clear() + $ps.AddScript('function CallSetAndEcho($obj) { $obj.SetAndEcho(''Hello world'') }').Invoke() > $null; $ps.Commands.Clear() + $ps.AddScript('function GetName($obj) { $obj.Name }').Invoke() > $null; $ps.Commands.Clear() + $ps.AddScript('function NewObj($type) { $type::new().RunspaceId }').Invoke() > $null; $ps.Commands.Clear() + + $ps.AddCommand('CallEcho').AddArgument($t).Invoke() | Should -Be $ps.Runspace.Id; $ps.Commands.Clear() + $ps.AddCommand('CallSetAndEcho').AddArgument($o).Invoke() | Should -Be $ps.Runspace.Id; $ps.Commands.Clear() + $ps.AddCommand('GetName').AddArgument($o).Invoke() | Should -Be 'Hello world'; $ps.Commands.Clear() + $ps.AddCommand('NewObj').AddArgument($t).Invoke() | Should -Be $ps.Runspace.Id; $ps.Commands.Clear() + } + finally { + $ps.Dispose() + } + } +} diff --git a/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 b/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 index cf304d5918e..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() where T : IInterfaceWithStaticAbstractProperty + => T.Getter; + + public static int GetSetter() where T : IInterfaceWithStaticAbstractProperty + => T.Setter; + + public static int SetSetter(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 {} @@ -628,3 +671,253 @@ class Derived : Base $sb.Invoke() | Should -Be 200 } } + +Describe 'Base type has abstract properties' -Tags "CI" { + It 'can derive from `FileSystemInfo`' { + ## FileSystemInfo has 3 abstract members that a derived type needs to implement + ## - public abstract bool Exists { get; } + ## - public abstract string Name { get; } + ## - public abstract void Delete (); + + class myFileSystemInfo : System.IO.FileSystemInfo + { + [string] $Name + [bool] $Exists + + myFileSystemInfo([string]$path) + { + # ctor + $this.Name = $path + $this.Exists = $true + } + + [void] Delete() + { + } + } + + $myFile = [myFileSystemInfo]::new('Hello') + $myFile.Name | Should -Be 'Hello' + $myFile.Exists | Should -BeTrue + } + + It 'deriving from `FileSystemInfo` will fail when the abstract property `Exists` is not implemented' { + $script = [scriptblock]::Create('class WillFail : System.IO.FileSystemInfo { [string] $Name }') + $failure = $null + try { + & $script + } catch { + $failure = $_ + } + + $failure | Should -Not -BeNullOrEmpty + $failure.FullyQualifiedErrorId | Should -BeExactly "TypeCreationError" + $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 base ctor' -TestCases $testCases { + param($derivedType) + $derivedType::new('foo').CtorUsed | Should -Be 'C1_ctor_1args:foo' + } + + It 'can access 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 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 base method' -TestCases $testCases { + param($derivedType) + $derivedType::new().CallInstanceMethod() | Should -Be 'C1_InstanceMethod' + } + + It 'can access 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 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 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 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 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 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 instance base members in ' -TestCases $negativeTestCases { + param($derivedType, $classScope) + if ($null -eq $classScope) { + $instanceTest.Invoke() + } + else { + $c3 = $classScope::new() + $c3.RunInstanceTest($derivedType) + } + } + } +} diff --git a/test/powershell/Language/Interop/DotNet/DotNetInterop.Tests.ps1 b/test/powershell/Language/Interop/DotNet/DotNetInterop.Tests.ps1 index 02d49c4435c..42426b8279a 100644 --- a/test/powershell/Language/Interop/DotNet/DotNetInterop.Tests.ps1 +++ b/test/powershell/Language/Interop/DotNet/DotNetInterop.Tests.ps1 @@ -121,7 +121,7 @@ namespace DotNetInterop } It "Calling constructor of a ByRef-like type via dotnet adapter should fail gracefully - " -TestCases @( - @{ Number = 1; Script = { [System.Span[string]]::new.Invoke("abc") } } + @{ Number = 1; Script = { [System.Span[string]]::new.Invoke([ref]$null) } } @{ Number = 2; Script = { [DotNetInterop.MyByRefLikeType]::new.Invoke(2) } } ) { param($Script) 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 for backtick comparison " -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/Operators/PipelineChainOperator.Tests.ps1 b/test/powershell/Language/Operators/PipelineChainOperator.Tests.ps1 index e2e54c32170..15340dd18e6 100644 --- a/test/powershell/Language/Operators/PipelineChainOperator.Tests.ps1 +++ b/test/powershell/Language/Operators/PipelineChainOperator.Tests.ps1 @@ -47,7 +47,7 @@ Describe "Experimental Feature: && and || operators - Feature-Enabled" -Tag CI { @{ Statement = 'testexe -returncode -1 || testexe -returncode -2 && testexe -echoargs "A"'; Output = @('-1', '-2') } @{ Statement = 'testexe -returncode -1 || testexe -returncode -2 || testexe -echoargs "B"'; Output = @('-1', '-2', 'Arg 0 is ') } - # Native command and succesful cmdlet + # Native command and successful cmdlet @{ Statement = 'Test-SuccessfulCommand && testexe -returncode 0'; Output = @('SUCCESS', '0') } @{ Statement = 'testexe -returncode 0 && Test-SuccessfulCommand'; Output = @('0', 'SUCCESS') } @{ Statement = 'Test-SuccessfulCommand && testexe -returncode 1'; Output = @('SUCCESS', '1') } diff --git a/test/powershell/Language/Operators/ReplaceOperator.Tests.ps1 b/test/powershell/Language/Operators/ReplaceOperator.Tests.ps1 index b72779f59c4..074351d3414 100644 --- a/test/powershell/Language/Operators/ReplaceOperator.Tests.ps1 +++ b/test/powershell/Language/Operators/ReplaceOperator.Tests.ps1 @@ -85,24 +85,13 @@ Describe "Replace Operator" -Tags CI { Describe "Culture-invariance tests for -split and -replace" -Tags CI { BeforeAll { - $skipTest = -not [ExperimentalFeature]::IsEnabled("PSCultureInvariantReplaceOperator") - if ($skipTest) { - Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSCultureInvariantReplaceOperator' to be enabled." -Verbose - $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() - $PSDefaultParameterValues["it:skip"] = $true - } else { - $prevCulture = [cultureinfo]::CurrentCulture - # The French culture uses "," as the decimal mark. - [cultureinfo]::CurrentCulture = 'fr' - } + $prevCulture = [cultureinfo]::CurrentCulture + # The French culture uses "," as the decimal mark. + [cultureinfo]::CurrentCulture = 'fr' } AfterAll { - if ($skipTest) { - $global:PSDefaultParameterValues = $originalDefaultParameterValues - } else { - [cultureinfo]::CurrentCulture = $prevCulture - } + [cultureinfo]::CurrentCulture = $prevCulture } It "-split: LHS stringification is not culture-sensitive" { diff --git a/test/powershell/Language/Parser/AutomaticVariables.Tests.ps1 b/test/powershell/Language/Parser/AutomaticVariables.Tests.ps1 index cef1861f807..009def56758 100644 --- a/test/powershell/Language/Parser/AutomaticVariables.Tests.ps1 +++ b/test/powershell/Language/Parser/AutomaticVariables.Tests.ps1 @@ -2,16 +2,15 @@ # Licensed under the MIT License. Describe 'Automatic variable $input' -Tags "CI" { - # Skip on hold for discussion on https://github.com/PowerShell/PowerShell/issues/1563 # $input type in advanced functions - It '$input Type should be enumerator' -Skip { + It '$input Type should be arraylist and object array' { function from_begin { [cmdletbinding()]param() begin { Write-Output -NoEnumerate $input } } function from_process { [cmdletbinding()]param() process { Write-Output -NoEnumerate $input } } function from_end { [cmdletbinding()]param() end { Write-Output -NoEnumerate $input } } - (from_begin) -is [System.Collections.IEnumerator] | Should -BeTrue - (from_process) -is [System.Collections.IEnumerator] | Should -BeTrue - (from_end) -is [System.Collections.IEnumerator] | Should -BeTrue + (from_begin) -is [System.Collections.ArrayList] | Should -BeTrue + (from_process) -is [System.Collections.ArrayList] | Should -BeTrue + (from_end) -is [System.Object[]] | Should -BeTrue } It 'Empty $input really is empty' { diff --git a/test/powershell/Language/Parser/BNotOperator.Tests.ps1 b/test/powershell/Language/Parser/BNotOperator.Tests.ps1 index eb6dd934e30..41930766dc6 100644 --- a/test/powershell/Language/Parser/BNotOperator.Tests.ps1 +++ b/test/powershell/Language/Parser/BNotOperator.Tests.ps1 @@ -12,7 +12,7 @@ $ns = [Guid]::NewGuid() -replace '-','' $typeDefinition = "namespace ns_$ns`n{" -$enumTypeNames = foreach ($baseType in $baseTypes.Keys) +foreach ($baseType in $baseTypes.Keys) { $baseTypeName = $baseTypes[$baseType] $typeDefinition += @" @@ -24,14 +24,12 @@ $enumTypeNames = foreach ($baseType in $baseTypes.Keys) Max = $($baseType::MaxValue) } "@ - - "ns_$ns.E_$baseTypeName" } $typeDefinition += "`n}" -Write-Verbose $typeDefinition -Add-Type $typeDefinition +Write-Verbose $typeDefinition -verbose +$enumTypeNames = Add-Type $typeDefinition -Pass Describe "bnot on enums" -Tags "CI" { foreach ($enumType in [type[]]$enumTypeNames) diff --git a/test/powershell/Language/Parser/Conversions.Tests.ps1 b/test/powershell/Language/Parser/Conversions.Tests.ps1 index bfeeddc4f1a..f6874b1db93 100644 --- a/test/powershell/Language/Parser/Conversions.Tests.ps1 +++ b/test/powershell/Language/Parser/Conversions.Tests.ps1 @@ -78,6 +78,14 @@ Describe 'conversion syntax' -Tags "CI" { $result -join ";" | Should -Be ($Elements -join ";") } } + + It 'Should not convert invalid strings to type name using -as operator' { + 'int]whatever' -as [type] | Should -Be $null + } + + It 'Should not convert invalid strings to type name using left-hand side operator' { + {[Type] 'int]whatever'} | Should -Throw + } } Describe "Type resolution should prefer assemblies in powershell assembly cache" -Tags "Feature" { @@ -526,7 +534,7 @@ Describe 'method conversion' -Tags 'CI' { } Describe 'float/double precision when converting to string' -Tags "CI" { - It "-to-[string] conversion in PowerShell should use the precision specifier " -TestCases @( + It "-to-[string] conversion in PowerShell should use the precision specifier ()" -TestCases @( @{ SourceType = [double]; Format = "G15"; ValueScript = { 1.1 * 3 }; StringConversionResult = "3.3"; ToStringResult = "3.3000000000000003" } @{ SourceType = [double]; Format = "G15"; ValueScript = { 1.1 * 6 }; StringConversionResult = "6.6"; ToStringResult = "6.6000000000000005" } @{ SourceType = [double]; Format = "G15"; ValueScript = { [System.Math]::E }; StringConversionResult = [System.Math]::E.ToString("G15"); ToStringResult = [System.Math]::E.ToString() } diff --git a/test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1 b/test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1 index 3c9edd43915..3514f389718 100644 --- a/test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1 +++ b/test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1 @@ -175,6 +175,77 @@ function TestFunction ) } + +class NumberCompleter : IArgumentCompleter +{ + + [int] $From + [int] $To + [int] $Step + + NumberCompleter([int] $from, [int] $to, [int] $step) + { + if ($from -gt $to) { + throw [ArgumentOutOfRangeException]::new("from") + } + $this.From = $from + $this.To = $to + $this.Step = if($step -lt 1) { 1 } else { $step } + } + + [IEnumerable[CompletionResult]] CompleteArgument( + [string] $CommandName, + [string] $parameterName, + [string] $wordToComplete, + [CommandAst] $commandAst, + [IDictionary] $fakeBoundParameters) + { + $resultList = [List[CompletionResult]]::new() + $local:to = $this.To + for ($i = $this.From; $i -le $to; $i += $this.Step) { + if ($i.ToString().StartsWith($wordToComplete, [System.StringComparison]::Ordinal)) { + $num = $i.ToString() + $resultList.Add([CompletionResult]::new($num, $num, "ParameterValue", $num)) + } + } + + return $resultList + } +} + +class NumberCompletionAttribute : ArgumentCompleterAttribute, IArgumentCompleterFactory +{ + [int] $From + [int] $To + [int] $Step + + NumberCompletionAttribute([int] $from, [int] $to) + { + $this.From = $from + $this.To = $to + $this.Step = 1 + } + + [IArgumentCompleter] Create() { return [NumberCompleter]::new($this.From, $this.To, $this.Step) } +} + +function FactoryCompletionAdd { + param( + [NumberCompletion(0, 50, Step = 5)] + [int] $Number + ) +} + +Describe "Factory based extensible completion" -Tags "CI" { + @{ + ExpectedResults = @( + @{CompletionText = "5"; ResultType = "ParameterValue" } + @{CompletionText = "50"; ResultType = "ParameterValue" } + ) + TestInput = 'FactoryCompletionAdd -Number 5' + } | Get-CompletionTestCaseData | Test-Completions +} + Describe "Script block based extensible completion" -Tags "CI" { @{ ExpectedResults = @( diff --git a/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 b/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 index f28b6801389..a9641951473 100644 --- a/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 +++ b/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 @@ -1,10 +1,270 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -if ( $IsCoreCLR ) { - return + +Describe 'Generic Method invocation' -Tags 'CI' { + + BeforeAll { + $EmptyArrayCases = @( + @{ + Script = '[Array]::Empty[string]()' + ExpectedType = [string[]] + } + @{ + Script = '[Array]::Empty[System.Collections.Generic.Dictionary[System.Numerics.BigInteger, System.Collections.Generic.List[string[,]]]]()' + ExpectedType = [System.Collections.Generic.Dictionary[System.Numerics.BigInteger, System.Collections.Generic.List[string[, ]]][]] + } + ) + + $IndexingAProperty = @( + @{ + Script = '[object]::Property[[type]]' + IndexType = 'System.Management.Automation.Language.TypeExpressionAst' + IndexString = '[type]' + } + @{ + Script = '$object.IPSubnet[[Array]::IndexOf($_.IPAddress, $_.IPAddress[0])]' + IndexType = 'System.Management.Automation.Language.InvokeMemberExpressionAst' + IndexString = '[Array]::IndexOf($_.IPAddress, $_.IPAddress[0])' + } + @{ + Script = @' + [IPAddress]::Parse( + $_.IPSubnet[ + [Array]::IndexOf($_.IPAddress, $_.IPAddress[0]) + ] + ) +'@ + IndexType = 'System.Management.Automation.Language.InvokeMemberExpressionAst' + IndexString = '[Array]::IndexOf($_.IPAddress, $_.IPAddress[0])' + } + @{ + Script = @' + [IPAddress]::Parse( + $_.IPSubnet[ + ([Array]::IndexOf($_.IPAddress, $_.IPAddress[0])) + ] + ) +'@ + IndexType = 'System.Management.Automation.Language.ParenExpressionAst' + IndexString = '([Array]::IndexOf($_.IPAddress, $_.IPAddress[0]))' + } + ) + + $ExpectedParseErrors = @( + @{ + Script = '$object.Method[incompl' + ExpectedErrors = @('EndSquareBracketExpectedAtEndOfType') + ErrorCount = 1 + } + @{ + Script = '[type]::Member[incompl' + ExpectedErrors = @('EndSquareBracketExpectedAtEndOfType') + ErrorCount = 1 + } + @{ + Script = '$object.Method[Type1[Type2' + ExpectedErrors = @('EndSquareBracketExpectedAtEndOfAttribute','EndSquareBracketExpectedAtEndOfType') + ErrorCount = 2 + } + @{ + Script = '[array]::empty[type]]()' + ExpectedErrors = @('MissingArrayIndexExpression', 'UnexpectedToken', 'ExpectedExpression') + ErrorCount = 3 + } + @{ + Script = '$object.Method[type,]()' + ExpectedErrors = @('MissingTypename') + ErrorCount = 1 + } + @{ + Script = '$object.Method[]()' + ExpectedErrors = @('MissingArrayIndexExpression', 'UnexpectedToken', 'ExpectedExpression') + ErrorCount = 3 + } + @{ + Script = '$object.Method[,]()' + ExpectedErrors = @('MissingExpressionAfterOperator', 'UnexpectedToken', 'ExpectedExpression') + ErrorCount = 3 + } + @{ + Script = '$object.Method[,type]()' + ExpectedErrors = @('MissingExpressionAfterOperator', 'UnexpectedToken', 'ExpectedExpression') + ErrorCount = 3 + } + @{ + Script = '$object.Method[type()' + ExpectedErrors = @('EndSquareBracketExpectedAtEndOfType', 'UnexpectedToken', 'ExpectedExpression') + ErrorCount = 3 + } + @{ + Script = '$object.Method[type)' + ExpectedErrors = @('EndSquareBracketExpectedAtEndOfType', 'UnexpectedToken') + ErrorCount = 2 + } + @{ + Script = '$object.Method[[type]]()' + ExpectedErrors = @('UnexpectedToken', 'ExpectedExpression') + ErrorCount = 2 + } + @{ + Script = '[Array]::Empty[[type]]()' + ExpectedErrors = @('UnexpectedToken', 'ExpectedExpression') + ErrorCount = 2 + } + @{ + Script = '$object.Property[type]' + ExpectedErrors = @('MissingArrayIndexExpression', 'UnexpectedToken') + ErrorCount = 2 + } + ) + } + + It 'does not throw a parse error for " + +'@ + } + @{ + Command = "cscript.exe" + Filename = "test.vbs" + ExpectedResults = @( + "Argument 0 is: " + "Argument 1 is: " + "Argument 2 is: " + "Argument 3 is: " + ) + Script = @' +for i = 0 to wScript.arguments.count - 1 + wscript.echo "Argument " & i & " is: <" & (wScript.arguments(i)) & ">" +next +'@ + } + @{ + Command = "cscript" + Filename = "test.js" + ExpectedResults = @( + "Argument 0 is: " + "Argument 1 is: " + "Argument 2 is: " + "Argument 3 is: " + ) + Script = @' +for(i = 0; i < WScript.Arguments.Count(); i++) { + WScript.echo("Argument " + i + " is: <" + WScript.Arguments(i) + ">"); +} +'@ + } + @{ + Command = "" + Filename = "test.bat" + ExpectedResults = @( + "Argument 1 is: " + "Argument 2 is: " + "Argument 3 is: <""ab cd"">" + "Argument 4 is: <""a'b c'd"">" + ) + Script = @' +@echo off +echo Argument 1 is: ^<%1^> +echo Argument 2 is: ^<%2^> +echo Argument 3 is: ^<%3^> +echo Argument 4 is: ^<%4^> +'@ + } + @{ + Command = "" + Filename = "test.cmd" + ExpectedResults = @( + "Argument 1 is: " + "Argument 2 is: " + "Argument 3 is: <""ab cd"">" + "Argument 4 is: <""a'b c'd"">" + ) + Script = @' +@echo off +echo Argument 1 is: ^<%1^> +echo Argument 2 is: ^<%2^> +echo Argument 3 is: ^<%3^> +echo Argument 4 is: ^<%4^> +'@ + } + ) - It "Should handle PowerShell arrays with or without spaces correctly: " -TestCases @( - @{arguments = "1,2"; expected = @("1,2")} - @{arguments = "1,2,3"; expected = @("1,2,3")} - @{arguments = "1, 2"; expected = "1,", "2"} - @{arguments = "1 ,2"; expected = "1", ",2"} - @{arguments = "1 , 2"; expected = "1", ",", "2"} - @{arguments = "1, 2,3"; expected = "1,", "2,3"} - @{arguments = "1 ,2,3"; expected = "1", ",2,3"} - @{arguments = "1 , 2,3"; expected = "1", ",", "2,3"} - ) { - param($arguments, $expected) - $lines = @(Invoke-Expression "testexe -echoargs $arguments") - $lines.Count | Should -Be $expected.Count - for ($i = 0; $i -lt $expected.Count; $i++) { - $lines[$i] | Should -BeExactly "Arg $i is <$($expected[$i])>" + # determine whether we should skip the tests we just defined + # doing it in this order ensures that the test output will show each skipped test + $skipTests = -not $IsWindows + if ($skipTests) { + return } + + # save the passing style + $passingStyle = $PSNativeCommandArgumentPassing + # explicitely set the passing style to Windows + $PSNativeCommandArgumentPassing = "Windows" } -} -Describe 'PSPath to native commands' { - BeforeAll { - $featureEnabled = $EnabledExperimentalFeatures.Contains('PSNativePSPathResolution') - $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() - - $PSDefaultParameterValues["it:skip"] = (-not $featureEnabled) - - if ($IsWindows) { - $cmd = "cmd" - $cmdArg1 = "/c" - $cmdArg2 = "type" - $dir = "cmd" - $dirArg1 = "/c" - $dirArg2 = "dir" + It "Invoking '' is compatible with PowerShell 5" -TestCases $testCases -Skip:$($skipTests) { + param ( $Command, $Arguments, $Filename, $Script, $ExpectedResults ) + cscript //h:cscript //nologo //s + $a = 'a"b c"d' + $scriptPath = Join-Path $TESTDRIVE $Filename + $Script | out-file -encoding ASCII $scriptPath + if ($Command) { + $results = & $Command $scriptPath $a 'a"b c"d' a"b c"d "a'b c'd" 2> "${TESTDRIVE}/error.txt" } else { - $cmd = "cat" - $dir = "ls" + $results = & $scriptPath $a 'a"b c"d' a"b c"d "a'b c'd" 2> "${TESTDRIVE}/error.txt" } - - Set-Content -Path testdrive:/test.txt -Value 'Hello' - Set-Content -Path "testdrive:/test file.txt" -Value 'Hello' - Set-Content -Path "env:/test var" -Value 'Hello' - $filePath = Join-Path -Path ~ -ChildPath (New-Guid) - Set-Content -Path $filePath -Value 'Home' - $complexDriveName = 'My test! ;+drive' - New-PSDrive -Name $complexDriveName -Root $testdrive -PSProvider FileSystem + $errorContent = Get-Content "${TESTDRIVE}/error.txt" -ErrorAction Ignore + $errorContent | Should -BeNullOrEmpty + $results.Count | Should -Be 4 + $results[0] | Should -Be $ExpectedResults[0] + $results[1] | Should -Be $ExpectedResults[1] + $results[2] | Should -Be $ExpectedResults[2] + $results[3] | Should -Be $ExpectedResults[3] } +} - AfterAll { - $global:PSDefaultParameterValues = $originalDefaultParameterValues - Remove-Item -Path "env:/test var" - Remove-Item -Path $filePath - Remove-PSDrive -Name $complexDriveName +Describe "Will error correctly if an attempt to set variable to improper value" -tags "CI" { + It "will error when setting variable incorrectly" { + { $global:PSNativeCommandArgumentPassing = "zzz" } | Should -Throw -ExceptionType System.Management.Automation.ArgumentTransformationMetadataException } +} - It 'PSPath with ~/path works' { - $out = & $cmd $cmdArg1 $cmdArg2 $filePath - $LASTEXITCODE | Should -Be 0 - $out | Should -BeExactly 'Home' +Describe "find.exe uses legacy behavior on Windows" -Tag 'CI' { + BeforeAll { + $currentSetting = $PSNativeCommandArgumentPassing + $PSNativeCommandArgumentPassing = "Windows" + $testCases = @{ pattern = "" }, + @{ pattern = "blat" }, + @{ pattern = "bl at" } } - - It 'PSPath with ~ works' { - $out = & $dir $dirArg1 $dirArg2 ~ - $LASTEXITCODE | Should -Be 0 - $out | Should -Not -BeNullOrEmpty + AfterAll { + $PSNativeCommandArgumentPassing = $currentSetting + } + It "The pattern '' is used properly by find.exe" -skip:(! $IsWindows) -testCases $testCases { + param ($pattern) + $expr = "'foo' | find.exe --% /v ""$pattern""" + $result = Invoke-Expression $expr + $result | Should -Be 'foo' } +} - It 'PSPath that is file system path works with native commands: ' -TestCases @( - @{ path = "testdrive:/test.txt" } - @{ path = "testdrive:/test file.txt" } - ){ - param($path) +foreach ( $argumentListValue in "Standard","Legacy","Windows" ) { + $PSNativeCommandArgumentPassing = $argumentListValue + Describe "Native Command Arguments (${PSNativeCommandArgumentPassing})" -tags "CI" { + # When passing arguments to native commands, quoted segments that contain + # spaces need to be quoted with '"' characters when they are passed to the + # native command (or to bash or sh on Linux). + # + # This test checks that the proper quoting is occuring by passing arguments + # to the testexe native command and looking at how it got the arguments. + It "Should handle quoted spaces correctly (ArgumentList=${PSNativeCommandArgumentPassing})" { + $a = 'a"b c"d' + $lines = testexe -echoargs $a 'a"b c"d' a"b c"d "a'b c'd" + $lines.Count | Should -Be 4 + if ($PSNativeCommandArgumentPassing -ne "Legacy") { + $lines[0] | Should -BeExactly 'Arg 0 is ' + $lines[1] | Should -BeExactly 'Arg 1 is ' + } + else { + $lines[0] | Should -BeExactly 'Arg 0 is ' + $lines[1] | Should -BeExactly 'Arg 1 is ' + } + $lines[2] | Should -BeExactly 'Arg 2 is ' + $lines[3] | Should -BeExactly 'Arg 3 is ' + } - $out = & $cmd $cmdArg1 $cmdArg2 "$path" - $LASTEXITCODE | Should -Be 0 - $out | Should -BeExactly 'Hello' - } + # In order to pass '"' characters so they are actually part of command line + # arguments for native commands, they need to be escaped with a '\' (this + # is in addition to the '`' escaping needed inside '"' quoted strings in + # PowerShell). + # + # This functionality was broken in PowerShell 5.0 and 5.1, so this test + # will fail on those versions unless the fix is backported to them. + # + # This test checks that the proper quoting and escaping is occurring by + # passing arguments with escaped quotes to the testexe native command and + # looking at how it got the arguments. + It "Should handle spaces between escaped quotes (ArgumentList=${PSNativeCommandArgumentPassing})" { + $lines = testexe -echoargs 'a\"b c\"d' "a\`"b c\`"d" + $lines.Count | Should -Be 2 + if ($PSNativeCommandArgumentPassing -ne "Legacy") { + $lines[0] | Should -BeExactly 'Arg 0 is ' + $lines[1] | Should -BeExactly 'Arg 1 is ' + } + else { + $lines[0] | Should -BeExactly 'Arg 0 is ' + $lines[1] | Should -BeExactly 'Arg 1 is ' + } + } - It 'PSPath passed with single quotes should be treated as literal' { - $out = & $cmd $cmdArg1 $cmdArg2 'testdrive:/test.txt' - $LASTEXITCODE | Should -Not -Be 0 - $out | Should -BeNullOrEmpty - } + It "Should correctly quote paths with spaces (ArgumentList=${PSNativeCommandArgumentPassing}): " -TestCases @( + @{arguments = "'.\test 1\' `".\test 2\`"" ; expected = @(".\test 1\",".\test 2\")}, + @{arguments = "'.\test 1\\\' `".\test 2\\`""; expected = @(".\test 1\\\",".\test 2\\")} + ) { + param($arguments, $expected) + $lines = Invoke-Expression "testexe -echoargs $arguments" + $lines.Count | Should -Be $expected.Count + for ($i = 0; $i -lt $lines.Count; $i++) { + $lines[$i] | Should -BeExactly "Arg $i is <$($expected[$i])>" + } + } - It 'PSPath that is not a file system path fails with native commands: ' -TestCases @( - @{ path = "env:/PSModulePath" } - @{ path = "env:/test var" } - ){ - param($path) + It "Should handle arguments that include commas without spaces (windbg example)" { + $lines = testexe -echoargs -k com:port=\\devbox\pipe\debug,pipe,resets=0,reconnect + $lines.Count | Should -Be 2 + $lines[0] | Should -BeExactly "Arg 0 is <-k>" + $lines[1] | Should -BeExactly "Arg 1 is " + } - $out = & $cmd $cmdArg1 $cmdArg2 "$path" - $LASTEXITCODE | Should -Not -Be 0 - $out | Should -BeNullOrEmpty - } + It "Should handle when the ':' is the parameter value" { + $lines = testexe -echoargs awk -F: '{print $1}' + $lines.Count | Should -Be 3 + $lines[0] | Should -BeExactly 'Arg 0 is ' + $lines[1] | Should -BeExactly 'Arg 1 is <-F:>' + $lines[2] | Should -BeExactly 'Arg 2 is <{print $1}>' + } - It 'Relative PSPath works' { - New-Item -Path $testdrive -Name TestFolder -ItemType Directory -ErrorAction Stop - $cwd = Get-Location - Set-Content -Path (Join-Path -Path $testdrive -ChildPath 'TestFolder' -AdditionalChildPath 'test.txt') -Value 'hello' - Set-Location -Path (Join-Path -Path $testdrive -ChildPath 'TestFolder') - Set-Location -Path $cwd - $out = & $cmd $cmdArg1 $cmdArg2 "TestDrive:test.txt" - $LASTEXITCODE | Should -Be 0 - $out | Should -BeExactly 'Hello' - } + It "Should handle DOS style arguments" { + $lines = testexe -echoargs /arg1 /c:"a string" + $lines.Count | Should -Be 2 + $lines[0] | Should -BeExactly "Arg 0 is " + $lines[1] | Should -BeExactly "Arg 1 is " + } + + It "Should handle PowerShell arrays with or without spaces correctly (ArgumentList=${PSNativeCommandArgumentPassing}): " -TestCases @( + @{arguments = "1,2"; expected = @("1,2")} + @{arguments = "1,2,3"; expected = @("1,2,3")} + @{arguments = "1, 2"; expected = "1,", "2"} + @{arguments = "1 ,2"; expected = "1", ",2"} + @{arguments = "1 , 2"; expected = "1", ",", "2"} + @{arguments = "1, 2,3"; expected = "1,", "2,3"} + @{arguments = "1 ,2,3"; expected = "1", ",2,3"} + @{arguments = "1 , 2,3"; expected = "1", ",", "2,3"} + ) { + param($arguments, $expected) + $lines = @(Invoke-Expression "testexe -echoargs $arguments") + $lines.Count | Should -Be $expected.Count + for ($i = 0; $i -lt $expected.Count; $i++) { + $lines[$i] | Should -BeExactly "Arg $i is <$($expected[$i])>" + } + } + + It "Should handle empty args correctly (ArgumentList=${PSNativeCommandArgumentPassing})" { + if ($PSNativeCommandArgumentPassing -eq 'Legacy') { + $expectedLines = 2 + } + else { + $expectedLines = 3 + } - It 'Complex PSDrive name works' { - $out = & $cmd $cmdArg1 $cmdArg2 "${complexDriveName}:/test.txt" - $LASTEXITCODE | Should -Be 0 - $out | Should -BeExactly 'Hello' + $lines = testexe -echoargs 1 '' 2 + $lines.Count | Should -Be $expectedLines + $lines[0] | Should -BeExactly 'Arg 0 is <1>' + + if ($expectedLines -eq 2) { + $lines[1] | Should -BeExactly 'Arg 1 is <2>' + } + else { + $lines[1] | Should -BeExactly 'Arg 1 is <>' + $lines[2] | Should -BeExactly 'Arg 2 is <2>' + } + + } + + It 'Should treat a PSPath as literal' { + $lines = testexe -echoargs temp:/foo + $lines.Count | Should -Be 1 + $lines | Should -BeExactly 'Arg 0 is ' + } } } diff --git a/test/powershell/Language/Scripting/NativeExecution/NativeCommandProcessor.Tests.ps1 b/test/powershell/Language/Scripting/NativeExecution/NativeCommandProcessor.Tests.ps1 index 95eb59857ae..542f1724303 100644 --- a/test/powershell/Language/Scripting/NativeExecution/NativeCommandProcessor.Tests.ps1 +++ b/test/powershell/Language/Scripting/NativeExecution/NativeCommandProcessor.Tests.ps1 @@ -43,6 +43,62 @@ Describe 'native commands with pipeline' -tags 'Feature' { $result[0] | Should -Match "pwsh" } } + + It 'native command should be killed when pipeline is disposed' -Skip:($IsWindows) { + $yes = (Get-Process 'yes' -ErrorAction Ignore).Count + yes | Select-Object -First 2 + # wait a little to be sure that the process is ended + Start-Sleep -Milliseconds 500 + (Get-Process 'yes' -ErrorAction Ignore).Count | Should -Be $yes + } + + It 'native command should still execute if the current working directory no longer exists with command: ' -Skip:($IsWindows) -TestCases @( + @{ command = 'ps' } + @{ command = 'start-process ps -nonewwindow'} + ){ + param($command) + + $wd = New-Item testdrive:/tmp -ItemType directory + $lock = New-Item testdrive:/lock -ItemType file + $script = @" + while (`$null -ne (Get-Item "$lock" -ErrorAction Ignore)) { + Start-Sleep -Seconds 1 + } + + try { + `$out = $command + } + catch { + `$null = Set-Content -Path "$testdrive/error" -Value (`$_ | Out-String) + } + + `$null = Set-Content -Path "$testdrive/out" -Value `$out +"@ + + $pwsh = Start-Process -FilePath "${PSHOME}/pwsh" -WorkingDirectory $wd -ArgumentList @('-noprofile','-command',$script) + + Remove-Item -Path $wd -Force + Remove-Item $lock + $start = Get-Date + + try { + while ($null -eq (Get-Item "$testdrive/error" -ErrorAction Ignore) -and $null -eq (Get-Item "$testdrive/out" -ErrorAction Ignore)) { + if (((Get-Date) - $start).TotalSeconds -gt 60) { + throw "Timeout" + } + + Start-Sleep -Seconds 1 + } + } + finally { + $pwsh | Stop-Process -Force -ErrorAction Ignore + } + + $err = Get-Item -Path "$testdrive/error" -ErrorAction Ignore + $err | Should -BeNullOrEmpty -Because $err + $out = Get-Item -Path "$testdrive/out" -ErrorAction Ignore + $out | Should -Not -BeNullOrEmpty + } } Describe "Native Command Processor" -tags "Feature" { @@ -89,6 +145,10 @@ Describe "Native Command Processor" -tags "Feature" { } It "Should not block running Windows executables" -Skip:(!$IsWindows -or !(Get-Command notepad.exe)) { + if (Test-IsWindowsArm64) { + Set-ItResult -Pending -Because "Needs investigation" + } + function FindNewNotepad { Get-Process -Name notepad -ErrorAction Ignore | Where-Object { $_.Id -NotIn $dontKill } @@ -141,7 +201,7 @@ Describe "Native Command Processor" -tags "Feature" { } } - It '$ErrorActionPreference does not apply to redirected stderr output' -Skip:(!$EnabledExperimentalFeatures.Contains('PSNotApplyErrorActionToStderr')) { + It '$ErrorActionPreference does not apply to redirected stderr output' { pwsh -noprofile -command '$ErrorActionPreference = ''Stop''; testexe -stderr stop 2>$null; ''hello''; $error; $?' | Should -BeExactly 'hello','True' } @@ -153,6 +213,12 @@ Describe "Native Command Processor" -tags "Feature" { Wait-UntilTrue -sb { (Get-Process mmc).Count -gt 0 } -TimeoutInMilliseconds 5000 -IntervalInMilliseconds 1000 | Should -BeTrue Get-Process mmc | Stop-Process } + + It 'Can redirect stdout and stderr to different files' { + testexe -stderrandout testing > $TestDrive/stdout.txt 2> $TestDrive/stderr.txt + Get-Content $TestDrive/stdout.txt | Should -Be testing + Get-Content $TestDrive/stderr.txt | Should -Be gnitset + } } Describe "Open a text file with NativeCommandProcessor" -tags @("Feature", "RequireAdminOnWindows") { @@ -246,3 +312,93 @@ Categories=Application; { $dllFile = "$PSHOME\System.Management.Automation.dll"; & $dllFile } | Should -Throw -ErrorId "NativeCommandFailed" } } + +Describe "Run native command from a mounted FAT-format VHD" -tags @("Feature", "RequireAdminOnWindows") { + BeforeAll { + if (-not $IsWindows) { + return; + } + else { + $storageModule = Get-Module -Name 'Storage' -ListAvailable -ErrorAction SilentlyContinue + + if (-not $storageModule) { + Write-Verbose -Verbose "Storage module is not available." + return; + } + } + + $vhdx = Join-Path -Path $TestDrive -ChildPath ncp.vhdx + + if (Test-Path -Path $vhdx) { + Remove-item -Path $vhdx -Force + } + + $create_vhdx = Join-Path -Path $TestDrive -ChildPath 'create_vhdx.txt' + + Set-Content -Path $create_vhdx -Force -Value @" + create vdisk file="$vhdx" maximum=20 type=fixed + select vdisk file="$vhdx" + attach vdisk + convert mbr + create partition primary + format fs=fat + assign letter="T" + detach vdisk +"@ + + diskpart.exe /s $create_vhdx + Mount-DiskImage -ImagePath $vhdx > $null + + Copy-Item "$env:WinDir\System32\whoami.exe" "T:\whoami.exe" + } + + AfterAll { + if ($IsWindows) { + $storageModule = Get-Module -Name 'Storage' -ListAvailable -ErrorAction SilentlyContinue + + if (-not $storageModule) { + Write-Verbose -Verbose "Storage module is not available." + return; + } + + Dismount-DiskImage -ImagePath $vhdx + Remove-Item $vhdx, $create_vhdx -Force + } + } + + It "Should run 'whoami.exe' from FAT file system without error" -Skip:(!$IsWindows) { + if ((Test-IsWinServer2012R2) -or (Test-IsWindows2016)) { + Set-ItResult -Pending -Because "Marking as pending since whomai.exe is not found on T:\ on 2012R2 and 2016 after copying to VHD" + return + } + + $expected = & "$env:WinDir\System32\whoami.exe" + $result = T:\whoami.exe + $result | Should -BeExactly $expected + } +} + +Describe "Native application invocation and getting cursor position" -Tags 'CI' { + It "Invoking a native application should not collect the cursor position" -Skip:($IsWindows) { + $expectCmd = Get-Command expect -Type Application -ErrorAction Ignore + $dateCmd = Get-Command date -Type Application -ErrorAction Ignore + # if date or expect are missing mark the test as pending + # test setup will need to ensure that these programs are present. + $missing = @() + if ($null -eq $expectCmd) { + $missing += "expect" + } + if ($null -eq $dateCmd) { + $missing += "date" + } + if ($missing.count -ne 0) { + $message = "missing command(s) {0}" -f ($missing -join ", ") + Set-ItResult -Pending -Because $message + } + + $powershell = Join-Path -Path $PSHOME -ChildPath "pwsh" + $commandString = "spawn $powershell -nopro -c /bin/date; expect eof" + [string]$result = expect -c $commandString + $result.IndexOf("`e[6n") | Should -Be -1 -Because $result.replace("`e","``e").replace("`u{7}","") + } +} diff --git a/test/powershell/Language/Scripting/NativeExecution/NativeStreams.Tests.ps1 b/test/powershell/Language/Scripting/NativeExecution/NativeStreams.Tests.ps1 index 59702938375..53ed45fe4da 100644 --- a/test/powershell/Language/Scripting/NativeExecution/NativeStreams.Tests.ps1 +++ b/test/powershell/Language/Scripting/NativeExecution/NativeStreams.Tests.ps1 @@ -12,9 +12,9 @@ Describe "Native streams behavior with PowerShell" -Tags 'CI' { $error.Clear() $command = [string]::Join('', @( - '[Console]::Error.Write(\"foo`n`nbar`n`nbaz\"); ', - '[Console]::Error.Write(\"middle\"); ', - '[Console]::Error.Write(\"foo`n`nbar`n`nbaz\")' + '[Console]::Error.Write("foo`n`nbar`n`nbaz"); ', + '[Console]::Error.Write("middle"); ', + '[Console]::Error.Write("foo`n`nbar`n`nbaz")' )) $out = & $powershell -noprofile -command $command 2>&1 @@ -53,7 +53,11 @@ Describe "Native streams behavior with PowerShell" -Tags 'CI' { ($out | Out-String).Replace("`r", '') | Should -BeExactly "foo`n`nbar`n`nbazmiddlefoo`n`nbar`n`nbaz`n" } - It 'does not get truncated or split when redirected' { + It 'Does not get truncated or split when redirected' { + if (Test-IsWindowsArm64) { + Set-ItResult -Pending -Because "IOException: The handle is invalid." + } + $longtext = "0123456789" while ($longtext.Length -lt [console]::WindowWidth) { $longtext += $longtext diff --git a/test/powershell/Language/Scripting/NativeExecution/NativeWindowsTildeExpansion.Tests.ps1 b/test/powershell/Language/Scripting/NativeExecution/NativeWindowsTildeExpansion.Tests.ps1 new file mode 100644 index 00000000000..04156099fa4 --- /dev/null +++ b/test/powershell/Language/Scripting/NativeExecution/NativeWindowsTildeExpansion.Tests.ps1 @@ -0,0 +1,32 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe 'Native Windows tilde expansion tests' -tags "CI" { + BeforeAll { + $originalDefaultParams = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues["it:skip"] = -Not $IsWindows + $EnabledExperimentalFeatures.Contains('PSNativeWindowsTildeExpansion') | Should -BeTrue + } + + AfterAll { + $global:PSDefaultParameterValues = $originalDefaultParams + } + + # Test ~ expansion + It 'Tilde should be replaced by the filesystem provider home directory' { + cmd /c echo ~ | Should -BeExactly ($ExecutionContext.SessionState.Provider.Get("FileSystem").Home) + } + # Test ~ expansion with a path fragment (e.g. ~/foo) + It '~/foo should be replaced by the /foo' { + 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' + cmd /c echo '~\foo' | Should -BeExactly '~\foo' + cmd /c echo "~\foo" | Should -BeExactly '~\foo' + } +} diff --git a/test/powershell/Language/Scripting/ParameterBinding.Tests.ps1 b/test/powershell/Language/Scripting/ParameterBinding.Tests.ps1 index 95d62071ad1..8177adab219 100644 --- a/test/powershell/Language/Scripting/ParameterBinding.Tests.ps1 +++ b/test/powershell/Language/Scripting/ParameterBinding.Tests.ps1 @@ -170,6 +170,19 @@ Describe "Tests for parameter binding" -Tags "CI" { ( get-foo -b b a c d ) -join ',' | Should -BeExactly 'a,c,d' } + It 'Too many parameter sets defined' { + $scriptblock = { + param($numSets=1) + $parameters = (1..($numSets) | ForEach-Object { "[Parameter(parametersetname='set$_')]`$a$_" }) -join ', ' + $body = "param($parameters) 'working'" + $sb = [scriptblock]::Create($body) + & $sb -a1 123 + } + + & $scriptblock -numSets 32 | Should -Be 'working' + { & $scriptblock -numSets 33 } | Should -Throw -ErrorId 'ParsingTooManyParameterSets' + } + It 'Default parameter set with value from remaining arguments case 1' { function get-foo { diff --git a/test/powershell/Language/Scripting/PipelineBehaviour.Tests.ps1 b/test/powershell/Language/Scripting/PipelineBehaviour.Tests.ps1 new file mode 100644 index 00000000000..04993c80a9f --- /dev/null +++ b/test/powershell/Language/Scripting/PipelineBehaviour.Tests.ps1 @@ -0,0 +1,625 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe 'Function Pipeline Behaviour' -Tag 'CI' { + + BeforeAll { + $filePath = "$TestDrive\output.txt" + if (Test-Path $filePath) { + Remove-Item $filePath -Force + } + } + + Context "'Clean' block runs when any other named blocks run" { + + AfterEach { + if (Test-Path $filePath) { + Remove-Item $filePath -Force + } + } + + It "'Clean' block executes only if at least one of the other named blocks executed" { + ## The 'Clean' block is for cleanup purpose. When none of other named blocks execute, + ## there is no point to execute the 'Clean' block, so it will be skipped in this case. + function test-1 { + clean { 'clean-redirected-output' > $filePath } + } + + function test-2 { + End { 'end' } + clean { 'clean-redirected-output' > $filePath } + } + + ## The 'Clean' block is skipped. + test-1 | Should -BeNullOrEmpty + Test-Path -Path $filePath | Should -BeFalse + + ## The 'Clean' block runs. + test-2 | Should -BeExactly 'end' + Test-Path -Path $filePath | Should -BeTrue + Get-Content $filePath | Should -BeExactly 'clean-redirected-output' + } + + It "'Clean' block is skipped when the command doesn't run due to no input from upstream command" { + function test-1 ([switch] $WriteOutput) { + Process { + if ($WriteOutput) { + Write-Output 'process' + } else { + Write-Verbose -Verbose 'process' + } + } + } + + function test-2 { + Process { Write-Output "test-2: $_" } + clean { Write-Warning 'test-2-clean-warning' } + } + + ## No output from 'test-1.Process', so 'test-2.Process' didn't run, and thus 'test-2.Clean' was skipped. + test-1 | test-2 *>&1 | Should -BeNullOrEmpty + + ## Output from 'test-1.Process' would trigger 'test-2.Process' to run, and thus 'test-2.Clean' would run. + $output = test-1 -WriteOutput | test-2 *>&1 + $output | Should -Be @('test-2: process', 'test-2-clean-warning') + } + + It "'Clean' block is skipped when the command doesn't run due to terminating error from upstream Process block" { + function test-1 ([switch] $ThrowException) { + Process { + if ($ThrowException) { + throw 'process' + } else { + Write-Output 'process' + } + } + } + + function test-2 { + Process { Write-Output "test-2: $_" } + clean { 'clean-redirected-output' > $filePath } + } + + $failure = $null + try { test-1 -ThrowException | test-2 } catch { $failure = $_ } + $failure | Should -Not -BeNullOrEmpty + $failure.Exception.Message | Should -BeExactly 'process' + ## 'test-2' didn't run because 'test-1' throws terminating exception, so 'test-2.Clean' didn't run either. + Test-Path -Path $filePath | Should -BeFalse + + test-1 | test-2 | Should -BeExactly 'test-2: process' + Test-Path -Path $filePath | Should -BeTrue + Get-Content $filePath | Should -BeExactly 'clean-redirected-output' + } + + It "'Clean' block is skipped when the command doesn't run due to terminating error from upstream Begin block" { + function test-1 { + Begin { throw 'begin' } + End { 'end' } + } + + function test-2 { + Begin { 'begin' } + Process { Write-Output "test-2: $_" } + clean { 'clean-redirected-output' > $filePath } + } + + $failure = $null + try { test-1 | test-2 } catch { $failure = $_ } + $failure | Should -Not -BeNullOrEmpty + $failure.Exception.Message | Should -BeExactly 'begin' + ## 'test-2' didn't run because 'test-1' throws terminating exception, so 'test-2.Clean' didn't run either. + Test-Path -Path $filePath | Should -BeFalse + } + + It "'Clean' block runs when '' runs" -TestCases @( + @{ Script = { [CmdletBinding()]param() begin { 'output' } clean { Write-Warning 'clean-warning' } }; BlockName = 'Begin' } + @{ Script = { [CmdletBinding()]param() process { 'output' } clean { Write-Warning 'clean-warning' } }; BlockName = 'Process' } + @{ Script = { [CmdletBinding()]param() end { 'output' } clean { Write-Warning 'clean-warning' } }; BlockName = 'End' } + ) { + param($Script, $BlockName) + + & $Script -WarningVariable wv | Should -BeExactly 'output' + $wv | Should -BeExactly 'clean-warning' + } + + It "'Clean' block runs when '' throws terminating error" -TestCases @( + @{ Script = { [CmdletBinding()]param() begin { throw 'failure' } clean { Write-Warning 'clean-warning' } }; BlockName = 'Begin' } + @{ Script = { [CmdletBinding()]param() process { throw 'failure' } clean { Write-Warning 'clean-warning' } }; BlockName = 'Process' } + @{ Script = { [CmdletBinding()]param() end { throw 'failure' } clean { Write-Warning 'clean-warning' } }; BlockName = 'End' } + ) { + param($Script, $BlockName) + + $failure = $null + try { & $Script -WarningVariable wv } catch { $failure = $_ } + $failure | Should -Not -BeNullOrEmpty + $failure.Exception.Message | Should -BeExactly 'failure' + $wv | Should -BeExactly 'clean-warning' + } + + It "'Clean' block runs in pipeline - simple function" { + function test-1 { + param([switch] $EmitError) + process { + if ($EmitError) { + throw 'test-1-process-error' + } else { + Write-Output 'test-1' + } + } + + clean { 'test-1-clean' >> $filePath } + } + + function test-2 { + begin { Write-Verbose -Verbose 'test-2-begin' } + process { $_ } + clean { 'test-2-clean' >> $filePath } + } + + function test-3 { + end { Write-Verbose -Verbose 'test-3-end' } + clean { 'test-3-clean' >> $filePath } + } + + ## All command will run, so all 'Clean' blocks will run + test-1 | test-2 | test-3 + Test-Path $filePath | Should -BeTrue + $content = Get-Content $filePath + $content | Should -Be @('test-1-clean', 'test-2-clean', 'test-3-clean') + + $failure = $null + Remove-Item $filePath -Force + try { + test-1 -EmitError | test-2 | test-3 + } catch { + $failure = $_ + } + + ## Exception is thrown from 'test-1.Process'. By that time, the 'test-2.Begin' has run, + ## so 'test-2.Clean' will run. However, 'test-3.End' won't run, so 'test-3.Clean' won't run. + $failure | Should -Not -BeNullOrEmpty + $failure.Exception.Message | Should -BeExactly 'test-1-process-error' + Test-Path $filePath | Should -BeTrue + $content = Get-Content $filePath + $content | Should -Be @('test-1-clean', 'test-2-clean') + } + + It "'Clean' block runs in pipeline - advanced function" { + function test-1 { + [CmdletBinding()] + param([switch] $EmitError) + process { + if ($EmitError) { + throw 'test-1-process-error' + } else { + Write-Output 'test-1' + } + } + + clean { 'test-1-clean' >> $filePath } + } + + function test-2 { + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline)] + $pipeInput + ) + + begin { Write-Verbose -Verbose 'test-2-begin' } + process { $pipeInput } + clean { 'test-2-clean' >> $filePath } + } + + function test-3 { + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline)] + $pipeInput + ) + + end { Write-Verbose -Verbose 'test-3-end' } + clean { 'test-3-clean' >> $filePath } + } + + ## All command will run, so all 'Clean' blocks will run + test-1 | test-2 | test-3 + Test-Path $filePath | Should -BeTrue + $content = Get-Content $filePath + $content | Should -Be @('test-1-clean', 'test-2-clean', 'test-3-clean') + + + $failure = $null + Remove-Item $filePath -Force + ## Exception will be thrown from 'test-1.Process'. By that time, the 'test-2.Begin' has run, + ## so 'test-2.Clean' will run. However, 'test-3.End' won't run, so 'test-3.Clean' won't run. + try { + test-1 -EmitError | test-2 | test-3 + } catch { + $failure = $_ + } + $failure | Should -Not -BeNullOrEmpty + $failure.Exception.Message | Should -BeExactly 'test-1-process-error' + Test-Path $filePath | Should -BeTrue + $content = Get-Content $filePath + $content | Should -Be @('test-1-clean', 'test-2-clean') + } + + It 'does not execute End {} if the pipeline is halted during Process {}' { + # We don't need Should -Not -Throw as if this reaches end{} and throws the test will fail anyway. + 1..10 | + & { + begin { "BEGIN" } + process { "PROCESS $_" } + end { "END"; throw "This should not be reached." } + } | + Select-Object -First 3 | + Should -Be @( "BEGIN", "PROCESS 1", "PROCESS 2" ) + } + + It "still executes 'Clean' block if the pipeline is halted" { + 1..10 | + & { + process { $_ } + clean { "Clean block hit" > $filePath } + } | + Select-Object -First 1 | + Should -Be 1 + + Test-Path $filePath | Should -BeTrue + Get-Content $filePath | Should -BeExactly 'Clean block hit' + } + + It "Select-Object in pipeline" { + function bar { + process { 'bar_' + $_ } end { 'bar_end' } clean { 'bar_clean' > $filePath } + } + + function zoo { + process { 'zoo_' + $_ } end { 'zoo_end' } clean { 'zoo_clean' >> $filePath } + } + + 1..10 | bar | Select-Object -First 2 | zoo | Should -Be @('zoo_bar_1', 'zoo_bar_2', 'zoo_end') + Test-Path $filePath | Should -BeTrue + $content = Get-Content $filePath + $content | Should -Be @('bar_clean', 'zoo_clean') + } + } + + Context 'Streams from Named Blocks' { + + It 'Permits output from named block: